Noah
6c91c67c97
* Users who set their Profile Picture to "friends only" or "private" can have their avatar be private all over the website to users who are not their friends or not granted access. * Users who are not your friends see a yellow placeholder avatar, and users not granted access to a private Profile Pic sees a purple avatar. * Admin users see these same placeholder avatars most places too (on search, forums, comments, etc.) if the user did not friend or grant the admin. But admins ALWAYS see it on their Profile Page directly, for ability to moderate. * Fix marking Notifications as read: clicking the link in an unread notification now will wait on the ajax request to finish before allowing the redirect. * Update the FAQ
367 lines
11 KiB
Go
367 lines
11 KiB
Go
package photo
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/config"
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
"code.nonshy.com/nonshy/website/pkg/mail"
|
|
"code.nonshy.com/nonshy/website/pkg/models"
|
|
"code.nonshy.com/nonshy/website/pkg/photo"
|
|
"code.nonshy.com/nonshy/website/pkg/session"
|
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
|
)
|
|
|
|
// CertificationRequiredError handles the error page when a user is denied due to lack of certification.
|
|
func CertificationRequiredError() http.HandlerFunc {
|
|
tmpl := templates.Must("errors/certification_required.html")
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
currentUser, err := session.CurrentUser(r)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Unexpected error: could not get currentUser.")
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
// Get the current user's cert photo (or create the DB record).
|
|
cert, err := models.GetCertificationPhoto(currentUser.ID)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Unexpected error: could not get or create CertificationPhoto record.")
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
var vars = map[string]interface{}{
|
|
"CertificationPhoto": cert,
|
|
}
|
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
// Certification photo controller.
|
|
func Certification() http.HandlerFunc {
|
|
tmpl := templates.Must("photo/certification.html")
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
currentUser, err := session.CurrentUser(r)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Unexpected error: could not get currentUser.")
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
// Get the current user's cert photo (or create the DB record).
|
|
cert, err := models.GetCertificationPhoto(currentUser.ID)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Unexpected error: could not get or create CertificationPhoto record.")
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
// Uploading?
|
|
if r.Method == http.MethodPost {
|
|
// Are they deleting their photo?
|
|
if r.PostFormValue("delete") == "true" {
|
|
if cert.Filename != "" {
|
|
if err := photo.Delete(cert.Filename); err != nil {
|
|
log.Error("Failed to delete old cert photo for %s (%s): %s", currentUser.Username, cert.Filename, err)
|
|
}
|
|
cert.Filename = ""
|
|
}
|
|
|
|
cert.Status = models.CertificationPhotoNeeded
|
|
cert.AdminComment = ""
|
|
cert.Save()
|
|
|
|
// Removing your photo = not certified again.
|
|
currentUser.Certified = false
|
|
if err := currentUser.Save(); err != nil {
|
|
session.FlashError(w, r, "Error saving your User data: %s", err)
|
|
}
|
|
|
|
session.Flash(w, r, "Your certification photo has been deleted.")
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
// Get the uploaded file.
|
|
file, header, err := r.FormFile("file")
|
|
if err != nil {
|
|
session.FlashError(w, r, "Error receiving your file: %s", err)
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
io.Copy(&buf, file)
|
|
|
|
filename, _, err := photo.UploadPhoto(photo.UploadConfig{
|
|
User: currentUser,
|
|
Extension: filepath.Ext(header.Filename),
|
|
Data: buf.Bytes(),
|
|
})
|
|
if err != nil {
|
|
session.FlashError(w, r, "Error processing your upload: %s", err)
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
// Are they replacing their old photo?
|
|
if cert.Filename != "" {
|
|
if err := photo.Delete(cert.Filename); err != nil {
|
|
log.Error("Failed to delete old cert photo for %s (%s): %s", currentUser.Username, cert.Filename, err)
|
|
}
|
|
}
|
|
|
|
// Update their certification photo.
|
|
cert.Status = models.CertificationPhotoPending
|
|
cert.Filename = filename
|
|
cert.AdminComment = ""
|
|
if err := cert.Save(); err != nil {
|
|
session.FlashError(w, r, "Error saving your CertificationPhoto: %s", err)
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
// Set their approval status back to false.
|
|
currentUser.Certified = false
|
|
if err := currentUser.Save(); err != nil {
|
|
session.FlashError(w, r, "Error saving your User data: %s", err)
|
|
}
|
|
|
|
// Notify the admin email to check out this photo.
|
|
if err := mail.Send(mail.Message{
|
|
To: config.Current.AdminEmail,
|
|
Subject: "New Certification Photo Needs Approval",
|
|
Template: "email/certification_admin.html",
|
|
Data: map[string]interface{}{
|
|
"User": currentUser,
|
|
"URL": config.Current.BaseURL + "/admin/photo/certification",
|
|
},
|
|
}); err != nil {
|
|
log.Error("Certification: failed to notify admins of pending photo: %s", err)
|
|
}
|
|
|
|
session.Flash(w, r, "Your certification photo has been uploaded and is now awaiting approval.")
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
var vars = map[string]interface{}{
|
|
"CertificationPhoto": cert,
|
|
}
|
|
|
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
// AdminCertification controller (/admin/photo/certification)
|
|
func AdminCertification() http.HandlerFunc {
|
|
tmpl := templates.Must("admin/certification.html")
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// View status
|
|
var view = r.FormValue("view")
|
|
if view == "" {
|
|
view = "pending"
|
|
}
|
|
|
|
// Get the current user.
|
|
currentUser, err := session.CurrentUser(r)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Couldn't get CurrentUser: %s", err)
|
|
}
|
|
|
|
// Short circuit the GET view for username/email search (exact match)
|
|
if username := r.FormValue("username"); username != "" {
|
|
user, err := models.FindUser(username)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Username or email '%s' not found.", username)
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
cert, err := models.GetCertificationPhoto(user.ID)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Couldn't get their certification photo: %s", err)
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
var vars = map[string]interface{}{
|
|
"View": view,
|
|
"Photos": []*models.CertificationPhoto{cert},
|
|
"UserMap": &models.UserMap{user.ID: user},
|
|
"FoundUser": user,
|
|
}
|
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Making a verdict?
|
|
if r.Method == http.MethodPost {
|
|
var (
|
|
comment = r.PostFormValue("comment")
|
|
verdict = r.PostFormValue("verdict")
|
|
)
|
|
|
|
userID, err := strconv.Atoi(r.PostFormValue("user_id"))
|
|
if err != nil {
|
|
session.FlashError(w, r, "Invalid user_id data type.")
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
// Look up the user in case we'll toggle their Certified state.
|
|
user, err := models.GetUser(uint64(userID))
|
|
if err != nil {
|
|
session.FlashError(w, r, "Couldn't get user ID %d: %s", userID, err)
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
// Look up this photo.
|
|
cert, err := models.GetCertificationPhoto(uint64(userID))
|
|
if err != nil {
|
|
session.FlashError(w, r, "Couldn't get certification photo.")
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
} else if cert.Filename == "" {
|
|
session.FlashError(w, r, "That photo has no filename anymore??")
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
switch verdict {
|
|
case "reject":
|
|
if comment == "" {
|
|
session.FlashError(w, r, "An admin comment is required when rejecting a photo.")
|
|
} else {
|
|
cert.Status = models.CertificationPhotoRejected
|
|
cert.AdminComment = comment
|
|
if err := cert.Save(); err != nil {
|
|
session.FlashError(w, r, "Failed to save CertificationPhoto: %s", err)
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
// Uncertify the user just in case.
|
|
user.Certified = false
|
|
user.Save()
|
|
|
|
// Notify the user about this rejection.
|
|
notif := &models.Notification{
|
|
UserID: user.ID,
|
|
AboutUser: *user,
|
|
Type: models.NotificationCertRejected,
|
|
Message: comment,
|
|
}
|
|
if err := models.CreateNotification(notif); err != nil {
|
|
log.Error("Couldn't create rejection notification: %s", err)
|
|
}
|
|
|
|
// Notify the user via email.
|
|
if err := mail.Send(mail.Message{
|
|
To: user.Email,
|
|
Subject: "Your certification photo has been rejected",
|
|
Template: "email/certification_rejected.html",
|
|
Data: map[string]interface{}{
|
|
"Username": user.Username,
|
|
"AdminComment": comment,
|
|
"URL": config.Current.BaseURL + "/photo/certification",
|
|
},
|
|
}); err != nil {
|
|
session.FlashError(w, r, "Note: failed to email user about the rejection: %s", err)
|
|
}
|
|
}
|
|
|
|
session.Flash(w, r, "Certification photo rejected!")
|
|
case "approve":
|
|
cert.Status = models.CertificationPhotoApproved
|
|
cert.AdminComment = ""
|
|
if err := cert.Save(); err != nil {
|
|
session.FlashError(w, r, "Failed to save CertificationPhoto: %s", err)
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
// Certify the user!
|
|
user.Certified = true
|
|
user.Save()
|
|
|
|
// Notify the user about this approval.
|
|
notif := &models.Notification{
|
|
UserID: user.ID,
|
|
AboutUser: *user,
|
|
Type: models.NotificationCertApproved,
|
|
}
|
|
if err := models.CreateNotification(notif); err != nil {
|
|
log.Error("Couldn't create approval notification: %s", err)
|
|
}
|
|
|
|
// Notify the user via email.
|
|
if err := mail.Send(mail.Message{
|
|
To: user.Email,
|
|
Subject: "Your certification photo has been approved!",
|
|
Template: "email/certification_approved.html",
|
|
Data: map[string]interface{}{
|
|
"Username": user.Username,
|
|
"URL": config.Current.BaseURL,
|
|
},
|
|
}); err != nil {
|
|
session.FlashError(w, r, "Note: failed to email user about the approval: %s", err)
|
|
}
|
|
|
|
session.Flash(w, r, "Certification photo approved!")
|
|
default:
|
|
session.FlashError(w, r, "Unsupported verdict option: %s", verdict)
|
|
}
|
|
templates.Redirect(w, r.URL.Path)
|
|
return
|
|
}
|
|
|
|
// Get the pending photos.
|
|
pager := &models.Pagination{
|
|
Page: 1,
|
|
PerPage: config.PageSizeAdminCertification,
|
|
Sort: "updated_at desc",
|
|
}
|
|
pager.ParsePage(r)
|
|
photos, err := models.CertificationPhotosNeedingApproval(models.CertificationPhotoStatus(view), pager)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Couldn't load certification photos from DB: %s", err)
|
|
}
|
|
|
|
// Map user IDs.
|
|
var userIDs = []uint64{}
|
|
for _, p := range photos {
|
|
userIDs = append(userIDs, p.UserID)
|
|
}
|
|
userMap, err := models.MapUsers(currentUser, userIDs)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Couldn't map user IDs: %s", err)
|
|
}
|
|
|
|
var vars = map[string]interface{}{
|
|
"View": view,
|
|
"Photos": photos,
|
|
"UserMap": userMap,
|
|
"Pager": pager,
|
|
}
|
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
})
|
|
}
|