// POST /users/password/reset func HandlePasswordResetSend(context router.Context) error { // Find the user by email (if not found let them know) // Find the user by hex token in the db email := context.Param("email") user, err := users.First(users.Where("email=?", email)) if err != nil { return router.Redirect(context, "/users/password/reset?message=invalid_email") } // Generate a random token and url for the email token := auth.BytesToHex(auth.RandomToken()) // Generate the url to use in our email base := fmt.Sprintf("%s://%s", context.Request().URL.Scheme, context.Request().URL.Host) url := fmt.Sprintf("%s/users/password?token=%s", base, token) context.Logf("#info sending reset email:%s url:%s", email, url) // Update the user record with with this token p := map[string]string{"reset_token": token} user.Update(p) // Send a password reset email out //mail.Send("mymail") // Tell the user what we have done return router.Redirect(context, "/users/password/sent") }
// HandleIndex serves a get request at /pages func HandleIndex(context router.Context) error { // Authorise err := authorise.Path(context) if err != nil { return router.NotAuthorizedError(err) } // Fetch the pages q := pages.Query().Order("url asc") // Filter if necessary filter := context.Param("filter") if len(filter) > 0 { filter = strings.Replace(filter, "&", "", -1) filter = strings.Replace(filter, " ", "", -1) filter = strings.Replace(filter, " ", " & ", -1) q.Where("(to_tsvector(name) || to_tsvector(summary) || to_tsvector(url) @@ to_tsquery(?) )", filter) } pageList, err := pages.FindAll(q) if err != nil { context.Logf("#error Error indexing pages %s", err) return router.InternalError(err) } // Serve template view := view.New(context) view.AddKey("filter", filter) view.AddKey("pages", pageList) return view.Render() }
// HandleHome displays a list of stories using gravity to order them // used for the home page for gravity rank see votes.go // responds to GET / func HandleHome(context router.Context) error { // Build a query q := stories.Query().Limit(listLimit) // Select only above 0 points, Order by rank, then points, then name q.Where("points > 0").Order("rank desc, points desc, id desc") // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) view.Template("stories/views/index.html.got") if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// Serve a get request at /images // // func HandleIndex(context router.Context) error { // Authorise err := authorise.Path(context) if err != nil { return router.NotAuthorizedError(err) } // Setup context for template view := view.New(context) // Build a query q := images.Query() // Show only published (defaults to showing all) // q.Apply(status.WherePublished) // Order by required order, or default to id asc switch context.Param("order") { case "1": q.Order("created_at desc") case "2": q.Order("updated_at desc") case "3": q.Order("name asc") default: q.Order("id desc") } // Filter if necessary - this assumes name and summary cols filter := context.Param("filter") if len(filter) > 0 { filter = strings.Replace(filter, "&", "", -1) filter = strings.Replace(filter, " ", "", -1) filter = strings.Replace(filter, " ", " & ", -1) q.Where("( to_tsvector(name) || to_tsvector(summary) @@ to_tsquery(?) )", filter) } // Fetch the images imagesList, err := images.FindAll(q) if err != nil { return router.InternalError(err) } // Serve template view.AddKey("filter", filter) view.AddKey("images", imagesList) // Can we add these programatically? view.AddKey("admin_links", helpers.Link("Create image", images.New().URLCreate())) return view.Render() }
// HandleIndex displays a list of stories at /stories func HandleIndex(context router.Context) error { // Build a query q := stories.Query().Limit(listLimit) // Order by date by default q.Where("points > -6").Order("created_at desc") // Filter if necessary - this assumes name and summary cols filter := context.Param("q") if len(filter) > 0 { // Replace special characters with escaped sequence filter = strings.Replace(filter, "_", "\\_", -1) filter = strings.Replace(filter, "%", "\\%", -1) wildcard := "%" + filter + "%" // Perform a wildcard search for name or url q.Where("stories.name ILIKE ? OR stories.url ILIKE ?", wildcard, wildcard) // If filtering, order by rank, not by date q.Order("rank desc, points desc, id desc") } // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// HandleLoginShow handles GET /users/login func HandleLoginShow(context router.Context) error { // Check we have no current user u := authorise.CurrentUser(context) if !u.Anon() { return router.Redirect(context, fmt.Sprintf("/users/%d", u.Id)) } // Render the template view := view.New(context) view.AddKey("error", context.Param("error")) return view.Render() }
// HandleShowKey displays a single user's key func HandleShowKey(context router.Context) error { // Find the user user, err := users.FindName(context.Param("name")) if err != nil { return router.InternalError(err) } // Render the key directly to the httpwriter as text context.Writer().Header().Set("Content-Type", "text/plain; charset=utf-8") _, err = io.WriteString(context.Writer(), user.Key) return err }
// HandleIndex displays a list of posts func HandleIndex(context router.Context) error { // Authorise err := authorise.Path(context) if err != nil { return router.NotAuthorizedError(err) } // Build a query q := posts.Query() // Order by required order, or default to id asc switch context.Param("order") { case "1": q.Order("created desc") case "2": q.Order("updated desc") case "3": q.Order("name asc") default: q.Order("id asc") } // Filter if necessary - this assumes name and summary cols filter := context.Param("filter") if len(filter) > 0 { filter = strings.Replace(filter, "&", "", -1) filter = strings.Replace(filter, " ", "", -1) filter = strings.Replace(filter, " ", " & ", -1) q.Where("( to_tsvector(name) || to_tsvector(summary) @@ to_tsquery(?) )", filter) } // Fetch the posts results, err := posts.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) view.AddKey("filter", filter) view.AddKey("posts", results) return view.Render() }
// HandleShowName displays a single user by name func HandleShowName(context router.Context) error { // Find the user user, err := users.FindName(context.Param("name")) if err != nil { return router.NotFoundError(err) } // Render the template view := view.New(context) view.AddKey("user", user) view.Template("users/views/show.html.got") return view.Render() }
// AuthenticityToken checks the token in the current request func AuthenticityToken(context router.Context) error { token := context.Param(auth.SessionTokenKey) err := auth.CheckAuthenticityToken(token, context.Request()) if err != nil { // If the check fails, log out the user and completely clear the session context.Logf("#warn invalid authenticity token at %v", context) session, err := auth.SessionGet(context.Request()) if err != nil { return err } session.Clear(context.Writer()) } return err }
// HandleUpvoted displays a list of stories the user has upvoted in the past func HandleUpvoted(context router.Context) error { // Build a query q := stories.Query().Limit(listLimit) // Select only above 0 points, Order by rank, then points, then name q.Where("points > 0").Order("rank desc, points desc, id desc") // Select only stories which the user has upvoted user := authorise.CurrentUser(context) if !user.Anon() { // Can we use a join instead? v := query.New("votes", "story_id").Select("select story_id as id from votes").Where("user_id=? AND story_id IS NOT NULL AND points > 0", user.Id) storyIDs := v.ResultIDs() if len(storyIDs) > 0 { q.WhereIn("id", storyIDs) } } // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) view.Template("stories/views/index.html.got") if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// POST /users/password?token=DEADFISH - handle password reset link func HandlePasswordReset(context router.Context) error { token := context.Param("token") if len(token) == 0 { return router.InternalError(fmt.Errorf("Blank reset token")) } // Find the user by hex token in the db user, err := users.First(users.Where("token=?", token)) if err != nil { return router.InternalError(err) } // If we found a user with this token, log the sender in as them // and remove the token from the user so that it can't be used twice // we should possibly add a time limit to tokens too? // Redirect to the user update page so that they can change their password return router.Redirect(context, fmt.Sprintf("/users/%d/update", user.Id)) }
// HandleUpdateShow serves a get request at /images/1/update (show form to update) func HandleUpdateShow(context router.Context) error { // Setup context for template view := view.New(context) image, err := images.Find(context.ParamInt("id")) if err != nil { context.Logf("#error Error finding image %s", err) return router.NotFoundError(err) } // Authorise err = authorise.Resource(context, image) if err != nil { return router.NotAuthorizedError(err) } view.AddKey("redirect", context.Param("redirect")) view.AddKey("image", image) return view.Render() }
// HandleLoginShow shows the page at /users/login func HandleLoginShow(context router.Context) error { // Setup context for template view := view.New(context) // Check we're not already logged in, if so redirect with a message // we could alternatively display an error here? if !authorise.CurrentUser(context).Anon() { return router.Redirect(context, "/?warn=already_logged_in") } switch context.Param("error") { case "failed_email": view.AddKey("warning", "Sorry, we couldn't find a user with that email.") case "failed_password": view.AddKey("warning", "Sorry, the password was incorrect, please try again.") } // Serve return view.Render() }
// HandleCode displays a list of stories linking to repos (github etc) using gravity to order them // responds to GET /stories/code func HandleCode(context router.Context) error { // Build a query q := stories.Query().Where("points > -6").Order("rank desc, points desc, id desc").Limit(listLimit) // Restrict to stories with have a url starting with github.com or bitbucket.org // other code repos can be added later q.Where("url ILIKE 'https://github.com%'").OrWhere("url ILIKE 'https://bitbucket.org'") // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) // view.AddKey("tags", []string{"web", "mobile", "data", "email", "crypto", "data", "graphics", "ui", "security"}) // TODO: remove these calls and put in a filter // - given it is not too expensive, we could just generate tokens on every request view.Template("stories/views/index.html.got") if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// HandleIndex displays a list of stories at /stories func HandleIndex(context router.Context) error { // Build a query q := stories.Query().Limit(listLimit) // Order by date by default q.Where("points > -6").Order("created_at desc") // Filter if necessary - this assumes name and summary cols filter := context.Param("filter") if len(filter) > 0 { context.Logf("FILTER %s", filter) // Replace special characters with escaped sequence filter = strings.Replace(filter, "_", "\\_", -1) filter = strings.Replace(filter, "%", "\\%", -1) // initially very simple, do ilike query for filter with wildcards q.Where("stories.name ILIKE ?", "%"+filter+"%") // If filtering, order by rank, not by date q.Order("rank desc, points desc, id desc") } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) view.AddKey("filter", filter) view.AddKey("stories", results) view.AddKey("meta_title", "Go Hacker News Links") view.AddKey("authenticity_token", authorise.CreateAuthenticityToken(context)) return view.Render() }
// HandleCode displays a list of stories linking to repos (github etc) using gravity to order them // responds to GET /stories/code func HandleCode(context router.Context) error { // Build a query q := stories.Query().Where("points > -6").Order("rank desc, points desc, id desc").Limit(listLimit) // Restrict to stories with have a url starting with github.com or bitbucket.org // other code repos can be added later q.Where("url ILIKE 'https://github.com%'").OrWhere("url ILIKE 'https://bitbucket.org'") // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) view.AddKey("page", page) view.AddKey("stories", results) view.AddKey("pubdate", storiesModTime(results)) view.AddKey("meta_title", "Go Code") view.AddKey("meta_desc", context.Config("meta_desc")) view.AddKey("meta_keywords", context.Config("meta_keywords")) view.AddKey("meta_rss", storiesXMLPath(context)) view.Template("stories/views/index.html.got") if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// HandleIndex displays a list of comments func HandleIndex(context router.Context) error { // No auth this is public // Build a query to fetch latest 100 comments q := comments.Query().Limit(100).Order("created_at desc") // Filter on user id - we only show the actual user's comments // so not a nested view as in HN userID := context.ParamInt("u") if userID > 0 { q.Where("user_id=?", userID) } // Filter if necessary - this assumes name and summary cols filter := context.Param("filter") if len(filter) > 0 { filter = strings.Replace(filter, "&", "", -1) filter = strings.Replace(filter, " ", "", -1) filter = strings.Replace(filter, " ", " & ", -1) q.Where("(to_tsvector(text) @@ to_tsquery(?) )", filter) } // Fetch the comments results, err := comments.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) view.AddKey("filter", filter) view.AddKey("comments", results) view.AddKey("meta_title", "Comments") view.AddKey("authenticity_token", authorise.CreateAuthenticityToken(context)) return view.Render() }
// HandleHome displays a list of stories using gravity to order them // used for the home page for gravity rank see votes.go // responds to GET / func HandleHome(context router.Context) error { // Build a query q := stories.Query().Limit(listLimit) // Select only above 0 points, Order by rank, then points, then name q.Where("points > 0").Order("rank desc, points desc, id desc") // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) view.AddKey("page", page) view.AddKey("stories", results) view.Template("stories/views/index.html.got") view.AddKey("pubdate", storiesModTime(results)) view.AddKey("meta_title", fmt.Sprintf("%s, %s", context.Config("meta_title"), context.Config("meta_desc"))) view.AddKey("meta_desc", context.Config("meta_desc")) view.AddKey("meta_keywords", context.Config("meta_keywords")) view.AddKey("meta_rss", storiesXMLPath(context)) if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// HandleIndex serves a get request at /tags func HandleIndex(context router.Context) error { // Authorise err := authorise.Path(context) if err != nil { return router.NotAuthorizedError(err) } //UpdateAllDottedIds() // Setup context for template view := view.New(context) // Fetch the tags q := tags.RootTags().Order("name asc") // Filter if necessary filter := context.Param("filter") if len(filter) > 0 { filter = strings.Replace(filter, "&", "", -1) filter = strings.Replace(filter, " ", "", -1) filter = strings.Replace(filter, " ", " & ", -1) q.Where("(to_tsvector(name) || to_tsvector(summary) @@ to_tsquery(?))", filter) } tagList, err := tags.FindAll(q) if err != nil { return router.InternalError(err) } // Serve template view.AddKey("filter", filter) view.AddKey("tags", tagList) return view.Render() }
// HandleBlog displays a list of posts in reverse chronological order func HandleBlog(context router.Context) error { // Authorise err := authorise.Path(context) if err != nil { return router.NotAuthorizedError(err) } // Build a query q := posts.Published().Order("created_at desc") // Filter if necessary - this assumes name and summary cols filter := context.Param("filter") if len(filter) > 0 { filter = strings.Replace(filter, "&", "", -1) filter = strings.Replace(filter, " ", "", -1) filter = strings.Replace(filter, " ", " & ", -1) q.Where("( to_tsvector(name) || to_tsvector(summary) @@ to_tsquery(?) )", filter) } // Fetch the posts results, err := posts.FindAll(q) if err != nil { return router.InternalError(err) } context.Logf("POSTS HERE :%v", results) // Render the template view := view.New(context) view.AddKey("filter", filter) view.AddKey("posts", results) view.Template("posts/views/blog.html.got") return view.Render() }
// HandleSetup responds to a POST at /fragmenta/setup // by creating our first user and page func HandleSetup(context router.Context) error { // If we have pages or users already, do not proceed if !missingUsersAndPages() { return router.NotAuthorizedError(nil) } // Take the details given and create the first user params := map[string]string{ "email": context.Param("email"), "password": context.Param("password"), "name": nameFromEmail(context.Param("email")), "status": "100", "role": "100", "title": "Administrator", } uid, err := users.Create(params) if err != nil { return router.InternalError(err) } context.Logf("#info Created user #%d", uid) user, err := users.Find(uid) if err != nil { return router.InternalError(err) } // Login this user automatically - save cookie session, err := auth.Session(context, context.Request()) if err != nil { return router.InternalError(err) } context.Logf("#info Automatic login for first user: %d %s", user.Id, user.Email) session.Set(auth.SessionUserKey, fmt.Sprintf("%d", user.Id)) session.Save(context) // Load our welcomepage template html // and put it into the text field of a new page with id 1 welcomeText, err := ioutil.ReadFile("src/pages/views/welcome.html.got") if err != nil { return router.InternalError(err) } params = map[string]string{ "status": "100", "name": "Fragmenta", "url": "/", "text": string(welcomeText), } _, err = pages.Create(params) if err != nil { return router.InternalError(err) } // Create another couple of simple pages as examples (about and privacy) params = map[string]string{ "status": "100", "name": "About Us", "url": "/about", "text": "<section class=\"narrow\"><h1>About us</h1><p>About us</p></section>", } _, err = pages.Create(params) if err != nil { return router.InternalError(err) } params = map[string]string{ "status": "100", "name": "Privacy Policy", "url": "/privacy", "text": "<section class=\"narrow\"><h1>Privacy Policy</h1><p>We respect your privacy.</p></section>", } _, err = pages.Create(params) if err != nil { return router.InternalError(err) } // Redirect back to the newly populated home page return router.Redirect(context, "/") }
// HandleCreate handles the POST of the create form for stories func HandleCreate(context router.Context) error { // Check csrf token err := authorise.AuthenticityToken(context) if err != nil { return router.NotAuthorizedError(err) } // Check permissions - if not logged in and above 1 points, redirect to error if !authorise.CurrentUser(context).CanSubmit() { return router.NotAuthorizedError(nil, "Sorry", "You need to be registered and have more than 1 points to submit stories.") } // Get user details user := authorise.CurrentUser(context) ip := getUserIP(context) // Check that no story with this url already exists q := stories.Where("url=?", context.Param("url")) duplicates, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } if len(duplicates) > 0 { dupe := duplicates[0] // Add a point to dupe addStoryVote(dupe, user, ip, 1) return router.Redirect(context, dupe.URLShow()) } // Setup context params, err := context.Params() if err != nil { return router.InternalError(err) } // Set a few params params.SetInt("points", 1) params.SetInt("user_id", user.Id) params.Set("user_name", user.Name) id, err := stories.Create(params.Map()) if err != nil { return err // Create returns a router.Error } // Log creation context.Logf("#info Created story id,%d", id) // Redirect to the new story story, err := stories.Find(id) if err != nil { return router.InternalError(err) } // We need to add a vote to the story here too by adding a join to the new id err = recordStoryVote(story, user, ip, +1) if err != nil { return err } // Re-rank stories err = updateStoriesRank() if err != nil { return err } return router.Redirect(context, story.URLIndex()) }
// HandleCreate handles the POST of the create form for files func HandleCreate(context router.Context) error { // Do not perform auth on posts - we could check a shared secret here or similar but that is not secure // better to require siging of posts by users with their own key to confirm identity if we wanted to check submissions. // Parse multipart first - must fix this to do it automatically fileParams, err := context.ParamFiles("file") if err != nil || len(fileParams) < 1 { return router.InternalError(err, "Invalid file", "Sorry, the file upload failed.") } fh := fileParams[0] context.Logf("#info FILE RECD:%v", fh.Filename) // Now extract other params params, err := context.Params() if err != nil { return router.InternalError(err) } // First find the username, if we have no user, reject post //context.Logf("PARAMS:%v", params) // PARAMS:map[sender:[Kenny Grant] recipient:[testtest]] // Check for user with name recipient // Find the user user, err := users.FindName(context.Param("recipient")) if err != nil || user == nil { return router.NotFoundError(err, "User not found", "Sorry this user doesn't exist") } // link with the named recipient user params.SetInt("user_id", user.Id) context.Logf("PARAMS:%v", params) // Ideally perform some identity check on the sender here, and set sender id if we have a user? // Perhaps require sending pgp sig of data? // We only consider the first file id, err := files.Create(params.Map(), fh) if err != nil { return router.InternalError(err) } // Log creation context.Logf("#info Created file id,%d", id) /* _, err := files.Find(id) if err != nil { return router.InternalError(err) } */ // Render a 200 response view := view.New(context) view.Layout("files/views/create.json.got") return view.Render() // return router.Redirect(context, m.URLIndex()) }