website/pkg/models/blocklist.go
2023-12-04 19:57:14 -08:00

288 lines
6.8 KiB
Go

package models
import (
"fmt"
"time"
"code.nonshy.com/nonshy/website/pkg/log"
"gorm.io/gorm"
)
// Block table.
type Block struct {
ID uint64 `gorm:"primaryKey"`
SourceUserID uint64 `gorm:"index"`
TargetUserID uint64 `gorm:"index"`
CreatedAt time.Time
UpdatedAt time.Time
}
// AddBlock is sourceUserId adding targetUserId to their block list.
func AddBlock(sourceUserID, targetUserID uint64) error {
// Unfriend in the process.
RemoveFriend(sourceUserID, targetUserID)
// Did we already block this user?
var b *Block
forward := DB.Where(
"source_user_id = ? AND target_user_id = ?",
sourceUserID, targetUserID,
).First(&b).Error
// Update existing.
if forward == nil {
return nil
}
// Create the block.
b = &Block{
SourceUserID: sourceUserID,
TargetUserID: targetUserID,
}
return DB.Create(b).Error
}
// IsBlocking quickly sees if either user blocks the other.
func IsBlocking(sourceUserID, targetUserID uint64) bool {
b := &Block{}
result := DB.Where(
"(source_user_id = ? AND target_user_id = ?) OR "+
"(target_user_id = ? AND source_user_id = ?)",
sourceUserID, targetUserID,
sourceUserID, targetUserID,
).First(&b)
return result.Error == nil
}
// IsBlocked quickly checks if sourceUserID currently blocks targetUserID.
func IsBlocked(sourceUserID, targetUserID uint64) bool {
b := &Block{}
result := DB.Where(
"source_user_id = ? AND target_user_id = ?",
sourceUserID, targetUserID,
).First(&b)
return result.Error == nil
}
// PaginateBlockList views a user's blocklist.
func PaginateBlockList(user *User, pager *Pagination) ([]*User, error) {
// We paginate over the Block table.
var (
bs = []*Block{}
userIDs = []uint64{}
query *gorm.DB
)
query = DB.Where(
"source_user_id = ?",
user.ID,
)
query = query.Order(pager.Sort)
query.Model(&Block{}).Count(&pager.Total)
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&bs)
if result.Error != nil {
return nil, result.Error
}
// Now of these friends get their User objects.
for _, b := range bs {
userIDs = append(userIDs, b.TargetUserID)
}
return GetUsers(user, userIDs)
}
// BlockedUserIDs returns all user IDs blocked by the user (bidirectional, source or target user).
func BlockedUserIDs(user *User) []uint64 {
// Have we looked this up already on this request?
if user.cacheBlockedUserIDs != nil {
return user.cacheBlockedUserIDs
}
var (
bs = []*Block{}
userIDs = []uint64{}
)
DB.Where("source_user_id = ? OR target_user_id = ?", user.ID, user.ID).Find(&bs)
for _, row := range bs {
for _, uid := range []uint64{row.TargetUserID, row.SourceUserID} {
if uid != user.ID {
userIDs = append(userIDs, uid)
}
}
}
// Cache the result in the User so we don't query it again.
user.cacheBlockedUserIDs = userIDs
return userIDs
}
// MapBlockedUserIDs returns BlockedUserIDs as a lookup hashmap (not for front-end templates currently).
func MapBlockedUserIDs(user *User) map[uint64]interface{} {
var (
result = map[uint64]interface{}{}
blockedIDs = BlockedUserIDs(user)
)
for _, uid := range blockedIDs {
result[uid] = nil
}
return result
}
// FilterBlockingUserIDs narrows down a set of User IDs to remove ones that block (or are blocked by) the current user.
func FilterBlockingUserIDs(currentUser *User, userIDs []uint64) []uint64 {
var (
// Get the IDs to exclude.
blockedIDs = MapBlockedUserIDs(currentUser)
// Filter the result.
result = []uint64{}
)
for _, uid := range userIDs {
if _, ok := blockedIDs[uid]; ok {
continue
}
result = append(result, uid)
}
return result
}
// BlockedUserIDsByUser returns all user IDs blocked by the user (one directional only)
func BlockedUserIDsByUser(userId uint64) []uint64 {
var (
bs = []*Block{}
userIDs = []uint64{}
)
DB.Where("source_user_id = ?", userId).Find(&bs)
for _, row := range bs {
for _, uid := range []uint64{row.TargetUserID, row.SourceUserID} {
if uid != userId {
userIDs = append(userIDs, uid)
}
}
}
return userIDs
}
// BlockedUsernames returns all usernames blocked by (or blocking) the user.
func BlockedUsernames(user *User) []string {
var (
userIDs = BlockedUserIDs(user)
usernames = []string{}
)
if len(userIDs) == 0 {
return usernames
}
if res := DB.Table(
"users",
).Select(
"username",
).Where(
"id IN ?", userIDs,
).Scan(&usernames); res.Error != nil {
log.Error("BlockedUsernames(%s): %s", user.Username, res.Error)
}
return usernames
}
// GetBlocklistInsights returns detailed block lists (both directions) about a user, for admin insight.
func GetBlocklistInsights(user *User) (*BlocklistInsight, error) {
// Collect ALL user IDs (both directions) of this user's blocklist.
var (
bs = []*Block{}
forward = []*Block{} // Users they block
reverse = []*Block{} // Users who block the target
userIDs = []uint64{user.ID}
usernames = map[uint64]string{}
)
// Get the complete blocklist and bucket them into forward and reverse.
DB.Where("source_user_id = ? OR target_user_id = ?", user.ID, user.ID).Order("created_at desc").Find(&bs)
for _, row := range bs {
if row.SourceUserID == user.ID {
forward = append(forward, row)
userIDs = append(userIDs, row.TargetUserID)
} else {
reverse = append(reverse, row)
userIDs = append(userIDs, row.SourceUserID)
}
}
// Map all the user IDs to user names.
if len(userIDs) > 0 {
type scanItem struct {
ID uint64
Username string
}
var scan = []scanItem{}
if res := DB.Table(
"users",
).Select(
"id",
"username",
).Where(
"id IN ?", userIDs,
).Scan(&scan); res.Error != nil {
return nil, fmt.Errorf("GetBlocklistInsights(%s): mapping user IDs to names: %s", user.Username, res.Error)
}
for _, row := range scan {
usernames[row.ID] = row.Username
}
}
// Assemble the final result.
var result = &BlocklistInsight{
Blocks: []BlocklistInsightUser{},
BlockedBy: []BlocklistInsightUser{},
}
for _, row := range forward {
if username, ok := usernames[row.TargetUserID]; ok {
result.Blocks = append(result.Blocks, BlocklistInsightUser{
Username: username,
Date: row.CreatedAt,
})
}
}
for _, row := range reverse {
if username, ok := usernames[row.SourceUserID]; ok {
result.BlockedBy = append(result.BlockedBy, BlocklistInsightUser{
Username: username,
Date: row.CreatedAt,
})
}
}
return result, nil
}
type BlocklistInsight struct {
Blocks []BlocklistInsightUser
BlockedBy []BlocklistInsightUser
}
type BlocklistInsightUser struct {
Username string
Date time.Time
}
// UnblockUser removes targetUserID from your blocklist.
func UnblockUser(sourceUserID, targetUserID uint64) error {
result := DB.Where(
"source_user_id = ? AND target_user_id = ?",
sourceUserID, targetUserID,
).Delete(&Block{})
return result.Error
}
// Save photo.
func (b *Block) Save() error {
result := DB.Save(b)
return result.Error
}