website/pkg/models/blocklist.go
Noah Petherbridge 481bd0ae61 Deactivate Account; Friends Lists on Profiles
* Add a way for users to temporarily deactivate their accounts, in a
  recoverable way should they decide to return later.
* A deactivated account may log in but have limited options: to
  reactivate their account, permanently delete it, or log out.
* Fix several bugs around the display of comments, messages and
  forum threads for disabled, banned, or blocked users:
  * Messages (inbox and sentbox) will be hidden and the unread indicator
    will not count unread messages the user can't access.
  * Comments on photos and forum posts are hidden, and top-level threads
    on the "Newest" tab will show "[unavailable]" for their text and
    username.
  * Your historical notifications will hide users who are blocked, banned
    or disabled.
* Add a "Friends" tab to user profile pages, to see other users' friends.
  * The page is Certification Required so non-cert users can't easily
    discover any members on the site.
2023-10-22 15:02:24 -07:00

206 lines
4.8 KiB
Go

package models
import (
"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(%d): %s", user.Username, res.Error)
}
return usernames
}
// 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
}