website/pkg/models/friend.go
Noah Petherbridge 9788ea6a33 The inner circle
2023-05-23 20:04:17 -07:00

253 lines
6.2 KiB
Go

package models
import (
"errors"
"time"
"code.nonshy.com/nonshy/website/pkg/log"
"gorm.io/gorm"
)
// Friend table.
type Friend struct {
ID uint64 `gorm:"primaryKey"`
SourceUserID uint64 `gorm:"index"`
TargetUserID uint64 `gorm:"index"`
Approved bool `gorm:"index"`
CreatedAt time.Time
UpdatedAt time.Time
}
// AddFriend sends a friend request or accepts one if there was already a pending one.
func AddFriend(sourceUserID, targetUserID uint64) error {
// Did we already send a friend request?
f := &Friend{}
forward := DB.Where(
"source_user_id = ? AND target_user_id = ?",
sourceUserID, targetUserID,
).First(&f).Error
// Is there a reverse friend request pending?
rev := &Friend{}
reverse := DB.Where(
"source_user_id = ? AND target_user_id = ?",
targetUserID, sourceUserID,
).First(&rev).Error
// If the reverse exists (requested us) but not the forward, this completes the friendship.
if reverse == nil && forward != nil {
// Approve the reverse.
rev.Approved = true
rev.Save()
// Add the matching forward.
f = &Friend{
SourceUserID: sourceUserID,
TargetUserID: targetUserID,
Approved: true,
}
return DB.Create(f).Error
}
// If the forward already existed, error.
if forward == nil {
if f.Approved {
return errors.New("you are already friends")
}
return errors.New("a friend request had already been sent")
}
// Create the pending forward request.
f = &Friend{
SourceUserID: sourceUserID,
TargetUserID: targetUserID,
Approved: false,
}
return DB.Create(f).Error
}
// AreFriends quickly checks if two user IDs are friends.
func AreFriends(sourceUserID, targetUserID uint64) bool {
f := &Friend{}
DB.Where(
"source_user_id = ? AND target_user_id = ?",
sourceUserID, targetUserID,
).First(&f)
return f.Approved
}
// FriendStatus returns an indicator of friendship status: "none", "pending", "approved"
func FriendStatus(sourceUserID, targetUserID uint64) string {
f := &Friend{}
result := DB.Where(
"source_user_id = ? AND target_user_id = ?",
sourceUserID, targetUserID,
).First(&f)
if result.Error == nil {
if f.Approved {
return "approved"
}
return "pending"
}
return "none"
}
// FriendIDs returns all user IDs with approved friendship to the user.
func FriendIDs(userId uint64) []uint64 {
var (
fs = []*Friend{}
userIDs = []uint64{}
)
DB.Where("source_user_id = ? AND approved = ?", userId, true).Find(&fs)
for _, row := range fs {
userIDs = append(userIDs, row.TargetUserID)
}
return userIDs
}
// FriendIDsAreExplicit returns friend IDs who have opted-in for Explicit content,
// e.g. to notify only them when you uploaded a new Explicit photo so that non-explicit
// users don't need to see that notification.
func FriendIDsAreExplicit(userId uint64) []uint64 {
var (
userIDs = []uint64{}
)
err := DB.Table(
"friends",
).Joins(
"JOIN users ON (users.id = friends.target_user_id)",
).Select(
"friends.target_user_id AS friend_id",
).Where(
"friends.source_user_id = ? AND friends.approved = ? AND users.explicit = ?",
userId, true, true,
).Scan(&userIDs)
if err.Error != nil {
log.Error("SQL error collecting explicit FriendIDs for %d: %s", userId, err)
}
return userIDs
}
// FriendIDsInCircle returns friend IDs who are part of the inner circle.
func FriendIDsInCircle(userId uint64) []uint64 {
var (
userIDs = []uint64{}
)
err := DB.Table(
"friends",
).Joins(
"JOIN users ON (users.id = friends.target_user_id)",
).Select(
"friends.target_user_id AS friend_id",
).Where(
"friends.source_user_id = ? AND friends.approved = ? AND (users.inner_circle = ? OR users.is_admin = ?)",
userId, true, true, true,
).Scan(&userIDs)
if err.Error != nil {
log.Error("SQL error collecting circle FriendIDs for %d: %s", userId, err)
}
return userIDs
}
// CountFriendRequests gets a count of pending requests for the user.
func CountFriendRequests(userID uint64) (int64, error) {
var count int64
result := DB.Where(
"target_user_id = ? AND approved = ?",
userID,
false,
).Model(&Friend{}).Count(&count)
return count, result.Error
}
/*
PaginateFriends gets a page of friends (or pending friend requests) as User objects ordered
by friendship date.
The `requests` and `sent` bools are mutually exclusive (use only one, or neither). `requests`
asks for unanswered friend requests to you, and `sent` returns the friend requests that you
have sent and have not been answered.
*/
func PaginateFriends(user *User, requests bool, sent bool, pager *Pagination) ([]*User, error) {
// We paginate over the Friend table.
var (
fs = []*Friend{}
userIDs = []uint64{}
query *gorm.DB
)
if requests && sent {
return nil, errors.New("requests and sent are mutually exclusive options, use one or neither")
}
if requests {
query = DB.Where(
"target_user_id = ? AND approved = ?",
user.ID, false,
)
} else if sent {
query = DB.Where(
"source_user_id = ? AND approved = ?",
user.ID, false,
)
} else {
query = DB.Where(
"source_user_id = ? AND approved = ?",
user.ID, true,
)
}
query = query.Order(pager.Sort)
query.Model(&Friend{}).Count(&pager.Total)
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&fs)
if result.Error != nil {
return nil, result.Error
}
// Now of these friends get their User objects.
for _, friend := range fs {
if requests {
userIDs = append(userIDs, friend.SourceUserID)
} else {
userIDs = append(userIDs, friend.TargetUserID)
}
}
return GetUsers(user, userIDs)
}
// GetFriendRequests returns all pending friend requests for a user.
func GetFriendRequests(userID uint64) ([]*Friend, error) {
var fs = []*Friend{}
result := DB.Where(
"target_user_id = ? AND approved = ?",
userID,
false,
).Find(&fs)
return fs, result.Error
}
// RemoveFriend severs a friend connection both directions, used when
// rejecting a request or removing a friend.
func RemoveFriend(sourceUserID, targetUserID uint64) error {
result := DB.Where(
"(source_user_id = ? AND target_user_id = ?) OR "+
"(target_user_id = ? AND source_user_id = ?)",
sourceUserID, targetUserID,
sourceUserID, targetUserID,
).Delete(&Friend{})
return result.Error
}
// Save photo.
func (f *Friend) Save() error {
result := DB.Save(f)
return result.Error
}