website/pkg/controller/comment/post_comment.go
Noah aa8d719fc4 Comment Thread Subscriptions
* Add ability to (un)subscribe from comment threads on Forums and Photos.
* Creating a forum post, replying to a post or adding a comment to a photo
  automatically subscribes you to be notified when somebody else adds a
  comment to the thing later.
* At the top of each comment thread is a link to disable or re-enable your
  subscription. You can join a subscription without even needing to comment.
  If you click to disable notifications, they stay disabled even if you
  add another comment later.
2022-08-27 11:42:48 -07:00

233 lines
6.8 KiB
Go

package comment
import (
"net/http"
"net/url"
"strconv"
"strings"
"code.nonshy.com/nonshy/website/pkg/log"
"code.nonshy.com/nonshy/website/pkg/models"
"code.nonshy.com/nonshy/website/pkg/session"
"code.nonshy.com/nonshy/website/pkg/templates"
)
// PostComment view - for previewing or submitting your comment.
func PostComment() http.HandlerFunc {
tmpl := templates.Must("comment/post_comment.html")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Query params.
var (
tableName = r.FormValue("table_name")
tableID uint64
editCommentID = r.FormValue("edit") // edit your comment
isDelete = r.FormValue("delete") == "true"
intent = r.FormValue("intent") // preview or submit
message = r.PostFormValue("message") // comment body
comment *models.Comment // if editing a comment
fromURL = r.FormValue("next") // what page to send back to
)
// Parse the table ID param.
if idStr := r.FormValue("table_id"); idStr == "" {
session.FlashError(w, r, "Comment table ID required.")
templates.Redirect(w, "/")
return
} else {
if idInt, err := strconv.Atoi(idStr); err != nil {
session.FlashError(w, r, "Comment table ID invalid.")
templates.Redirect(w, "/")
return
} else {
tableID = uint64(idInt)
}
}
// Redirect URL must be relative.
if !strings.HasPrefix(fromURL, "/") {
// Maybe it's URL encoded?
fromURL, _ = url.QueryUnescape(fromURL)
if !strings.HasPrefix(fromURL, "/") {
fromURL = "/"
}
}
// Validate everything else.
if _, ok := models.CommentableTables[tableName]; !ok {
session.FlashError(w, r, "You can not comment on that.")
templates.Redirect(w, "/")
return
}
// Get the current user.
currentUser, err := session.CurrentUser(r)
if err != nil {
session.FlashError(w, r, "Couldn't get current user: %s", err)
templates.Redirect(w, "/")
return
}
// Who will we notify about this comment? e.g. if commenting on a photo,
// this is the user who owns the photo.
var notifyUser *models.User
switch tableName {
case "photos":
if photo, err := models.GetPhoto(tableID); err == nil {
if user, err := models.GetUser(photo.UserID); err == nil {
notifyUser = user
} else {
log.Error("Comments: couldn't get NotifyUser for photo ID %d (user ID %d): %s",
tableID, photo.UserID, err,
)
}
} else {
log.Error("Comments: couldn't get NotifyUser for photo ID %d: %s", tableID, err)
}
}
// Are we editing or deleting our comment?
if len(editCommentID) > 0 {
if i, err := strconv.Atoi(editCommentID); err == nil {
if found, err := models.GetComment(uint64(i)); err == nil {
comment = found
// Verify that it is indeed OUR comment to manage:
// - If the current user posted it
// - If we are an admin
// - If we are the notifyUser for this comment (they can delete, not edit).
if currentUser.ID != comment.UserID && !currentUser.IsAdmin &&
!(notifyUser != nil && currentUser.ID == notifyUser.ID && isDelete) {
templates.ForbiddenPage(w, r)
return
}
// Initialize the form w/ the content of this message.
if r.Method == http.MethodGet {
message = comment.Message
}
// Are we DELETING this comment?
if isDelete {
if err := comment.Delete(); err != nil {
session.FlashError(w, r, "Error deleting your commenting: %s", err)
} else {
session.Flash(w, r, "Your comment has been deleted.")
}
templates.Redirect(w, fromURL)
return
}
} else {
// Comment not found - show the Forbidden page anyway.
templates.ForbiddenPage(w, r)
return
}
} else {
templates.NotFoundPage(w, r)
return
}
}
// Submitting the form.
if r.Method == http.MethodPost {
// Default intent is preview unless told to submit.
if intent == "submit" {
// A message is required.
if message == "" {
session.FlashError(w, r, "A message is required for your comment.")
templates.Redirect(w, fromURL)
return
}
// Are we modifying an existing comment?
if comment != nil {
comment.Message = message
if err := comment.Save(); err != nil {
session.FlashError(w, r, "Couldn't save comment: %s", err)
} else {
session.Flash(w, r, "Comment updated!")
}
templates.Redirect(w, fromURL)
return
}
// Create the comment.
if comment, err := models.AddComment(
currentUser,
tableName,
tableID,
message,
); err != nil {
session.FlashError(w, r, "Couldn't create comment: %s", err)
} else {
session.Flash(w, r, "Comment added!")
templates.Redirect(w, fromURL)
// Notify the recipient of the comment.
if notifyUser != nil && notifyUser.ID != currentUser.ID {
notif := &models.Notification{
UserID: notifyUser.ID,
AboutUser: *currentUser,
Type: models.NotificationComment,
TableName: comment.TableName,
TableID: comment.TableID,
Message: message,
Link: fromURL,
}
if err := models.CreateNotification(notif); err != nil {
log.Error("Couldn't create Comment notification: %s", err)
}
}
// Notify subscribers to this comment thread.
for _, userID := range models.GetSubscribers(comment.TableName, comment.TableID) {
if notifyUser != nil && userID == notifyUser.ID {
// Don't notify the recipient twice.
continue
} else if userID == currentUser.ID {
// Don't notify the poster of the comment.
continue
}
notif := &models.Notification{
UserID: userID,
AboutUser: *currentUser,
Type: models.NotificationAlsoCommented,
TableName: comment.TableName,
TableID: comment.TableID,
Message: message,
Link: fromURL,
}
if err := models.CreateNotification(notif); err != nil {
log.Error("Couldn't create Comment notification for subscriber %d: %s", userID, err)
}
}
// Subscribe the current user to this comment thread, so they are
// notified if other users add followup comments.
if _, err := models.SubscribeTo(currentUser, comment.TableName, comment.TableID); err != nil {
log.Error("Couldn't subscribe user %d to comment thread %s/%d: %s",
currentUser.ID, comment.TableName, comment.TableID, err,
)
}
return
}
}
}
var vars = map[string]interface{}{
"Intent": intent,
"EditCommentID": editCommentID,
"Message": message,
"TableName": tableName,
"TableID": tableID,
"Next": fromURL,
}
if err := tmpl.Execute(w, r, vars); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
}