diff --git a/pkg/controller/photo/batch_edit.go b/pkg/controller/photo/batch_edit.go index 6c79967..061b215 100644 --- a/pkg/controller/photo/batch_edit.go +++ b/pkg/controller/photo/batch_edit.go @@ -171,7 +171,7 @@ func batchDeletePhotos( } { 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) + session.FlashError(w, r, "Delete Photo: couldn't remove file from disk: %s: %s", filename, err) } } } @@ -191,6 +191,15 @@ func batchDeletePhotos( } } + // 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)) } diff --git a/pkg/models/certification.go b/pkg/models/certification.go index e4865ab..278ebd3 100644 --- a/pkg/models/certification.go +++ b/pkg/models/certification.go @@ -2,8 +2,10 @@ package models import ( "errors" + "fmt" "time" + "code.nonshy.com/nonshy/website/pkg/log" "gorm.io/gorm" ) @@ -101,6 +103,64 @@ func CountCertificationPhotosNeedingApproval() int64 { return count } +// MaybeRevokeCertificationForEmptyGallery will delete a user's certification photo if they delete every picture from their gallery. +// +// Returns true if their certification was revoked. +func MaybeRevokeCertificationForEmptyGallery(user *User) bool { + cert, err := GetCertificationPhoto(user.ID) + if err != nil { + return false + } + + // Ignore if their cert photo status is not applicable to be revoked. + if cert.Status == CertificationPhotoNeeded || cert.Status == CertificationPhotoRejected { + return false + } + + if count := CountPhotos(user.ID); count == 0 { + // Revoke their cert status. + cert.Status = CertificationPhotoRejected + cert.SecondaryVerified = false + cert.AdminComment = "Your certification photo has been automatically rejected because you have deleted every photo on your gallery. " + + "To restore your certified status, please upload photos to your gallery and submit a new Certification Photo for approval." + + if err := cert.Save(); err != nil { + log.Error("MaybeRevokeCertificationForEmptyGallery(%s): %s", user.Username, err) + } + + // Update the user's Certified flag. Note: we freshly query the user here in case they had JUST deleted + // their default profile picture - so that we don't (re)set their old ProfilePhotoID by accident! + if user, err := GetUser(user.ID); err == nil { + user.Certified = false + if err := user.Save(); err != nil { + log.Error("MaybeRevokeCertificationForEmptyGallery(%s): saving user certified flag: %s", user.Username, err) + } + } + + // Notify the site admin for visibility. + fb := &Feedback{ + Intent: "report", + Subject: "A certified user has deleted all their pictures", + UserID: user.ID, + TableName: "users", + TableID: user.ID, + Message: fmt.Sprintf( + "The username **@%s** has deleted every picture in their gallery, and so their Certification Photo status has been revoked.", + user.Username, + ), + } + + // Save the feedback. + if err := CreateFeedback(fb); err != nil { + log.Error("Couldn't save feedback from user auto-revoking their cert photo: %s", err) + } + + return true + } + + return false +} + // Save photo. func (p *CertificationPhoto) Save() error { result := DB.Save(p) diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go index 259ae7b..1056bae 100644 --- a/pkg/templates/templates.go +++ b/pkg/templates/templates.go @@ -162,6 +162,7 @@ func (t *Template) Reload() error { // Base template layout. var baseTemplates = []string{ config.TemplatePath + "/base.html", + config.TemplatePath + "/partials/alert_modal.html", config.TemplatePath + "/partials/user_avatar.html", config.TemplatePath + "/partials/like_modal.html", config.TemplatePath + "/partials/right_click.html", diff --git a/web/static/js/alert-modal.js b/web/static/js/alert-modal.js new file mode 100644 index 0000000..e729665 --- /dev/null +++ b/web/static/js/alert-modal.js @@ -0,0 +1,108 @@ +/** + * Alert and Confirm modals. + * + * Usage: + * + * modalAlert({message: "Hello world!"}).then(callback); + * modalConfirm({message: "Are you sure?"}).then(callback); + * + * Available options for modalAlert: + * - message + * - title: Alert + * + * Available options for modalConfirm: + * - message + * - title: Confirm + * - buttons: ["Ok", "Cancel"] + * - event (pass `event` for easy inline onclick handlers) + * - element (pass `this` for easy inline onclick handlers) + * + * Example onclick for modalConfirm: + * + * + * + * The `element` is used to find the nearest