package controllers import ( "bytes" "fmt" "html/template" "net/http" "strings" "git.kirsle.net/apps/gophertype/pkg/authentication" "git.kirsle.net/apps/gophertype/pkg/glue" "git.kirsle.net/apps/gophertype/pkg/models" "git.kirsle.net/apps/gophertype/pkg/responses" "git.kirsle.net/apps/gophertype/pkg/session" "github.com/albrow/forms" uuid "github.com/satori/go.uuid" ) func init() { glue.Register(glue.Endpoint{ Path: "/comments", Methods: []string{"POST"}, Handler: PostComment, }) glue.Register(glue.Endpoint{ Path: "/comments/quick-delete", Methods: []string{"GET"}, Handler: CommentQuickDelete, }) } // RenderComments returns the partial comments HTML to embed on a page. func RenderComments(w http.ResponseWriter, r *http.Request, subject string, ids ...string) template.HTML { thread := strings.Join(ids, "-") return renderComments(w, r, subject, thread, false) } // RenderCommentsRO returns a read-only comment view. func RenderCommentsRO(w http.ResponseWriter, r *http.Request, ids ...string) template.HTML { thread := strings.Join(ids, "-") return renderComments(w, r, "", thread, true) } // RenderComment renders the HTML partial for a single comment in the thread. func RenderComment(w http.ResponseWriter, r *http.Request, com models.Comment, originURL string, editable bool) template.HTML { var ( html = bytes.NewBuffer([]byte{}) v = responses.NewTemplateVars(html, r) editToken = getEditToken(w, r) ) v.V["Comment"] = com v.V["Editable"] = editable && (com.EditToken == editToken || authentication.LoggedIn(r)) v.V["OriginURL"] = originURL responses.PartialTemplate(html, r, "_builtin/comments/entry.partial.gohtml", v) return template.HTML(html.String()) } // RenderCommentForm renders the comment entry form HTML onto a page. func RenderCommentForm(r *http.Request, com models.Comment, subject, threadID, originURL string) template.HTML { var ( html = bytes.NewBuffer([]byte{}) v = responses.NewTemplateVars(html, r) ) v.V["Comment"] = com v.V["Subject"] = subject v.V["ThreadID"] = threadID v.V["OriginURL"] = originURL responses.PartialTemplate(html, r, "_builtin/comments/form.partial.gohtml", v) return template.HTML(html.String()) } // renderComments is the internal logic for both RenderComments and RenderCommentsRO. func renderComments(w http.ResponseWriter, r *http.Request, subject string, thread string, readonly bool) template.HTML { var ( html = bytes.NewBuffer([]byte{}) v = responses.NewTemplateVars(w, r) ses = session.Get(r) ) comments, err := models.Comments.GetThread(thread) if err != nil { return template.HTML(fmt.Sprintf("[comment error: %s]", err)) } // Load their cached name and email from any previous comments the user posted. name, _ := ses.Values["c.name"].(string) email, _ := ses.Values["c.email"].(string) editToken, _ := ses.Values["c.token"].(string) // Logged in? Populate defaults from the user info. if currentUser, err := authentication.CurrentUser(r); err == nil { name = currentUser.Name email = currentUser.Email } // v.V["posts"] = posts.Posts v.V["Readonly"] = readonly v.V["Subject"] = subject v.V["ThreadID"] = thread v.V["Comments"] = comments v.V["OriginURL"] = r.URL.Path v.V["NewComment"] = models.Comment{ Name: name, Email: email, EditToken: editToken, } responses.PartialTemplate(html, r, "_builtin/comments/comments.partial.gohtml", v) return template.HTML(html.String()) } // PostComment handles all of the top-level blog index routes: func PostComment(w http.ResponseWriter, r *http.Request) { var ( v = responses.NewTemplateVars(w, r) editToken = getEditToken(w, r) comment = models.Comments.New() ses = session.Get(r) editing bool // true if editing an existing comment ) // Check if the user is logged in. var loggedIn bool currentUser, err := authentication.CurrentUser(r) loggedIn = err == nil // Get form parameters. form, _ := forms.Parse(r) v.V["Subject"] = form.Get("subject") v.V["ThreadID"] = form.Get("thread") v.V["OriginURL"] = form.Get("origin") // Are they editing an existing post ID? if id := form.GetInt("id"); id > 0 { // Load the comment from DB. com, err := models.Comments.Load(id) if err != nil { responses.NotFound(w, r) return } // Verify the user's EditToken matches this comment. if editToken != com.EditToken && !loggedIn { responses.Forbidden(w, r, "You do not have permission to edit that comment.") return } editing = true comment = com } comment.EditToken = editToken for { // Validate form parameters. val := form.Validator() val.Require("body") if !form.GetBool("editing") { comment.ParseForm(form) } if val.HasErrors() { v.ValidationError = val.ErrorMap() break } // Cache their name and email in their session, for future requests. ses.Values["c.email"] = form.Get("email") ses.Values["c.name"] = form.Get("name") ses.Save(r, w) switch form.Get("submit") { case "delete": v.V["deleting"] = true case "confirm-delete": // Delete the comment. err := comment.Delete() if err != nil { session.Flash(w, r, "Error deleting the comment: %s", err) } else { session.Flash(w, r, "Comment has been deleted!") } responses.Redirect(w, r, form.Get("origin")) return case "preview": v.V["preview"] = comment.HTML() case "post": // If we're logged in, tag our user ID with this post. if loggedIn && !editing { comment.UserID = currentUser.ID } // Post their comment. err := comment.Save() if err != nil { session.Flash(w, r, "Error posting comment: %s", err) responses.Redirect(w, r, form.Get("origin")) return } session.Flash(w, r, "Your comment has been added!") responses.Redirect(w, r, form.Get("origin")) return } break } v.V["NewComment"] = comment responses.RenderTemplate(w, r, "_builtin/comments/preview.gohtml", v) } // CommentQuickDelete handles quick-delete links to remove spam comments. func CommentQuickDelete(w http.ResponseWriter, r *http.Request) { v := responses.NewTemplateVars(w, r) responses.RenderTemplate(w, r, "_builtin/blog/view-post.gohtml", v) } // getEditToken gets the edit token from the user's session. func getEditToken(w http.ResponseWriter, r *http.Request) string { ses := session.Get(r) if token, ok := ses.Values["c.token"].(string); ok && len(token) > 0 { return token } token := uuid.NewV4().String() ses.Values["c.token"] = token ses.Save(r, w) return token }