1c013aa8d8
* If a Certified member deletes the final picture from their gallery page, their Certification Photo will be automatically rejected and they are instructed to begin the process again from the beginning. * Add nice Alert and Confirm modals around the website in place of the standard browser feature. Note: the inline confirm on submit buttons are still using the standard feature for now, as intercepting submit buttons named "intent" causes problems in getting the final form to submit.
245 lines
6.6 KiB
Go
245 lines
6.6 KiB
Go
package photo
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/chat"
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
"code.nonshy.com/nonshy/website/pkg/models"
|
|
pphoto "code.nonshy.com/nonshy/website/pkg/photo"
|
|
"code.nonshy.com/nonshy/website/pkg/session"
|
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
|
)
|
|
|
|
// BatchEdit controller (/photo/batch-edit?id=N) to change properties about your picture.
|
|
func BatchEdit() http.HandlerFunc {
|
|
tmpl := templates.Must("photo/batch_edit.html")
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
// Form params
|
|
intent = r.FormValue("intent")
|
|
photoIDs []uint64
|
|
)
|
|
|
|
// Collect the photo ID params.
|
|
if value, ok := r.Form["id"]; ok {
|
|
for _, idStr := range value {
|
|
if photoID, err := strconv.Atoi(idStr); err == nil {
|
|
photoIDs = append(photoIDs, uint64(photoID))
|
|
} else {
|
|
log.Error("parsing photo ID %s: %s", idStr, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validation.
|
|
if len(photoIDs) == 0 || len(photoIDs) > 100 {
|
|
session.FlashError(w, r, "Invalid number of photo IDs.")
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
// Find these photos by ID.
|
|
photos, err := models.GetPhotos(photoIDs)
|
|
if err != nil {
|
|
templates.NotFoundPage(w, r)
|
|
return
|
|
}
|
|
|
|
// Load the current user.
|
|
currentUser, err := session.CurrentUser(r)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Unexpected error: couldn't get CurrentUser")
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
// Validate permission to edit all of these photos.
|
|
var (
|
|
ownerIDs []uint64
|
|
)
|
|
for _, photo := range photos {
|
|
|
|
if !photo.CanBeEditedBy(currentUser) {
|
|
templates.ForbiddenPage(w, r)
|
|
return
|
|
}
|
|
|
|
ownerIDs = append(ownerIDs, photo.UserID)
|
|
}
|
|
|
|
// Load the photo owners.
|
|
var (
|
|
owners, _ = models.MapUsers(currentUser, ownerIDs)
|
|
wasShy = map[uint64]bool{} // record if this change may make them shy
|
|
redirectURI = "/" // go first owner's gallery
|
|
|
|
// Are any of them a user's profile photo? (map userID->true) so we know
|
|
// who to unlink the picture from first and avoid a postgres error.
|
|
wasUserProfilePicture = map[uint64]bool{}
|
|
)
|
|
for _, user := range owners {
|
|
redirectURI = fmt.Sprintf("/u/%s/photos", user.Username)
|
|
wasShy[user.ID] = user.IsShy()
|
|
|
|
// Check if this user's profile ID is being deleted.
|
|
if user.ProfilePhotoID != nil {
|
|
if _, ok := photos[*user.ProfilePhotoID]; ok {
|
|
wasUserProfilePicture[user.ID] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Confirm batch deletion or edit.
|
|
if r.Method == http.MethodPost {
|
|
|
|
confirm := r.PostFormValue("confirm") == "true"
|
|
if !confirm {
|
|
session.FlashError(w, r, "Confirm you want to modify this photo.")
|
|
templates.Redirect(w, redirectURI)
|
|
return
|
|
}
|
|
|
|
// Which intent are they executing on?
|
|
switch intent {
|
|
case "delete":
|
|
batchDeletePhotos(w, r, currentUser, photos, wasUserProfilePicture, owners, redirectURI)
|
|
case "visibility":
|
|
batchUpdateVisibility(w, r, currentUser, photos, owners)
|
|
default:
|
|
session.FlashError(w, r, "Unknown intent")
|
|
}
|
|
|
|
// Maybe kick them from chat if this deletion makes them into a Shy Account.
|
|
for _, user := range owners {
|
|
user.FlushCaches()
|
|
if !wasShy[user.ID] && user.IsShy() {
|
|
if _, err := chat.MaybeDisconnectUser(user); err != nil {
|
|
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", user.Username, user.ID, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the user to their gallery.
|
|
templates.Redirect(w, redirectURI)
|
|
return
|
|
}
|
|
|
|
var vars = map[string]interface{}{
|
|
"Intent": intent,
|
|
"Photos": photos,
|
|
}
|
|
|
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
// Batch DELETE executive handler.
|
|
func batchDeletePhotos(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
currentUser *models.User,
|
|
photos map[uint64]*models.Photo,
|
|
wasUserProfilePicture map[uint64]bool,
|
|
owners map[uint64]*models.User,
|
|
redirectURI string,
|
|
) {
|
|
// Delete all the photos.
|
|
for _, photo := range photos {
|
|
|
|
// Was this someone's profile picture ID?
|
|
if wasUserProfilePicture[photo.UserID] {
|
|
log.Debug("Delete Photo: was the user's profile photo, unset ProfilePhotoID")
|
|
if owner, ok := owners[photo.UserID]; ok {
|
|
if err := owner.RemoveProfilePhoto(); err != nil {
|
|
session.FlashError(w, r, "Error unsetting your current profile photo: %s", err)
|
|
templates.Redirect(w, redirectURI)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the images from disk.
|
|
for _, filename := range []string{
|
|
photo.Filename,
|
|
photo.CroppedFilename,
|
|
} {
|
|
if len(filename) > 0 {
|
|
if err := pphoto.Delete(filename); err != nil {
|
|
session.FlashError(w, r, "Delete Photo: couldn't remove file from disk: %s: %s", filename, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Take back notifications on it.
|
|
models.RemoveNotification("photos", photo.ID)
|
|
|
|
if err := photo.Delete(); err != nil {
|
|
session.FlashError(w, r, "Couldn't delete photo: %s", err)
|
|
templates.Redirect(w, redirectURI)
|
|
return
|
|
}
|
|
|
|
// Log the change.
|
|
if owner, ok := owners[photo.UserID]; ok {
|
|
models.LogDeleted(owner, currentUser, "photos", photo.ID, "Deleted the photo.", photo)
|
|
}
|
|
}
|
|
|
|
// Maybe revoke their Certified status if they have cleared out their gallery.
|
|
for _, owner := range owners {
|
|
if revoked := models.MaybeRevokeCertificationForEmptyGallery(owner); revoked {
|
|
if owner.ID == currentUser.ID {
|
|
session.FlashError(w, r, "Notice: because you have deleted your entire photo gallery, your Certification status has been automatically revoked.")
|
|
}
|
|
}
|
|
}
|
|
|
|
session.Flash(w, r, "%d photo(s) deleted!", len(photos))
|
|
}
|
|
|
|
// Batch DELETE executive handler.
|
|
func batchUpdateVisibility(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
currentUser *models.User,
|
|
photos map[uint64]*models.Photo,
|
|
owners map[uint64]*models.User,
|
|
) {
|
|
// Visibility setting.
|
|
visibility := r.PostFormValue("visibility")
|
|
|
|
// Delete all the photos.
|
|
for _, photo := range photos {
|
|
|
|
// Diff for the ChangeLog.
|
|
diffs := []models.FieldDiff{
|
|
models.NewFieldDiff("Visibility", photo.Visibility, visibility),
|
|
}
|
|
|
|
photo.Visibility = models.PhotoVisibility(visibility)
|
|
|
|
// If going private, take back notifications on it.
|
|
if photo.Visibility == models.PhotoPrivate {
|
|
models.RemoveNotification("photos", photo.ID)
|
|
}
|
|
|
|
if err := photo.Save(); err != nil {
|
|
session.FlashError(w, r, "Error saving photo #%d: %s", photo.ID, err)
|
|
}
|
|
|
|
// Log the change.
|
|
if owner, ok := owners[photo.UserID]; ok {
|
|
// Log the change.
|
|
models.LogUpdated(owner, currentUser, "photos", photo.ID, "Updated the photo's settings.", diffs)
|
|
}
|
|
}
|
|
|
|
session.Flash(w, r, "%d photo(s) updated!", len(photos))
|
|
}
|