Noah
de9ba94dd9
* Add ability to unlock your private photos for others. * Link to unlock is on another user's Photos page. * You can also access your "Manage Private Photos" page in the User Menu or Dashboard and add a user by username. * See who you have granted access, and see users who have granted you access to their private photos. * Private photos of users who unlocked them for you may appear on the Site Gallery if the photo allows. * Add new filters to the Site Gallery: you can choose to filter by Explicit, limit to a specific Visibility, and order by Newest or Oldest. Non-explicit users do not get a filter to see explicit content - they must opt-in in their settings. * Bugfix: Site Gallery was not correctly filtering Explicit photos away from users who did not opt-in for explicit!
248 lines
6.7 KiB
Go
248 lines
6.7 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Notification table.
|
|
type Notification struct {
|
|
ID uint64 `gorm:"primaryKey"`
|
|
UserID uint64 `gorm:"index"` // who it belongs to
|
|
AboutUserID *uint64 `form:"index"` // the other party of this notification
|
|
AboutUser User `gorm:"foreignKey:about_user_id"`
|
|
Type NotificationType // like, comment, ...
|
|
Read bool `gorm:"index"`
|
|
TableName string // on which of your tables (photos, comments, ...)
|
|
TableID uint64
|
|
Message string // text associated, e.g. copy of comment added
|
|
Link string // associated URL, e.g. for comments
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// Preload related tables for the forum (classmethod).
|
|
func (n *Notification) Preload() *gorm.DB {
|
|
return DB.Preload("AboutUser.ProfilePhoto")
|
|
}
|
|
|
|
type NotificationType string
|
|
|
|
const (
|
|
NotificationLike NotificationType = "like"
|
|
NotificationFriendApproved = "friendship_approved"
|
|
NotificationComment = "comment"
|
|
NotificationAlsoCommented = "also_comment"
|
|
NotificationAlsoPosted = "also_posted" // forum replies
|
|
NotificationCertRejected = "cert_rejected"
|
|
NotificationCertApproved = "cert_approved"
|
|
NotificationPrivatePhoto = "private_photo"
|
|
NotificationCustom = "custom" // custom message pushed
|
|
)
|
|
|
|
// CreateNotification
|
|
func CreateNotification(n *Notification) error {
|
|
return DB.Create(n).Error
|
|
}
|
|
|
|
// GetNotification by ID.
|
|
func GetNotification(id uint64) (*Notification, error) {
|
|
var n *Notification
|
|
result := DB.Model(n).First(&n, id)
|
|
return n, result.Error
|
|
}
|
|
|
|
// RemoveNotification about a table ID, e.g. when removing a like.
|
|
func RemoveNotification(tableName string, tableID uint64) error {
|
|
result := DB.Where(
|
|
"table_name = ? AND table_id = ?",
|
|
tableName, tableID,
|
|
).Delete(&Notification{})
|
|
return result.Error
|
|
}
|
|
|
|
// RemoveSpecificNotification to remove more specialized notifications where just removing by
|
|
// table name+ID is not adequate, e.g. for Private Photo Unlocks.
|
|
func RemoveSpecificNotification(userID uint64, t NotificationType, tableName string, tableID uint64) error {
|
|
result := DB.Where(
|
|
"user_id = ? AND type = ? AND table_name = ? AND table_id = ?",
|
|
userID, t, tableName, tableID,
|
|
).Delete(&Notification{})
|
|
return result.Error
|
|
}
|
|
|
|
// MarkNotificationsRead sets all a user's notifications to read.
|
|
func MarkNotificationsRead(user *User) error {
|
|
return DB.Model(&Notification{}).Where(
|
|
"user_id = ? AND read IS NOT TRUE",
|
|
user.ID,
|
|
).Update("read", true).Error
|
|
}
|
|
|
|
// CountUnreadNotifications gets the count of unread Notifications for a user.
|
|
func CountUnreadNotifications(userID uint64) (int64, error) {
|
|
query := DB.Where(
|
|
"user_id = ? AND read = ?",
|
|
userID,
|
|
false,
|
|
)
|
|
|
|
var count int64
|
|
result := query.Model(&Notification{}).Count(&count)
|
|
return count, result.Error
|
|
}
|
|
|
|
// PaginateNotifications returns the user's notifications.
|
|
func PaginateNotifications(user *User, pager *Pagination) ([]*Notification, error) {
|
|
var ns = []*Notification{}
|
|
|
|
query := (&Notification{}).Preload().Where(
|
|
"user_id = ?",
|
|
user.ID,
|
|
).Order(
|
|
pager.Sort,
|
|
)
|
|
|
|
query.Model(&Notification{}).Count(&pager.Total)
|
|
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&ns)
|
|
return ns, result.Error
|
|
}
|
|
|
|
// Save a notification.
|
|
func (n *Notification) Save() error {
|
|
return DB.Save(n).Error
|
|
}
|
|
|
|
// NotificationBody can store remote tables mapped.
|
|
type NotificationBody struct {
|
|
PhotoID uint64
|
|
ThreadID uint64
|
|
Photo *Photo
|
|
Thread *Thread
|
|
}
|
|
|
|
type NotificationMap map[uint64]*NotificationBody
|
|
|
|
// Get a notification's body from the map.
|
|
func (m NotificationMap) Get(id uint64) *NotificationBody {
|
|
if body, ok := m[id]; ok {
|
|
return body
|
|
}
|
|
return &NotificationBody{}
|
|
}
|
|
|
|
// MapNotifications loads associated assets, like Photos, mapped to their notification ID.
|
|
func MapNotifications(ns []*Notification) NotificationMap {
|
|
var (
|
|
IDs = []uint64{}
|
|
result = NotificationMap{}
|
|
)
|
|
|
|
// Collect notification IDs.
|
|
for _, row := range ns {
|
|
IDs = append(IDs, row.ID)
|
|
result[row.ID] = &NotificationBody{}
|
|
}
|
|
|
|
result.mapNotificationPhotos(IDs)
|
|
result.mapNotificationThreads(IDs)
|
|
|
|
return result
|
|
}
|
|
|
|
// Helper function of MapNotifications to eager load Photo attachments.
|
|
func (nm NotificationMap) mapNotificationPhotos(IDs []uint64) {
|
|
type scanner struct {
|
|
PhotoID uint64
|
|
NotificationID uint64
|
|
}
|
|
var scan []scanner
|
|
|
|
// Load all of these that have photos.
|
|
err := DB.Table(
|
|
"notifications",
|
|
).Joins(
|
|
"JOIN photos ON (notifications.table_name='photos' AND notifications.table_id=photos.id)",
|
|
).Select(
|
|
"photos.id AS photo_id",
|
|
"notifications.id AS notification_id",
|
|
).Where(
|
|
"notifications.id IN ?",
|
|
IDs,
|
|
).Scan(&scan)
|
|
if err.Error != nil {
|
|
log.Error("Couldn't select photo IDs for notifications: %s", err.Error)
|
|
}
|
|
|
|
// Collect and load all the photos by ID.
|
|
var photoIDs = []uint64{}
|
|
for _, row := range scan {
|
|
// Store the photo ID in the result now.
|
|
nm[row.NotificationID].PhotoID = row.PhotoID
|
|
photoIDs = append(photoIDs, row.PhotoID)
|
|
}
|
|
|
|
// Load the photos.
|
|
if len(photoIDs) > 0 {
|
|
if photos, err := GetPhotos(photoIDs); err != nil {
|
|
log.Error("Couldn't load photo IDs for notifications: %s", err)
|
|
} else {
|
|
// Marry them to their notification IDs.
|
|
for _, body := range nm {
|
|
if photo, ok := photos[body.PhotoID]; ok {
|
|
body.Photo = photo
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper function of MapNotifications to eager load Thread attachments.
|
|
func (nm NotificationMap) mapNotificationThreads(IDs []uint64) {
|
|
type scanner struct {
|
|
ThreadID uint64
|
|
NotificationID uint64
|
|
}
|
|
var scan []scanner
|
|
|
|
// Load all of these that have threads.
|
|
err := DB.Table(
|
|
"notifications",
|
|
).Joins(
|
|
"JOIN threads ON (notifications.table_name='threads' AND notifications.table_id=threads.id)",
|
|
).Select(
|
|
"threads.id AS thread_id",
|
|
"notifications.id AS notification_id",
|
|
).Where(
|
|
"notifications.id IN ?",
|
|
IDs,
|
|
).Scan(&scan)
|
|
if err.Error != nil {
|
|
log.Error("Couldn't select thread IDs for notifications: %s", err.Error)
|
|
}
|
|
|
|
// Collect and load all the threads by ID.
|
|
var threadIDs = []uint64{}
|
|
for _, row := range scan {
|
|
// Store the thread ID in the result now.
|
|
nm[row.NotificationID].ThreadID = row.ThreadID
|
|
threadIDs = append(threadIDs, row.ThreadID)
|
|
}
|
|
|
|
// Load the threads.
|
|
if len(threadIDs) > 0 {
|
|
if threads, err := GetThreads(threadIDs); err != nil {
|
|
log.Error("Couldn't load thread IDs for notifications: %s", err)
|
|
} else {
|
|
// Marry them to their notification IDs.
|
|
for _, body := range nm {
|
|
if thread, ok := threads[body.ThreadID]; ok {
|
|
body.Thread = thread
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|