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 { 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)) }