// ResetUserPassword is a route which is called when accessing the page generated dispatched with // account recovery emails. func ResetUserPassword(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(vestigo.Param(r, "id")) if err != nil { log.Println("route ResetUserPassword, strconv.Atoi:", err) render.R.JSON(w, 400, map[string]interface{}{"error": "User ID could not be parsed from request URL."}) return } var user User user.ID = int64(id) entry, err := user.Get() if err != nil { log.Println("route ResetUserPassword, user.Get:", err) if err.Error() == "not found" { render.R.JSON(w, 400, map[string]interface{}{"error": "User with that ID does not exist."}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } // this ensures that accounts won't be compromised by posting recovery string as empty, // which would otherwise result in succesful password reset UUID := uuid.Parse(vestigo.Param(r, "recovery")) if UUID == nil { log.Println("route ResetUserPassword, uuid.Parse:", err) log.Println("there was a problem trying to verify password reset UUID for", entry.Email) render.R.JSON(w, 400, map[string]interface{}{"error": "Could not parse UUID from the request."}) return } if entry.Recovery == vestigo.Param(r, "recovery") { newpassword := context.Get(r, "newpassword").(string) entry.Password = newpassword _, err = user.PasswordReset(entry) if err != nil { log.Println("route ResetUserPassword, user.PasswordReset:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } switch Root(r) { case "api": render.R.JSON(w, 200, map[string]interface{}{"success": "Password was updated successfully."}) case "user": http.Redirect(w, r, "/user/login", 302) } } }
func (p *RelayMsgParser) SummaryHandler() http.HandlerFunc { // Initialize cache container with 1 second TTL, checks running twice a second. c := cache.New(1*time.Second, 500*time.Millisecond) return func(w http.ResponseWriter, r *http.Request) { localpart := vestigo.Param(r, "localpart") // Check cache first jsonUntyped, found := c.Get(localpart) if found { jsonBytes := jsonUntyped.([]byte) log.Printf("SummarizeEvents (cache): hit for [%s]", localpart) w.Write(jsonBytes) return } rows, err := p.Dbh.Query(fmt.Sprintf(` SELECT subject, count(distinct(smtp_from)) FROM %s.relay_messages WHERE smtp_to = $1 ||'@'|| $2 GROUP BY 1 `, p.Schema), localpart, p.Domain) if err != nil { log.Printf("SummarizeEvents (SELECT): %s", err) http.Error(w, "Database error", http.StatusInternalServerError) return } defer rows.Close() res := map[string][]SummaryResponse{} for rows.Next() { if rows.Err() == io.EOF { break } s := SummaryResponse{} if err = rows.Scan(&s.Subject, &s.Count); err != nil { log.Printf("SummarizeEvents (Scan): %s", err) http.Error(w, "Database error", http.StatusInternalServerError) return } res["results"] = append(res["results"], s) } if err = rows.Err(); err != nil { log.Printf("SummarizeEvents (Err): %s", err) http.Error(w, "Database error", http.StatusInternalServerError) return } jsonBytes, err := json.Marshal(res) if err != nil { log.Printf("SummarizeEvents (JSON): %s", err) http.Error(w, "Encoding error", http.StatusInternalServerError) return } // Add result to cache c.Set(localpart, jsonBytes, cache.DefaultExpiration) w.Write(jsonBytes) } }
func ExampleManyRoutes() { // new router router := vestigo.NewRouter() // standard http.HandlerFunc handler := func(w http.ResponseWriter, r *http.Request) { fmt.Printf("version: %s, resource: %s\n", vestigo.Param(r, "version"), r.URL.Path) } // setup a GET /v:version/hi endpoint route in router router.Get("/v:version/hi", handler) // setup a GET /v:version/hi endpoint route in router router.Post("/v:version/hi", handler) // setup a GET /v:version/hi endpoint route in router router.Put("/v:version/hi", handler) // setup a GET /v:version/hi endpoint route in router router.Delete("/v:version/hi", handler) // setup a GET /v:version/hi endpoint route in router router.Patch("/v:version/hi", handler) // create a new request and response writer r, _ := http.NewRequest("PATCH", "/v2.3/hi", nil) w := httptest.NewRecorder() // execute the request router.ServeHTTP(w, r) // Output: version: 2.3, resource: /v2.3/hi }
// ReadUser is a route which fetches user according to parameter "id" on API side and according to retrieved // session cookie on frontend side. // Returns user struct with all posts merged to object on API call. Frontend call will render user "home" page, "user/index.tmpl". func ReadUser(w http.ResponseWriter, r *http.Request) { var user User switch Root(r) { case "api": id, err := strconv.ParseInt(vestigo.Param(r, "id"), 10, 64) if err != nil { log.Println("route ReadUser, strconv.Atoi:", err) render.R.JSON(w, 400, map[string]interface{}{"error": "The user ID could not be parsed from the request URL."}) return } user.ID = id user, err := user.Get() if err != nil { log.Println("route ReadUser, user.Get:", err) if err.Error() == "not found" { render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } for i, post := range user.Posts { if !post.Published { user.Posts = append(user.Posts[:i], user.Posts[i+1:]...) } } if len(user.Posts) == 0 { p := make([]Post, 0) user.Posts = p } render.R.JSON(w, 200, user) case "user": id, ok := SessionGetValue(r, "id") if !ok { log.Println("route ReadUser, SessionGetValue:", ok) SessionDelete(w, r, "id") render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.") return } user.ID = id user, err := user.Get() if err != nil { log.Println("route ReadUser, user.Get:", err) SessionDelete(w, r, "id") render.R.HTML(w, 500, "error", err) return } render.R.HTML(w, 200, "user/index", user) } }
// Get or search.Get returns all posts which contain parameter search.Query in either // post.Title or post.Content. // Returns []Post and error object. func (search Search) Get() (Search, error) { var post Post posts, err := post.GetAll() if err != nil { return search, err } for _, post := range posts { if post.Published { // posts are searched for a match in both content and title, so here // we declare two scanners for them content := bufio.NewScanner(strings.NewReader(post.Markdown)) title := bufio.NewScanner(strings.NewReader(post.Title)) // Blackfriday makes smartypants corrections some characters, which break the search content.Split(bufio.ScanWords) title.Split(bufio.ScanWords) // content is scanned trough Jaro-Winkler distance with // quite strict matching score of 0.9/1 // matching score this high would most likely catch only different // capitalization and small typos // // since we are already in a for loop, we have to break the // iteration here by going to label End to avoid showing a // duplicate search result // // the condition after the OR operator limits searches to words basically // for example, searching for foobarbarbar would match foobar, but for now // we want to limit the searches to contain only the word foobar for content.Scan() { if jwd.Calculate(content.Text(), search.Query) >= 0.9 || strings.Contains(search.Query, content.Text()+" ") { search.Posts = append(search.Posts, post) goto End } } for title.Scan() { if jwd.Calculate(title.Text(), search.Query) >= 0.9 || strings.Contains(search.Query, title.Text()+" ") { search.Posts = append(search.Posts, post) goto End } } } End: } if len(search.Posts) == 0 { search.Posts = make([]Post, 0) } return search, nil } // SearchPost is a route which returns all posts and aggregates the ones which contain // the POSTed search query in either Title or Content field. func SearchPost(w http.ResponseWriter, r *http.Request) { search, err := GetSearch(r) if err != nil { log.Println("route SearchPost, context GetSearch:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } search, err = search.Get() if err != nil { log.Println("route SearchPost, search.Get:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } switch Root(r) { case "api": render.R.JSON(w, 200, search.Posts) case "posts": render.R.HTML(w, 200, "search", search.Posts) } } // CreatePost is a route which creates a new post according to the posted data. // API renderponse contains the created post object and normal request redirects to "/user" page. // Does not publish the post automatically. See PublishPost for more. func CreatePost(w http.ResponseWriter, r *http.Request) { post, err := GetPost(r) if err != nil { log.Println("route CreatePost, context GetPost:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } var user User id, ok := SessionGetValue(r, "id") if !ok { log.Println("route CreatePost, SessionGetValue:", ok) SessionDelete(w, r, "id") render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.") return } user.ID = id user, err = user.Get() if err != nil { log.Println("route CreatePost, user.Get:", err) SessionDelete(w, r, "id") render.R.HTML(w, 500, "error", err) return } post, err = post.Insert(user) if err != nil { log.Println("route CreatePost, post.Insert:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } switch Root(r) { case "api": render.R.JSON(w, 200, post) case "posts": http.Redirect(w, r, "/user", 302) } } // ReadPosts is a route which returns all posts without merged owner data (although the object does include author field) // Not available on frontend, so therefore it only returns a JSON renderponse, hence the post iteration in Go. func ReadPosts(w http.ResponseWriter, r *http.Request) { var post Post published := make([]Post, 0) posts, err := post.GetAll() if err != nil { log.Println("route ReadPosts, post.GetAll:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } for _, post := range posts { if post.Published { published = append(published, post) } } render.R.JSON(w, 200, published) } // ReadPost is a route which returns post with given post.Slug. // Returns post data on JSON call and displays a formatted page on frontend. func ReadPost(w http.ResponseWriter, r *http.Request) { log.Println("url query:", r.URL.Query()) var post Post if vestigo.Param(r, "slug") == "new" { render.R.JSON(w, 400, map[string]interface{}{"error": "There can't be a post called 'new'."}) return } post.Slug = vestigo.Param(r, "slug") post, err := post.Get() if err != nil { log.Println("route ReadPost, post.Get:", err) if err.Error() == "not found" { render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } go post.Increment() switch Root(r) { case "api": render.R.JSON(w, 200, post) case "post": render.R.HTML(w, 200, "post/display", post) } } // EditPost is a route which returns a post object to be displayed and edited on frontend. // Not available for JSON API. // Analogous to ReadPost. Could be replaced at some point. func EditPost(w http.ResponseWriter, r *http.Request) { var post Post post.Slug = vestigo.Param(r, "slug") post, err := post.Get() if err != nil { log.Println("route EditPost, post.Get:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } render.R.HTML(w, 200, "post/edit", post) } // UpdatePost is a route which updates a post defined by martini parameter "title" with posted data. // Requirender session cookie. JSON request returns the updated post object, frontend call will redirect to "/user". func UpdatePost(w http.ResponseWriter, r *http.Request) { var post Post post.Slug = vestigo.Param(r, "slug") post, err := post.Get() if err != nil { log.Println("route UpdatePost, post.Get:", err) if err.Error() == "not found" { render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } id, ok := SessionGetValue(r, "id") if !ok { log.Println("route UpdatePost, SessionGetValue:", ok) SessionDelete(w, r, "id") render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.") return } if post.Author != id { log.Println("route UpdatePost, post.Author and id mismatch") render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"}) return } entry, err := GetPost(r) if err != nil { log.Println("route UpdatePost, context GetPost:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } post, err = post.Update(entry) if err != nil { log.Println("route UpdatePost, post.Update:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } switch Root(r) { case "api": render.R.JSON(w, 200, post) case "post": http.Redirect(w, r, "/user", 302) } } // PublishPost is a route which publishes a post and therefore making it appear on frontpage and search. // JSON request returns `HTTP 200 {"success": "Post published"}` on success. Frontend call will redirect to // published page. // Requirender active session cookie. func PublishPost(w http.ResponseWriter, r *http.Request) { var post Post post.Slug = vestigo.Param(r, "slug") post, err := post.Get() if err != nil { log.Println("route PublishPost, post.Get:", err) if err.Error() == "not found" { render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } id, ok := SessionGetValue(r, "id") if !ok { log.Println("route PublishPost, SessionGetValue:", ok) SessionDelete(w, r, "id") render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.") return } if post.Author != id { log.Println("route PublishPost, post.Author and id mismatch") render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"}) return } var entry Post entry = post entry.Published = true post, err = post.Update(entry) if err != nil { log.Println("route PublishPost, post.Update:", err) if err.Error() == "unauthorized" { render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } switch Root(r) { case "api": render.R.JSON(w, 200, map[string]interface{}{"success": "Post published"}) case "post": http.Redirect(w, r, "/post/"+post.Slug, 302) } } // UnpublishPost is a route which unpublishes a post and therefore making it disappear from frontpage and search. // JSON request returns `HTTP 200 {"success": "Post unpublished"}` on success. Frontend call will redirect to // user control panel. // Requirender active session cookie. // The route is anecdotal to route PublishPost(). func UnpublishPost(w http.ResponseWriter, r *http.Request) { var post Post post.Slug = vestigo.Param(r, "slug") post, err := post.Get() if err != nil { log.Println("route UnpublishPost, post.Get:", err) if err.Error() == "not found" { render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } id, ok := SessionGetValue(r, "id") if !ok { log.Println("route UnpublishPost, SessionGetValue:", ok) SessionDelete(w, r, "id") render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.") return } if post.Author != id { log.Println("route UnpublishPost, author mismatch") render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"}) return } err = post.Unpublish() if err != nil { log.Println("route UnpublishPost, post.Unpublish:", err) render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } switch Root(r) { case "api": render.R.JSON(w, 200, map[string]interface{}{"success": "Post unpublished"}) case "post": http.Redirect(w, r, "/user", 302) } } // DeletePost is a route which deletes a post according to martini parameter "title". // JSON request returns `HTTP 200 {"success": "Post deleted"}` on success. Frontend call will redirect to // "/user" page on successful request. // Requirender active session cookie. func DeletePost(w http.ResponseWriter, r *http.Request) { var post Post post.Slug = vestigo.Param(r, "slug") post, err := post.Get() if err != nil { log.Println("route DeletePost, post.Get:", err) if err.Error() == "not found" { render.R.JSON(w, 404, map[string]interface{}{"error": "Not found"}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } id, ok := SessionGetValue(r, "id") if !ok { log.Println("route DeletePost, SessionGetValue:", ok) SessionDelete(w, r, "id") render.R.HTML(w, 500, "error", "Session could not be fetched. Please log in again.") return } if post.Author != id { log.Println("route DeletePost, author mismatch") render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"}) return } err = post.Delete() if err != nil { log.Println("route DeletePost, post.Delete:", err) if err.Error() == "unauthorized" { render.R.JSON(w, 401, map[string]interface{}{"error": "Unauthorized"}) return } render.R.JSON(w, 500, map[string]interface{}{"error": "Internal server error"}) return } switch Root(r) { case "api": render.R.JSON(w, 200, map[string]interface{}{"success": "Post deleted"}) case "post": http.Redirect(w, r, "/user", 302) } }