2022-08-13 22:39:31 +00:00
package models
import (
2024-08-23 04:57:14 +00:00
"errors"
2024-12-23 22:58:39 +00:00
"fmt"
2022-08-13 22:39:31 +00:00
"time"
2024-12-23 22:58:39 +00:00
"code.nonshy.com/nonshy/website/pkg/log"
2022-08-13 22:39:31 +00:00
"gorm.io/gorm"
)
// CertificationPhoto table.
type CertificationPhoto struct {
2024-05-26 19:34:00 +00:00
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
2022-08-13 22:39:31 +00:00
}
type CertificationPhotoStatus string
const (
CertificationPhotoNeeded CertificationPhotoStatus = "needed"
2023-05-25 01:40:27 +00:00
CertificationPhotoPending CertificationPhotoStatus = "pending"
CertificationPhotoApproved CertificationPhotoStatus = "approved"
CertificationPhotoRejected CertificationPhotoStatus = "rejected"
2024-05-26 19:34:00 +00:00
// 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"
2022-08-13 22:39:31 +00:00
)
// 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
}
2024-08-23 04:57:14 +00:00
// 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 {
2024-08-29 01:42:49 +00:00
// 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
2024-08-23 04:57:14 +00:00
}
return cert . UpdatedAt , nil
}
2022-08-13 22:39:31 +00:00
// 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
}
2022-08-14 23:27:57 +00:00
// 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
}
2024-12-23 22:58:39 +00:00
// 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 )
}
2024-12-31 23:56:08 +00:00
// Update the cert photo's change log.
LogEvent ( user , nil , ChangeLogRejected , "certification_photos" , user . ID , "Their cert photo was automatically rejected because they deleted their entire photo gallery." )
2024-12-23 22:58:39 +00:00
return true
}
return false
}
2022-08-13 22:39:31 +00:00
// Save photo.
func ( p * CertificationPhoto ) Save ( ) error {
result := DB . Save ( p )
return result . Error
}
2022-08-14 21:40:57 +00:00
// Delete the DB entry.
func ( p * CertificationPhoto ) Delete ( ) error {
return DB . Delete ( p ) . Error
}