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