package photo import ( "fmt" "net/http" "path/filepath" "strconv" "strings" "code.nonshy.com/nonshy/website/pkg/chat" "code.nonshy.com/nonshy/website/pkg/config" "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" ) // Edit controller (/photo/edit?id=N) to change properties about your picture. func Edit() http.HandlerFunc { // Reuse the upload page but with an EditPhoto variable. tmpl := templates.Must("photo/upload.html") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Query params. photoID, err := strconv.Atoi(r.FormValue("id")) if err != nil { session.FlashError(w, r, "Photo 'id' parameter required.") templates.Redirect(w, "/") return } // Find this photo by ID. photo, err := models.GetPhoto(uint64(photoID)) 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 } // In case an admin is editing this photo: remember the HTTP request current user, // before the currentUser may be set to the photo's owner below. var requestUser = currentUser // Do we have permission for this photo? if photo.UserID != currentUser.ID { if !currentUser.HasAdminScope(config.ScopePhotoModerator) { templates.ForbiddenPage(w, r) return } // Find the owner of this photo and assume currentUser is them for the remainder // of this controller. if user, err := models.GetUser(photo.UserID); err != nil { session.FlashError(w, r, "Couldn't get the owner User for this photo!") templates.Redirect(w, "/") return } else { currentUser = user } } // Is the user throttled for Site Gallery photo uploads? var SiteGalleryThrottled = models.IsSiteGalleryThrottled(currentUser, photo) // Are we saving the changes? if r.Method == http.MethodPost { // Record if this change is going to make them a Shy Account. var wasShy = currentUser.IsShy() var ( caption = strings.TrimSpace(r.FormValue("caption")) altText = strings.TrimSpace(r.FormValue("alt_text")) isExplicit = r.FormValue("explicit") == "true" isGallery = r.FormValue("gallery") == "true" isPinned = r.FormValue("pinned") == "true" visibility = models.PhotoVisibility(r.FormValue("visibility")) // Profile pic fields setProfilePic = r.FormValue("intent") == "profile-pic" crop = pphoto.ParseCropCoords(r.FormValue("crop")) // Are we GOING private? goingPrivate = visibility == models.PhotoPrivate && visibility != photo.Visibility ) if len(altText) > config.AltTextMaxLength { altText = altText[:config.AltTextMaxLength] } // Respect the Site Gallery throttle in case the user is messing around. if SiteGalleryThrottled { isGallery = false } // Diff for the ChangeLog. diffs := []models.FieldDiff{ models.NewFieldDiff("Caption", photo.Caption, caption), models.NewFieldDiff("Explicit", photo.Explicit, isExplicit), models.NewFieldDiff("Gallery", photo.Gallery, isGallery), models.NewFieldDiff("Pinned", photo.Pinned, isPinned), models.NewFieldDiff("Visibility", photo.Visibility, visibility), } photo.Caption = caption photo.AltText = altText photo.Explicit = isExplicit photo.Gallery = isGallery photo.Pinned = isPinned photo.Visibility = visibility // Can not use a GIF as profile pic. if setProfilePic && filepath.Ext(photo.Filename) == ".gif" { session.FlashError(w, r, "You can not use a GIF as your profile picture.") templates.Redirect(w, "/") return } // Are we cropping ourselves a new profile pic? log.Error("Profile pic? %+v and crop is: %+v", setProfilePic, crop) if setProfilePic && crop != nil && len(crop) >= 4 { cropFilename, err := pphoto.ReCrop(photo.Filename, crop[0], crop[1], crop[2], crop[3]) log.Error("ReCrop got: %s, %s", cropFilename, err) if err != nil { session.FlashError(w, r, "Couldn't re-crop for profile picture: %s", err) } else { // If there was an old profile pic, remove it from disk. if photo.CroppedFilename != "" { pphoto.Delete(photo.CroppedFilename) } photo.CroppedFilename = cropFilename log.Warn("HERE WE SET (%s) ON PHOTO (%+v)", cropFilename, photo) } } else { setProfilePic = false } if err := photo.Save(); err != nil { session.FlashError(w, r, "Couldn't save photo: %s", err) } // Set their profile pic to this one. if setProfilePic { currentUser.ProfilePhoto = *photo log.Error("Set user ProfilePhotoID=%d", photo.ID) if err := currentUser.Save(); err != nil { session.FlashError(w, r, "Couldn't save user: %s", err) } } // Flash success. session.Flash(w, r, "Photo settings updated!") // Log the change. models.LogUpdated(currentUser, requestUser, "photos", photo.ID, "Updated the photo's settings.", diffs) // Maybe kick them from the chat if this photo save makes them a Shy Account. currentUser.FlushCaches() if !wasShy && currentUser.IsShy() { if _, err := chat.MaybeDisconnectUser(currentUser); err != nil { log.Error("chat.MaybeDisconnectUser(%s#%d): %s", currentUser.Username, currentUser.ID, err) } } // If this picture has moved to Private, revoke any notification we gave about it before. if goingPrivate { log.Info("The picture is GOING PRIVATE (to %s), revoke any notifications about it", photo.Visibility) models.RemoveNotification("photos", photo.ID) } // Return the user to their gallery. templates.Redirect(w, "/u/"+currentUser.Username+"/photos") return } var vars = map[string]interface{}{ "EditPhoto": photo, "SiteGalleryThrottled": SiteGalleryThrottled, "SiteGalleryThrottleLimit": config.SiteGalleryRateLimitMax, } if err := tmpl.Execute(w, r, vars); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }) } // Delete controller (/photo/Delete?id=N) to change properties about your picture. func Delete() http.HandlerFunc { // Reuse the upload page but with an EditPhoto variable. tmpl := templates.Must("photo/delete.html") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Query params. photoID, err := strconv.Atoi(r.FormValue("id")) if err != nil { log.Error("photo.Delete: failed to parse `id` param (%s) as int: %s", r.FormValue("id"), err) session.FlashError(w, r, "Photo 'id' parameter required.") templates.Redirect(w, "/") return } // Page to redirect to in case of errors. redirect := fmt.Sprintf("%s?id=%d", r.URL.Path, photoID) // Find this photo by ID. photo, err := models.GetPhoto(uint64(photoID)) 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 } // In case an admin is editing this photo: remember the HTTP request current user, // before the currentUser may be set to the photo's owner below. var requestUser = currentUser // Do we have permission for this photo? if photo.UserID != currentUser.ID { if !currentUser.HasAdminScope(config.ScopePhotoModerator) { templates.ForbiddenPage(w, r) return } // Find the owner of this photo and assume currentUser is them for the remainder // of this controller. if user, err := models.GetUser(photo.UserID); err != nil { session.FlashError(w, r, "Couldn't get the owner User for this photo!") templates.Redirect(w, "/") return } else { currentUser = user } } // Confirm deletion? if r.Method == http.MethodPost { // Record if this change is going to make them a Shy Account. var wasShy = currentUser.IsShy() confirm := r.PostFormValue("confirm") == "true" if !confirm { session.FlashError(w, r, "Confirm you want to delete this photo.") templates.Redirect(w, redirect) return } // Was this our profile picture? if currentUser.ProfilePhotoID != nil && *currentUser.ProfilePhotoID == photo.ID { log.Debug("Delete Photo: was the user's profile photo, unset ProfilePhotoID") if err := currentUser.RemoveProfilePhoto(); err != nil { session.FlashError(w, r, "Error unsetting your current profile photo: %s", err) templates.Redirect(w, redirect) 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, redirect) return } // Log the change. models.LogDeleted(currentUser, requestUser, "photos", photo.ID, "Deleted the photo.", photo) session.Flash(w, r, "Photo deleted!") // Maybe kick them from chat if this deletion makes them into a Shy Account. currentUser.FlushCaches() if !wasShy && currentUser.IsShy() { if _, err := chat.MaybeDisconnectUser(currentUser); err != nil { log.Error("chat.MaybeDisconnectUser(%s#%d): %s", currentUser.Username, currentUser.ID, err) } } // Return the user to their gallery. templates.Redirect(w, "/u/"+currentUser.Username+"/photos") return } var vars = map[string]interface{}{ "Photo": photo, } if err := tmpl.Execute(w, r, vars); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } }) }