2022-08-12 06:03:06 +00:00
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2022-08-15 00:45:55 +00:00
|
|
|
"strings"
|
2022-08-12 06:03:06 +00:00
|
|
|
"time"
|
2022-08-13 22:39:31 +00:00
|
|
|
|
2022-08-26 04:21:46 +00:00
|
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
2022-08-13 22:39:31 +00:00
|
|
|
"gorm.io/gorm"
|
2022-08-12 06:03:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Photo table.
|
|
|
|
type Photo struct {
|
|
|
|
ID uint64 `gorm:"primaryKey"`
|
|
|
|
UserID uint64 `gorm:"index"`
|
|
|
|
Filename string
|
|
|
|
CroppedFilename string // if cropped, e.g. for profile photo
|
|
|
|
Filesize int64
|
|
|
|
Caption string
|
|
|
|
Flagged bool // photo has been reported by the community
|
|
|
|
Visibility PhotoVisibility
|
|
|
|
Gallery bool // photo appears in the public gallery (if public)
|
|
|
|
Explicit bool // is an explicit photo
|
|
|
|
CreatedAt time.Time
|
|
|
|
UpdatedAt time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
// PhotoVisibility settings.
|
|
|
|
type PhotoVisibility string
|
|
|
|
|
|
|
|
const (
|
2023-05-24 03:04:17 +00:00
|
|
|
PhotoPublic PhotoVisibility = "public" // on profile page and/or public gallery
|
2023-05-25 01:40:27 +00:00
|
|
|
PhotoFriends PhotoVisibility = "friends" // only friends can see it
|
|
|
|
PhotoPrivate PhotoVisibility = "private" // private
|
|
|
|
PhotoInnerCircle PhotoVisibility = "circle" // inner circle
|
2022-08-12 06:03:06 +00:00
|
|
|
)
|
|
|
|
|
2022-08-23 03:58:35 +00:00
|
|
|
// PhotoVisibility preset settings.
|
|
|
|
var (
|
|
|
|
PhotoVisibilityAll = []PhotoVisibility{
|
|
|
|
PhotoPublic,
|
|
|
|
PhotoFriends,
|
|
|
|
PhotoPrivate,
|
|
|
|
}
|
|
|
|
|
2023-05-24 03:04:17 +00:00
|
|
|
// "All" but also for Inner Circle members.
|
|
|
|
PhotoVisibilityCircle = []PhotoVisibility{
|
|
|
|
PhotoPublic,
|
|
|
|
PhotoFriends,
|
|
|
|
PhotoPrivate,
|
|
|
|
PhotoInnerCircle,
|
|
|
|
}
|
|
|
|
|
2022-08-23 03:58:35 +00:00
|
|
|
// Site Gallery visibility for when your friends show up in the gallery.
|
|
|
|
// Or: "Friends + Gallery" photos can appear to your friends in the Site Gallery.
|
|
|
|
PhotoVisibilityFriends = []string{
|
|
|
|
string(PhotoPublic),
|
|
|
|
string(PhotoFriends),
|
|
|
|
}
|
|
|
|
)
|
2022-08-14 21:40:57 +00:00
|
|
|
|
2022-08-12 06:03:06 +00:00
|
|
|
// CreatePhoto with most of the settings you want (not ID or timestamps) in the database.
|
|
|
|
func CreatePhoto(tmpl Photo) (*Photo, error) {
|
|
|
|
if tmpl.UserID == 0 {
|
|
|
|
return nil, errors.New("UserID required")
|
|
|
|
}
|
|
|
|
|
|
|
|
p := &Photo{
|
|
|
|
UserID: tmpl.UserID,
|
|
|
|
Filename: tmpl.Filename,
|
|
|
|
CroppedFilename: tmpl.CroppedFilename,
|
2022-12-07 05:50:42 +00:00
|
|
|
Filesize: tmpl.Filesize,
|
2022-08-12 06:03:06 +00:00
|
|
|
Caption: tmpl.Caption,
|
|
|
|
Visibility: tmpl.Visibility,
|
|
|
|
Gallery: tmpl.Gallery,
|
|
|
|
Explicit: tmpl.Explicit,
|
|
|
|
}
|
|
|
|
|
|
|
|
result := DB.Create(p)
|
|
|
|
return p, result.Error
|
|
|
|
}
|
2022-08-13 06:11:36 +00:00
|
|
|
|
|
|
|
// GetPhoto by ID.
|
|
|
|
func GetPhoto(id uint64) (*Photo, error) {
|
|
|
|
p := &Photo{}
|
|
|
|
result := DB.First(&p, id)
|
|
|
|
return p, result.Error
|
|
|
|
}
|
|
|
|
|
2022-08-25 04:17:34 +00:00
|
|
|
// GetPhotos by an array of IDs, mapped to their IDs.
|
|
|
|
func GetPhotos(IDs []uint64) (map[uint64]*Photo, error) {
|
|
|
|
var (
|
|
|
|
mp = map[uint64]*Photo{}
|
|
|
|
ps = []*Photo{}
|
|
|
|
)
|
|
|
|
|
|
|
|
result := DB.Model(&Photo{}).Where("id IN ?", IDs).Find(&ps)
|
|
|
|
for _, row := range ps {
|
|
|
|
mp[row.ID] = row
|
|
|
|
}
|
|
|
|
|
|
|
|
return mp, result.Error
|
|
|
|
}
|
|
|
|
|
2022-08-13 06:11:36 +00:00
|
|
|
/*
|
|
|
|
PaginateUserPhotos gets a page of photos belonging to a user ID.
|
|
|
|
*/
|
2022-08-13 22:39:31 +00:00
|
|
|
func PaginateUserPhotos(userID uint64, visibility []PhotoVisibility, explicitOK bool, pager *Pagination) ([]*Photo, error) {
|
2022-08-13 06:11:36 +00:00
|
|
|
var p = []*Photo{}
|
|
|
|
|
2022-08-13 22:39:31 +00:00
|
|
|
var explicit = []bool{false}
|
|
|
|
if explicitOK {
|
|
|
|
explicit = []bool{true, false}
|
|
|
|
}
|
|
|
|
|
2022-08-13 06:11:36 +00:00
|
|
|
query := DB.Where(
|
2022-08-13 22:39:31 +00:00
|
|
|
"user_id = ? AND visibility IN ? AND explicit IN ?",
|
2022-08-13 06:11:36 +00:00
|
|
|
userID,
|
|
|
|
visibility,
|
2022-08-13 22:39:31 +00:00
|
|
|
explicit,
|
2022-08-13 06:11:36 +00:00
|
|
|
).Order(
|
|
|
|
pager.Sort,
|
|
|
|
)
|
|
|
|
|
|
|
|
// Get the total count.
|
|
|
|
query.Model(&Photo{}).Count(&pager.Total)
|
|
|
|
|
|
|
|
result := query.Offset(
|
|
|
|
pager.GetOffset(),
|
|
|
|
).Limit(pager.PerPage).Find(&p)
|
|
|
|
|
|
|
|
return p, result.Error
|
|
|
|
}
|
|
|
|
|
2022-08-21 22:40:24 +00:00
|
|
|
// CountPhotos returns the total number of photos on a user's account.
|
2022-08-26 02:58:43 +00:00
|
|
|
func CountPhotos(userID uint64) int64 {
|
2022-08-21 22:40:24 +00:00
|
|
|
var count int64
|
|
|
|
result := DB.Where(
|
|
|
|
"user_id = ?",
|
|
|
|
userID,
|
|
|
|
).Model(&Photo{}).Count(&count)
|
2022-08-26 02:58:43 +00:00
|
|
|
if result.Error != nil {
|
|
|
|
log.Error("CountPhotos(%d): %s", userID, result.Error)
|
|
|
|
}
|
|
|
|
return count
|
2022-08-21 22:40:24 +00:00
|
|
|
}
|
|
|
|
|
2023-07-23 22:02:41 +00:00
|
|
|
// MapPhotoCounts returns a mapping of user ID to the CountPhotos()-equivalent result for each.
|
|
|
|
// It's used on the member directory to show photo counts on each user card.
|
|
|
|
func MapPhotoCounts(users []*User) PhotoCountMap {
|
|
|
|
var (
|
|
|
|
userIDs = []uint64{}
|
|
|
|
result = PhotoCountMap{}
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, user := range users {
|
|
|
|
userIDs = append(userIDs, user.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
type group struct {
|
|
|
|
UserID uint64
|
|
|
|
PhotoCount int64
|
|
|
|
}
|
|
|
|
var groups = []group{}
|
|
|
|
|
|
|
|
if res := DB.Table(
|
|
|
|
"photos",
|
|
|
|
).Select(
|
|
|
|
"user_id, count(id) AS photo_count",
|
|
|
|
).Where(
|
|
|
|
"user_id IN ?", userIDs,
|
|
|
|
).Group("user_id").Scan(&groups); res.Error != nil {
|
|
|
|
log.Error("CountPhotosForUsers: %s", res.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map the results in.
|
|
|
|
for _, row := range groups {
|
|
|
|
result[row.UserID] = row.PhotoCount
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
type PhotoCountMap map[uint64]int64
|
|
|
|
|
|
|
|
// Get a photo count for the given user ID from the map.
|
|
|
|
func (pc PhotoCountMap) Get(id uint64) int64 {
|
|
|
|
if value, ok := pc[id]; ok {
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2022-08-13 22:39:31 +00:00
|
|
|
// CountExplicitPhotos returns the number of explicit photos a user has (so non-explicit viewers can see some do exist)
|
|
|
|
func CountExplicitPhotos(userID uint64, visibility []PhotoVisibility) (int64, error) {
|
|
|
|
query := DB.Where(
|
|
|
|
"user_id = ? AND visibility IN ? AND explicit = ?",
|
|
|
|
userID,
|
|
|
|
visibility,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
|
|
|
|
var count int64
|
|
|
|
result := query.Model(&Photo{}).Count(&count)
|
|
|
|
return count, result.Error
|
|
|
|
}
|
|
|
|
|
2023-05-24 18:27:42 +00:00
|
|
|
// CountPublicPhotos returns the number of public photos on a user's page (to control whether the "invite to circle" prompt can appear).
|
|
|
|
func CountPublicPhotos(userID uint64) int64 {
|
|
|
|
query := DB.Where(
|
|
|
|
"user_id = ? AND visibility = ?",
|
|
|
|
userID,
|
|
|
|
PhotoPublic,
|
|
|
|
)
|
|
|
|
|
|
|
|
var count int64
|
|
|
|
result := query.Model(&Photo{}).Count(&count)
|
|
|
|
if result.Error != nil {
|
|
|
|
log.Error("CountPublicPhotos(%d): %s", userID, result.Error)
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
|
2023-02-14 06:19:18 +00:00
|
|
|
// DistinctPhotoTypes returns types of photos the user has: a set of public, friends, or private.
|
|
|
|
//
|
|
|
|
// The result is cached on the User the first time it's queried.
|
|
|
|
func (u *User) DistinctPhotoTypes() (result map[PhotoVisibility]struct{}) {
|
|
|
|
if u.cachePhotoTypes != nil {
|
|
|
|
return u.cachePhotoTypes
|
|
|
|
}
|
|
|
|
|
|
|
|
result = map[PhotoVisibility]struct{}{}
|
|
|
|
|
|
|
|
var results = []*Photo{}
|
|
|
|
query := DB.Model(&Photo{}).
|
|
|
|
Select("DISTINCT photos.visibility").
|
|
|
|
Where("user_id = ?", u.ID).
|
|
|
|
Group("photos.visibility").
|
|
|
|
Find(&results)
|
|
|
|
if query.Error != nil {
|
|
|
|
log.Error("User.DistinctPhotoTypes(%s): %s", u.Username, query.Error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, row := range results {
|
|
|
|
log.Warn("DistinctPhotoTypes(%s): got %+v", u.Username, row)
|
|
|
|
result[row.Visibility] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
u.cachePhotoTypes = result
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gallery config for the main Gallery paginator.
|
|
|
|
type Gallery struct {
|
|
|
|
Explicit string // Explicit filter
|
|
|
|
Visibility string // Visibility filter
|
|
|
|
AdminView bool // Show all images
|
|
|
|
ShyView bool // Current user is shy (self/friends only)
|
|
|
|
}
|
|
|
|
|
2022-09-08 04:18:54 +00:00
|
|
|
/*
|
|
|
|
PaginateGalleryPhotos gets a page of all public user photos for the site gallery.
|
|
|
|
|
|
|
|
Admin view returns ALL photos regardless of Gallery status.
|
|
|
|
*/
|
2023-02-14 06:19:18 +00:00
|
|
|
func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Photo, error) {
|
2022-08-13 22:39:31 +00:00
|
|
|
var (
|
2023-02-14 06:19:18 +00:00
|
|
|
filterExplicit = conf.Explicit
|
|
|
|
filterVisibility = conf.Visibility
|
|
|
|
adminView = conf.AdminView
|
|
|
|
isShy = conf.ShyView
|
|
|
|
p = []*Photo{}
|
|
|
|
query *gorm.DB
|
2022-09-08 04:18:54 +00:00
|
|
|
|
|
|
|
// Get the user ID and their preferences.
|
|
|
|
userID = user.ID
|
|
|
|
explicitOK = user.Explicit // User opted-in for explicit content
|
|
|
|
|
|
|
|
blocklist = BlockedUserIDs(userID)
|
|
|
|
friendIDs = FriendIDs(userID)
|
|
|
|
privateUserIDs = PrivateGrantedUserIDs(userID)
|
|
|
|
wheres = []string{}
|
|
|
|
placeholders = []interface{}{}
|
2022-08-13 22:39:31 +00:00
|
|
|
)
|
|
|
|
|
2023-05-24 03:04:17 +00:00
|
|
|
// Define "all photos visibilities"
|
2023-05-24 20:38:37 +00:00
|
|
|
var (
|
|
|
|
photosAll = PhotoVisibilityAll
|
|
|
|
photosPublic = []PhotoVisibility{
|
|
|
|
PhotoPublic,
|
|
|
|
}
|
|
|
|
photosFriends = []PhotoVisibility{
|
|
|
|
PhotoPublic,
|
|
|
|
PhotoFriends,
|
|
|
|
}
|
|
|
|
)
|
2023-05-24 03:04:17 +00:00
|
|
|
if user.IsInnerCircle() {
|
|
|
|
photosAll = PhotoVisibilityCircle
|
2023-05-24 20:38:37 +00:00
|
|
|
photosPublic = append(photosPublic, PhotoInnerCircle)
|
|
|
|
photosFriends = append(photosFriends, PhotoInnerCircle)
|
2023-05-24 03:04:17 +00:00
|
|
|
}
|
|
|
|
|
2022-09-10 03:11:50 +00:00
|
|
|
// Admins see everything on the site (only an admin user can get an admin view).
|
|
|
|
adminView = user.IsAdmin && adminView
|
|
|
|
|
2022-08-23 03:58:35 +00:00
|
|
|
// Include ourself in our friend IDs.
|
|
|
|
friendIDs = append(friendIDs, userID)
|
|
|
|
|
2023-02-14 06:19:18 +00:00
|
|
|
// Whose photos can you see on the Site Gallery?
|
|
|
|
if isShy {
|
|
|
|
// Shy users can only see their Friends photos (public or friends visibility)
|
|
|
|
// and any Private photos to whom they were granted access.
|
|
|
|
wheres = append(wheres,
|
|
|
|
"((user_id IN ? AND visibility IN ?) OR "+
|
|
|
|
"(user_id IN ? AND visibility IN ?))",
|
|
|
|
)
|
|
|
|
placeholders = append(placeholders,
|
|
|
|
friendIDs, PhotoVisibilityFriends,
|
2023-05-24 03:04:17 +00:00
|
|
|
privateUserIDs, photosAll,
|
2023-02-14 06:19:18 +00:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// You can see friends' Friend photos but only public for non-friends.
|
|
|
|
wheres = append(wheres,
|
|
|
|
"((user_id IN ? AND visibility IN ?) OR "+
|
|
|
|
"(user_id IN ? AND visibility IN ?) OR "+
|
2023-05-24 20:38:37 +00:00
|
|
|
"(user_id NOT IN ? AND visibility IN ?))",
|
2023-02-14 06:19:18 +00:00
|
|
|
)
|
|
|
|
placeholders = append(placeholders,
|
2023-05-24 20:38:37 +00:00
|
|
|
friendIDs, photosFriends,
|
2023-05-24 03:04:17 +00:00
|
|
|
privateUserIDs, photosAll,
|
2023-05-24 20:38:37 +00:00
|
|
|
friendIDs, photosPublic,
|
2023-02-14 06:19:18 +00:00
|
|
|
)
|
|
|
|
}
|
2022-08-23 03:58:35 +00:00
|
|
|
|
|
|
|
// Gallery photos only.
|
|
|
|
wheres = append(wheres, "gallery = ?")
|
|
|
|
placeholders = append(placeholders, true)
|
2022-08-15 00:45:55 +00:00
|
|
|
|
|
|
|
// Filter blocked users.
|
|
|
|
if len(blocklist) > 0 {
|
|
|
|
wheres = append(wheres, "user_id NOT IN ?")
|
|
|
|
placeholders = append(placeholders, blocklist)
|
2022-08-13 22:39:31 +00:00
|
|
|
}
|
|
|
|
|
2022-09-08 04:18:54 +00:00
|
|
|
// Non-explicit pics unless the user opted in. Allow explicit filter setting to override.
|
|
|
|
if filterExplicit != "" {
|
|
|
|
wheres = append(wheres, "explicit = ?")
|
|
|
|
placeholders = append(placeholders, filterExplicit == "true")
|
|
|
|
} else if !explicitOK {
|
2022-08-15 00:45:55 +00:00
|
|
|
wheres = append(wheres, "explicit = ?")
|
|
|
|
placeholders = append(placeholders, false)
|
|
|
|
}
|
|
|
|
|
2022-09-08 04:18:54 +00:00
|
|
|
// Is the user furthermore clamping the visibility filter?
|
|
|
|
if filterVisibility != "" {
|
|
|
|
wheres = append(wheres, "visibility = ?")
|
|
|
|
placeholders = append(placeholders, filterVisibility)
|
|
|
|
}
|
|
|
|
|
2023-03-10 01:14:20 +00:00
|
|
|
// Only certified (and not banned) user photos.
|
2022-08-15 00:45:55 +00:00
|
|
|
wheres = append(wheres,
|
2023-03-10 01:14:20 +00:00
|
|
|
"EXISTS (SELECT 1 FROM users WHERE id = photos.user_id AND certified = true AND status='active')",
|
2022-08-15 00:45:55 +00:00
|
|
|
)
|
|
|
|
|
2022-08-22 00:29:39 +00:00
|
|
|
// Exclude private users' photos.
|
|
|
|
wheres = append(wheres,
|
|
|
|
"NOT EXISTS (SELECT 1 FROM users WHERE id = photos.user_id AND visibility = 'private')",
|
|
|
|
)
|
|
|
|
|
2022-08-13 22:39:31 +00:00
|
|
|
// Admin view: get ALL PHOTOS on the site, period.
|
|
|
|
if adminView {
|
|
|
|
query = DB
|
2022-09-08 04:18:54 +00:00
|
|
|
|
|
|
|
// Admin may filter too.
|
|
|
|
if filterVisibility != "" {
|
|
|
|
query = query.Where("visibility = ?", filterVisibility)
|
|
|
|
}
|
|
|
|
if filterExplicit != "" {
|
|
|
|
query = query.Where("explicit = ?", filterExplicit == "true")
|
|
|
|
}
|
2022-08-13 22:39:31 +00:00
|
|
|
} else {
|
|
|
|
query = DB.Where(
|
2022-08-15 00:45:55 +00:00
|
|
|
strings.Join(wheres, " AND "),
|
|
|
|
placeholders...,
|
2022-08-13 22:39:31 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
query = query.Order(pager.Sort)
|
|
|
|
|
|
|
|
query.Model(&Photo{}).Count(&pager.Total)
|
|
|
|
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&p)
|
|
|
|
return p, result.Error
|
|
|
|
}
|
|
|
|
|
2022-08-13 06:11:36 +00:00
|
|
|
// Save photo.
|
|
|
|
func (p *Photo) Save() error {
|
|
|
|
result := DB.Save(p)
|
|
|
|
return result.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete photo.
|
|
|
|
func (p *Photo) Delete() error {
|
|
|
|
result := DB.Delete(p)
|
|
|
|
return result.Error
|
|
|
|
}
|