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.
174 lines
5.3 KiB
Go
174 lines
5.3 KiB
Go
package models
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// CertificationPhoto table.
|
|
type CertificationPhoto struct {
|
|
ID uint64 `gorm:"primaryKey"`
|
|
UserID uint64 `gorm:"uniqueIndex"`
|
|
Filename string
|
|
Filesize int64
|
|
Status CertificationPhotoStatus
|
|
AdminComment string
|
|
SecondaryNeeded bool // a secondary form of ID has been requested
|
|
SecondaryFilename string // photo ID upload
|
|
SecondaryVerified bool // mark true when ID checked so original can be deleted
|
|
IPAddress string // the IP they uploaded the photo from
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
type CertificationPhotoStatus string
|
|
|
|
const (
|
|
CertificationPhotoNeeded CertificationPhotoStatus = "needed"
|
|
CertificationPhotoPending CertificationPhotoStatus = "pending"
|
|
CertificationPhotoApproved CertificationPhotoStatus = "approved"
|
|
CertificationPhotoRejected CertificationPhotoStatus = "rejected"
|
|
|
|
// If a photo is pending approval but the admin wants to engage the
|
|
// secondary check (prompt user for a photo ID upload)
|
|
CertificationPhotoSecondary CertificationPhotoStatus = "secondary"
|
|
)
|
|
|
|
// GetCertificationPhoto retrieves the user's record from the DB or upserts their initial record.
|
|
func GetCertificationPhoto(userID uint64) (*CertificationPhoto, error) {
|
|
p := &CertificationPhoto{}
|
|
result := DB.Where("user_id = ?", userID).First(&p)
|
|
if result.Error == gorm.ErrRecordNotFound {
|
|
p = &CertificationPhoto{
|
|
UserID: userID,
|
|
Status: CertificationPhotoNeeded,
|
|
}
|
|
result = DB.Create(p)
|
|
return p, result.Error
|
|
}
|
|
return p, result.Error
|
|
}
|
|
|
|
// CertifiedSince retrieve's the last updated date of the user's certification photo, if approved.
|
|
//
|
|
// This incurs a DB query for their cert photo.
|
|
func (u *User) CertifiedSince() (time.Time, error) {
|
|
if !u.Certified {
|
|
return time.Time{}, errors.New("user is not certified")
|
|
}
|
|
|
|
cert, err := GetCertificationPhoto(u.ID)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
|
|
if cert.Status != CertificationPhotoApproved {
|
|
// The edge case can come up if a user was manually certified but didn't have an approved picture.
|
|
// Return their CreatedAt instead.
|
|
return u.CreatedAt, nil
|
|
}
|
|
|
|
return cert.UpdatedAt, nil
|
|
}
|
|
|
|
// CertificationPhotosNeedingApproval returns a pager of the pictures that require admin approval.
|
|
func CertificationPhotosNeedingApproval(status CertificationPhotoStatus, pager *Pagination) ([]*CertificationPhoto, error) {
|
|
var p = []*CertificationPhoto{}
|
|
|
|
query := DB.Where(
|
|
"status = ?",
|
|
status,
|
|
).Order(
|
|
pager.Sort,
|
|
)
|
|
|
|
// Get the total count.
|
|
query.Model(&CertificationPhoto{}).Count(&pager.Total)
|
|
|
|
result := query.Offset(
|
|
pager.GetOffset(),
|
|
).Limit(pager.PerPage).Find(&p)
|
|
|
|
return p, result.Error
|
|
}
|
|
|
|
// CountCertificationPhotosNeedingApproval gets the count of pending photos for admin alert.
|
|
func CountCertificationPhotosNeedingApproval() int64 {
|
|
var count int64
|
|
DB.Where("status = ?", CertificationPhotoPending).Model(&CertificationPhoto{}).Count(&count)
|
|
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)
|
|
return result.Error
|
|
}
|
|
|
|
// Delete the DB entry.
|
|
func (p *CertificationPhoto) Delete() error {
|
|
return DB.Delete(p).Error
|
|
}
|