// Delete deletes the resume. // // Method POST // // Route /resume/delete/:id // // Restrictions Yes // // Template None func Delete(ctx *echo.Context) error { var flashMessages = flash.New() id, err := utils.GetInt(ctx.Param("id")) if err != nil { utils.SetData(ctx, "Message", tmpl.BadRequestMessage) return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx)) } user := ctx.Get("User").(*models.Person) resume, err := query.GetResumeByID(id) if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, tmpl.NotFoundMessage) } // Users are allowed to delete resumes that they don't own. if resume.PersonID != user.ID { utils.SetData(ctx, "Message", tmpl.BadRequestMessage) return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx)) } err = query.Delete(resume) if err != nil { utils.SetData(ctx, "Message", tmpl.ServerErrorMessage) return ctx.Render(http.StatusInternalServerError, tmpl.ErrServerTpl, utils.GetData(ctx)) } flashMessages.Success("resume successful deleted") flashMessages.Save(ctx) ctx.Redirect(http.StatusFound, "/resume/") return nil }
// Must ensures that any route is authorized to access the next handler // otherwise an error is returned. // // TODO custom not authorized handler? func Must() echo.HandlerFunc { return func(ctx *echo.Context) error { // If this is called somewhere on the middleware chain, if we find the user // we check the context if is set. if v := ctx.Get("IsLoged"); v != nil && v.(bool) == true { return nil } ss, err := store.Get(ctx.Request(), settings.App.Session.Name) if err != nil { // TODO: log this? } if v, ok := ss.Values["userID"]; ok { person, err := query.GetPersonByUserID(v.(int)) if err != nil { // TODO: log this? } if person != nil { // set in main context ctx.Set("IsLoged", true) ctx.Set("User", person) // for templates utils.SetData(ctx, "IsLoged", true) utils.SetData(ctx, "User", person) return nil } } return echo.NewHTTPError(http.StatusUnauthorized) } }
// Register renders registration form. // // Method GET // // Route /auth/register // // Restrictions None // // Template auth/register.html func Register(ctx *echo.Context) error { f := forms.New(utils.GetLang(ctx)) utils.SetData(ctx, authForm, f.RegisterForm()()) // set page tittle to register utils.SetData(ctx, "PageTitle", "register") return ctx.Render(http.StatusOK, tmpl.RegisterTpl, utils.GetData(ctx)) }
// RegionsHome renders regions home page. // // // Method GET // // Route /jobs/regions // // Restrictions None // // Template base/regions.html // func RegionsHome(ctx *echo.Context) error { regs, err := query.GetAllRegions() if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx)) } utils.SetData(ctx, settings.RegionsListKey, regs) return ctx.Render(http.StatusOK, tmpl.BaseRegionsTpl, utils.GetData(ctx)) }
// LoginPost handlers login form, and logs in the user. If the form is valid, the user is // redirected to "/auth/login" with the form validation errors. When the user is validated // redirection is made to "/". // // Method POST // // Route /auth/login // // Restrictions None // // Template None (All actions redirect to other routes ) // // Flash messages may be set before redirection. func LoginPost(ctx *echo.Context) error { var flashMessages = flash.New() f := forms.New(utils.GetLang(ctx)) lf := f.LoginForm()(ctx.Request()) if !lf.IsValid() { utils.SetData(ctx, authForm, lf) ctx.Redirect(http.StatusFound, "/auth/login") return nil } // Check email and password user, err := query.AuthenticateUserByEmail(lf.GetModel().(forms.Login)) if err != nil { log.Error(ctx, err) // We want the user to try again, but rather than rendering the form right // away, we redirect him/her to /auth/login route(where the login process with // start aflsesh albeit with a flash message) flashMessages.Err(msgLoginErr) flashMessages.Save(ctx) ctx.Redirect(http.StatusFound, "/auth/login") return nil } // create a session for the user after the validation has passed. The info stored // in the session is the user ID, where as the key is userID. ss, err := sessStore.Get(ctx.Request(), settings.App.Session.Name) if err != nil { log.Error(ctx, err) } ss.Values["userID"] = user.ID err = ss.Save(ctx.Request(), ctx.Response()) if err != nil { log.Error(ctx, err) } person, err := query.GetPersonByUserID(user.ID) if err != nil { log.Error(ctx, err) flashMessages.Err(msgLoginErr) flashMessages.Save(ctx) ctx.Redirect(http.StatusFound, "/auth/login") return nil } // add context data. IsLoged is just a conveniece in template rendering. the User // contains a models.Person object, where the PersonName is already loaded. utils.SetData(ctx, "IsLoged", true) utils.SetData(ctx, "User", person) flashMessages.Success(msgLoginSuccess) flashMessages.Save(ctx) ctx.Redirect(http.StatusFound, "/") log.Info(ctx, "login success") return nil }
// Login renders login form. // // Method GET // // Route /auth/login // // Restrictions None // // Template auth/login.html // func Login(ctx *echo.Context) error { f := forms.New(utils.GetLang(ctx)) utils.SetData(ctx, authForm, f.LoginForm()()) // set page tittle to login utils.SetData(ctx, "PageTitle", "login") return ctx.Render(http.StatusOK, tmpl.LoginTpl, utils.GetData(ctx)) }
// JobsHome renders jobs home page // // // Method GET // // Route /jobs/ // // Restrictions None // // Template base/jobs.html func JobsHome(ctx *echo.Context) error { jobs, err := query.GetLatestJobs() if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx)) } utils.SetData(ctx, settings.JobsListKey, jobs) utils.SetData(ctx, settings.PageTitleKey, "jobs") return ctx.Render(http.StatusOK, tmpl.BaseJobsHomeTpl, utils.GetData(ctx)) }
// RegionsJobView renders jobs from a gien region. The region name should be in short form. // // // Method GET // // Route /jobs/regions/:name // // Restrictions None // // Template base/regions_job.html // func RegionsJobView(ctx *echo.Context) error { name := ctx.Param("name") jobs, count, err := query.GetJobByRegionShort(name) if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx)) } utils.SetData(ctx, settings.JobsFound, count) utils.SetData(ctx, settings.JobsListKey, jobs) return ctx.Render(http.StatusOK, tmpl.BaseRegionsJobViewTpl, utils.GetData(ctx)) }
// View displays the resume. // // Method GET // // Route /resume/view // // Restrictions Yes // // Template resume/view.html func View(ctx *echo.Context) error { iid, err := utils.GetInt(ctx.Param("id")) if err != nil { utils.SetData(ctx, "Message", tmpl.BadRequestMessage) return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx)) } resume, err := query.GetResumeByID(iid) if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, tmpl.NotFoundMessage) } utils.SetData(ctx, "resume", resume) return ctx.Render(http.StatusOK, tmpl.ResumeViewTpl, utils.GetData(ctx)) }
// Langs sets active language in the request context. func Langs() echo.HandlerFunc { return func(ctx *echo.Context) error { sess, _ := store.Get(ctx.Request(), settings.App.Session.Lang) target := sess.Values[settings.LangSessionKey] if target != nil { utils.SetData(ctx, settings.LangDataKey, target) return nil } sess.Values[settings.LangDataKey] = settings.App.DefaultLang store.Save(ctx.Request(), ctx.Response(), sess) utils.SetData(ctx, settings.LangDataKey, settings.App.DefaultLang) return nil } }
// Home shows the resumes home page. // // Method GET // // Route /resume/ // // Restrictions Yes // // Template resume/home.html func Home(ctx *echo.Context) error { user := ctx.Get("User").(*models.Person) if res, err := query.GetAllPersonResumes(user); err == nil { utils.SetData(ctx, "resumes", res) } return ctx.Render(http.StatusOK, tmpl.ResumeHomeTpl, utils.GetData(ctx)) }
// RegisterPost handles registration form, and create a session for the new user if the registration // process is complete. // // Method POST // // Route /auth/register // // Restrictions None // // Template None (All actions redirect to other routes ) // // Flash messages may be set before redirection. func RegisterPost(ctx *echo.Context) error { var flashMessages = flash.New() f := forms.New(utils.GetLang(ctx)) lf := f.RegisterForm()(ctx.Request()) if !lf.IsValid() { // Case the form is not valid, ships it back with the errors exclusively utils.SetData(ctx, authForm, lf) return ctx.Render(http.StatusOK, tmpl.RegisterTpl, utils.GetData(ctx)) } // we are not interested in the returned user, rather we make sure the user has // been created. _, err := query.CreateNewUser(lf.GetModel().(forms.Register)) if err != nil { flashMessages.Err(msgAccountCreateFailed) flashMessages.Save(ctx) ctx.Redirect(http.StatusFound, "/auth/register") return nil } // TODO: improve the message to include directions to use the current email and // password to login? flashMessages.Success(msgAccountCreate) flashMessages.Save(ctx) // Don't create session in this route, its best to leave only one place which // messes with the main user session. So we redirect to the login page, and encourage // the user to login. ctx.Redirect(http.StatusFound, "/auth/login") return nil }
// JobView displays a single job by the given job id. // // // Method GET // // Route /jobs/view/:id // // Restrictions None // // Template base/jobs_view.html func JobView(ctx *echo.Context) error { id, err := utils.GetInt(ctx.Param("id")) if err != nil { utils.SetData(ctx, "Message", tmpl.BadRequestMessage) return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx)) } job, err := query.GetJobByID(id) if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx)) } if job != nil { utils.SetData(ctx, "Job", job) } return ctx.Render(http.StatusOK, tmpl.BaseJobsViewTpl, utils.GetData(ctx)) }
// RegionsJobPaginate a route frr /jobs/regions/:name/:from/:to. It handles pagination where // form to is offset and limit respectively. // // For example route "/jobs/regions/mza/2/4" will render from 2nd to 4th latest jobs from mwanza. // // Method GET // // Route /jobs/regions/:name/:from/:to // // Restrictions None // // Template base/regions.html func RegionsJobPaginate(ctx *echo.Context) error { name := ctx.Param("name") offset, err := utils.GetInt(ctx.Param("from")) if err != nil { utils.SetData(ctx, "Message", tmpl.BadRequestMessage) return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx)) } limit, err := utils.GetInt(ctx.Param("to")) if err != nil { utils.SetData(ctx, "Message", tmpl.BadRequestMessage) return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx)) } jobs, err := query.GetJobByRegionPaginate(name, offset, limit) if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx)) } utils.SetData(ctx, settings.JobsListKey, jobs) return ctx.Render(http.StatusOK, tmpl.BaseRegionsPaginateTpl, utils.GetData(ctx)) }
// Update renders the resume update page. // // Method GET // // Route /resume/update/:id // // Restrictions Yes // // Template None func Update(ctx *echo.Context) error { id, err := utils.GetInt(ctx.Param("id")) if err != nil { utils.SetData(ctx, "Message", tmpl.BadRequestMessage) return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx)) } user := ctx.Get("User").(*models.Person) resume, err := query.GetResumeByID(id) if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, tmpl.NotFoundMessage) } // Users are allowed to update resumes that they own. if resume.PersonID != user.ID { utils.SetData(ctx, "Message", tmpl.BadRequestMessage) return ctx.Render(http.StatusBadRequest, tmpl.ErrBadRequest, utils.GetData(ctx)) } utils.SetData(ctx, "resume", resume) return ctx.Render(http.StatusOK, tmpl.ResumeUpddateTpl, utils.GetData(ctx)) }
// DocsHome renders the home.md document for the given language. // // Method GET // // Route /docs // // Restrictions None // // Template base/docs_index.html // func DocsHome(ctx *echo.Context) error { data := utils.GetData(ctx).(utils.Data) lang := data.Get(settings.LangDataKey).(string) home := settings.DocsPath + "/" + lang + "/" + settings.DocsIndexPage d, err := static.Asset(home) if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx)) } data.Set("doc", string(d)) data.Set(docIndex, getDocIndex(lang)) data.Set("PageTitle", settings.DocsIndexPage) return ctx.Render(http.StatusOK, tmpl.BaseDocsHomeTpl, data) }
// Docs renders individual zedlist document. // // Method GET // // Route /docs/:name // // Restrictions None // // Template base/docs.html func Docs(ctx *echo.Context) error { data := utils.GetData(ctx).(utils.Data) lang := data.Get(settings.LangDataKey).(string) fname := ctx.Param("name") if filepath.Ext(fname) != ".md" { fname = fname + ".md" } fPath := settings.DocsPath + "/" + lang + "/" + fname d, err := static.Asset(fPath) if err != nil { utils.SetData(ctx, "Message", tmpl.NotFoundMessage) return ctx.Render(http.StatusNotFound, tmpl.ErrNotFoundTpl, utils.GetData(ctx)) } data.Set("doc", string(d)) data.Set("PageTitle", fname) data.Set(docIndex, getDocIndex(lang)) return ctx.Render(http.StatusOK, tmpl.BaseDocTpl, data) }
// Create creates a new resume. // // Method POST // // Route /resume/new // // Restrictions Yes // // Template None func Create(ctx *echo.Context) error { var flashMessages = flash.New() r := ctx.Request() r.ParseForm() name := r.Form.Get("resume_name") user := ctx.Get("User").(*models.Person) resume := models.SampleResume() resume.Name = name err := query.CreateResume(user, resume) if err != nil { utils.SetData(ctx, "Message", tmpl.ServerErrorMessage) return ctx.Render(http.StatusInternalServerError, tmpl.ErrServerTpl, utils.GetData(ctx)) } flashMessages.Success("successful created a new resume") flashMessages.Save(ctx) // Redirect to the update page for further updating of the resume. ctx.Redirect(http.StatusFound, fmt.Sprintf("/resume/update/%d", resume.ID)) return nil }
// Tokens adds csrf token context. the context key is CsrfToken func Tokens() echo.HandlerFunc { return func(ctx *echo.Context) error { utils.SetData(ctx, "CsrfToken", nosurf.Token(ctx.Request())) return nil } }
// Profile renders user profile. // // Method GET // // Route /dash/profile // // Restrictions Yes // // Template dash/profile.html func Profile(ctx *echo.Context) error { utils.SetData(ctx, "PageTitle", "profile") return ctx.Render(http.StatusOK, tmpl.DashProfileTpl, utils.GetData(ctx)) }
// Home renders dashboard home page. // // Method GET // // Route /dash/ // // Restrictions Yes // // Template dash/home.html func Home(ctx *echo.Context) error { utils.SetData(ctx, "PageTitle", "dashboard") f := forms.New(utils.GetLang(ctx)) utils.SetData(ctx, "JobForm", f.JobForm()()) return ctx.Render(http.StatusOK, tmpl.DashHomeTpl, utils.GetData(ctx)) }
// JobsNewGet renders the new job form. // // Method GET // // Route /dash/jobs/new // // Restrictions Yes // // Template dash/jobs_new.html func JobsNewGet(ctx *echo.Context) error { f := forms.New(utils.GetLang(ctx)) utils.SetData(ctx, "PageTitle", "new job") utils.SetData(ctx, "JobForm", f.JobForm()()) return ctx.Render(http.StatusOK, tmpl.DashJobTpl, utils.GetData(ctx)) }