website/pkg/controller/photo/batch_edit.go

236 lines
6.2 KiB
Go
Raw Permalink Normal View History

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 (
2024-10-05 04:22:52 +00:00
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 {
2024-10-05 04:22:52 +00:00
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 {
2024-10-05 04:22:52 +00:00
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 {
log.Error("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)
}
}
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))
}