Noah Petherbridge
dbca37977e
* Add impersonate feature * Add ban/unban user feature * Add promote/demote admin status feature * Add admin user deletion feature * Admin ability to see other status certification pics * Nav bar indicator of pending admin actions such as cert pics needing approval * Admin ability to search cert pics for specific user
339 lines
9.9 KiB
Go
339 lines
9.9 KiB
Go
package photo
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
"git.kirsle.net/apps/gosocial/pkg/config"
|
|
"git.kirsle.net/apps/gosocial/pkg/log"
|
|
"git.kirsle.net/apps/gosocial/pkg/mail"
|
|
"git.kirsle.net/apps/gosocial/pkg/models"
|
|
"git.kirsle.net/apps/gosocial/pkg/photo"
|
|
"git.kirsle.net/apps/gosocial/pkg/session"
|
|
"git.kirsle.net/apps/gosocial/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.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"
|
|
}
|
|
|
|
// 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 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 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(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
|
|
}
|
|
})
|
|
}
|