328 lines
9.1 KiB
Go
328 lines
9.1 KiB
Go
package controllers
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.kirsle.net/apps/gophertype/pkg/authentication"
|
|
"git.kirsle.net/apps/gophertype/pkg/glue"
|
|
"git.kirsle.net/apps/gophertype/pkg/mail"
|
|
"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{"GET", "POST"},
|
|
Handler: PostComment,
|
|
})
|
|
glue.Register(glue.Endpoint{
|
|
Path: "/comments/subscription",
|
|
Methods: []string{"GET", "POST"},
|
|
Handler: ManageSubscription,
|
|
})
|
|
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())
|
|
}
|
|
|
|
// ReadComments handles the GET /comments for viewing recently added comments
|
|
// side wide.
|
|
func ReadComments(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
v = responses.NewTemplateVars(w, r)
|
|
query = r.URL.Query()
|
|
)
|
|
|
|
// Query parameters.
|
|
page, _ := strconv.Atoi(query.Get("page"))
|
|
perPage, _ := strconv.Atoi(query.Get("per_page"))
|
|
|
|
// Get the comments.
|
|
comments, err := models.Comments.GetRecent(page, perPage)
|
|
if err != nil {
|
|
responses.Error(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
v.V["PagedComments"] = comments
|
|
responses.RenderTemplate(w, r, "_builtin/comments/recent.gohtml", v)
|
|
}
|
|
|
|
// PostComment handles all of the top-level blog index routes
|
|
// for Post, Preview, Delete comments.
|
|
func PostComment(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodGet {
|
|
ReadComments(w, r)
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Store the OriginURL for this comment.
|
|
comment.OriginURL = form.Get("origin")
|
|
|
|
// 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
|
|
}
|
|
|
|
// Notify site admins and subscribers by email.
|
|
go mail.NotifyComment(v.V["Subject"].(string), v.V["OriginURL"].(string), comment)
|
|
|
|
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)
|
|
}
|
|
|
|
// ManageSubscription helps users unsubscribe from comment threads.
|
|
func ManageSubscription(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
v = responses.NewTemplateVars(w, r)
|
|
query = r.URL.Query()
|
|
qThread = query.Get("t")
|
|
qEmail = strings.ToLower(strings.TrimSpace(query.Get("e")))
|
|
qAll = query.Get("all")
|
|
)
|
|
|
|
// Are we unsubscribing from all?
|
|
if qEmail != "" && qAll != "" {
|
|
err := models.Comments.UnsubscribeFromAll(qEmail)
|
|
if err != nil {
|
|
session.Flash(w, r, "Error unsubscribing you: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "Success: '%s' has been unsubscribed from ALL comment threads.", qEmail)
|
|
}
|
|
responses.Redirect(w, r, "/comments/subscription")
|
|
return
|
|
}
|
|
|
|
// Is there a thread and email in the query string?
|
|
if qThread != "" && qEmail != "" {
|
|
err := models.Comments.UnsubscribeThread(qThread, qEmail)
|
|
if err != nil {
|
|
session.Flash(w, r, "Error unsubscribing you: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "Success: '%s' has been unsubscribed from this comment thread.", qEmail)
|
|
}
|
|
responses.Redirect(w, r, "/comments/subscription")
|
|
return
|
|
}
|
|
|
|
responses.RenderTemplate(w, r, "_builtin/comments/subscription.gohtml", v)
|
|
}
|
|
|
|
// CommentQuickDelete handles quick-delete links to remove spam comments.
|
|
func CommentQuickDelete(w http.ResponseWriter, r *http.Request) {
|
|
// Query parameters.
|
|
var (
|
|
deleteToken = r.URL.Query().Get("d")
|
|
nextURL = r.URL.Query().Get("next")
|
|
)
|
|
|
|
// Look up the comment by thread and quick-delete token.
|
|
comment, err := models.Comments.LoadByDeleteToken(deleteToken)
|
|
if err != nil {
|
|
responses.Forbidden(w, r, "Comment by Delete Token not found.")
|
|
return
|
|
}
|
|
|
|
comment.Delete()
|
|
|
|
session.Flash(w, r, "Comment has been quick-deleted!")
|
|
|
|
if nextURL == "" {
|
|
nextURL = "/"
|
|
}
|
|
responses.Redirect(w, r, nextURL)
|
|
}
|
|
|
|
// 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
|
|
}
|