From c37d0298b05e2a9402ad21315793fbbf6110958b Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 28 Aug 2024 18:42:49 -0700 Subject: [PATCH] User Forums Blocking Behavior + Misc Fixes Make some adjustments to blocking behavior regarding the forums: * Pre-existing bug: on a forum's home page (threads list), if a thread was created by a blocked user, the thread still appeared with the user's name and picture visible. Now: their picture and name will be "[unavailable]" but the thread title/message and link to the thread will remain. Note: in the thread view itself, posts by the blocked user will be missing as normal. * Make some tweaks to allow forum moderators (and owners of user-owned forums) able to see messages from blocked users on their forum: * In threads: a blocked user's picture and name are "[unavailable]" but the content of their message is still shown, and can be deleted by moderators. Misc fixes: * Private photos: when viewing your granted/grantee lists, hide users whose accounts are inactive or who are blocked. * CertifiedSince: in case a user was manually certified but their cert photo status is not correct, return their user CreatedAt time instead. --- pkg/controller/forum/thread.go | 2 +- pkg/models/certification.go | 5 +++-- pkg/models/comment.go | 24 +++++++++++++-------- pkg/models/forum.go | 3 ++- pkg/models/private_photo.go | 31 +++++++++++++++++++++------- pkg/models/user_relationship.go | 11 ++++++++++ web/templates/forum/board_index.html | 20 ++++++++++++------ web/templates/forum/thread.html | 21 +++++++++++++------ 8 files changed, 84 insertions(+), 33 deletions(-) diff --git a/pkg/controller/forum/thread.go b/pkg/controller/forum/thread.go index 3872c50..bb39df7 100644 --- a/pkg/controller/forum/thread.go +++ b/pkg/controller/forum/thread.go @@ -74,7 +74,7 @@ func Thread() http.HandlerFunc { } pager.ParsePage(r) - comments, err := models.PaginateComments(currentUser, "threads", thread.ID, pager) + comments, err := models.PaginateComments(currentUser, "threads", thread.ID, canModerate, pager) if err != nil { session.FlashError(w, r, "Couldn't paginate comments: %s", err) templates.Redirect(w, "/") diff --git a/pkg/models/certification.go b/pkg/models/certification.go index 5377c5d..e4865ab 100644 --- a/pkg/models/certification.go +++ b/pkg/models/certification.go @@ -2,7 +2,6 @@ package models import ( "errors" - "fmt" "time" "gorm.io/gorm" @@ -66,7 +65,9 @@ func (u *User) CertifiedSince() (time.Time, error) { } if cert.Status != CertificationPhotoApproved { - return time.Time{}, fmt.Errorf("cert photo status is: %s (expected 'approved')", cert.Status) + // The edge case can come up if a user was manually certified but didn't have an approved picture. + // Return their CreatedAt instead. + return u.CreatedAt, nil } return cert.UpdatedAt, nil diff --git a/pkg/models/comment.go b/pkg/models/comment.go index f4be430..ab92303 100644 --- a/pkg/models/comment.go +++ b/pkg/models/comment.go @@ -104,21 +104,27 @@ func CountCommentsReceived(user *User) int64 { } // PaginateComments provides a page of comments on something. -func PaginateComments(user *User, tableName string, tableID uint64, pager *Pagination) ([]*Comment, error) { +// +// Note: noBlockLists is to facilitate user-owned forums, where forum owners/moderators should override the block lists +// and retain full visibility into all user comments on their forum. Default/recommended is to leave it false, where +// the user's block list filters the view. +func PaginateComments(user *User, tableName string, tableID uint64, noBlockLists bool, pager *Pagination) ([]*Comment, error) { var ( - cs = []*Comment{} - query = (&Comment{}).Preload() - blockedUserIDs = BlockedUserIDs(user) - wheres = []string{} - placeholders = []interface{}{} + cs = []*Comment{} + query = (&Comment{}).Preload() + wheres = []string{} + placeholders = []interface{}{} ) wheres = append(wheres, "table_name = ? AND table_id = ?") placeholders = append(placeholders, tableName, tableID) - if len(blockedUserIDs) > 0 { - wheres = append(wheres, "user_id NOT IN ?") - placeholders = append(placeholders, blockedUserIDs) + if !noBlockLists { + blockedUserIDs := BlockedUserIDs(user) + if len(blockedUserIDs) > 0 { + wheres = append(wheres, "user_id NOT IN ?") + placeholders = append(placeholders, blockedUserIDs) + } } // Don't show comments from banned or disabled accounts. diff --git a/pkg/models/forum.go b/pkg/models/forum.go index 6c8a836..e83c06c 100644 --- a/pkg/models/forum.go +++ b/pkg/models/forum.go @@ -140,8 +140,9 @@ func PaginateForums(user *User, categories []string, search *Search, subscribed WHERE user_id = ? AND forum_id = forums.id ) + OR forums.owner_id = ? `) - placeholders = append(placeholders, user.ID) + placeholders = append(placeholders, user.ID, user.ID) } // Apply their search terms. diff --git a/pkg/models/private_photo.go b/pkg/models/private_photo.go index e193aba..736ac54 100644 --- a/pkg/models/private_photo.go +++ b/pkg/models/private_photo.go @@ -226,6 +226,10 @@ func PaginatePrivatePhotoList(user *User, grantee bool, pager *Pagination) ([]*U query *gorm.DB wheres = []string{} placeholders = []interface{}{} + blocklist = BlockedUserIDs(user) + + // Column name of "other user" depending on direction + otherUserColumn string ) // Which direction are we going? @@ -233,22 +237,33 @@ func PaginatePrivatePhotoList(user *User, grantee bool, pager *Pagination) ([]*U // Return the private photo grants for whom YOU are the recipient. wheres = append(wheres, "target_user_id = ?") placeholders = append(placeholders, user.ID) + otherUserColumn = "source_user_id" } else { // Return the users that YOU have granted access to YOUR private pictures. wheres = append(wheres, "source_user_id = ?") placeholders = append(placeholders, user.ID) + otherUserColumn = "target_user_id" } - // Users that actually exist. - wheres = append(wheres, ` - EXISTS ( - SELECT 1 - FROM users - WHERE users.id = private_photos.target_user_id - OR users.id = private_photos.source_user_id - )`, + // Filter out users who are banned/disabled. + wheres = append(wheres, + fmt.Sprintf(` + EXISTS ( + SELECT 1 + FROM users + WHERE private_photos.%s = users.id + AND users.status = 'active' + )`, + otherUserColumn, + ), ) + // Filter blocked users. + if len(blocklist) > 0 { + wheres = append(wheres, fmt.Sprintf("%s NOT IN ?", otherUserColumn)) + placeholders = append(placeholders, blocklist) + } + query = DB.Where( strings.Join(wheres, " AND "), placeholders..., diff --git a/pkg/models/user_relationship.go b/pkg/models/user_relationship.go index b94fc25..1c0bca0 100644 --- a/pkg/models/user_relationship.go +++ b/pkg/models/user_relationship.go @@ -8,6 +8,7 @@ type UserRelationship struct { Computed bool // check whether the SetUserRelationships function has been run IsFriend bool // if true, a friends-only profile pic can show IsPrivateGranted bool // if true, a private profile pic can show + IsBlocked bool // if true, the users are blocking each other } // SetUserRelationships updates a set of User objects to populate their UserRelationships in @@ -17,12 +18,14 @@ func SetUserRelationships(currentUser *User, users []*User) error { var ( friendIDs = FriendIDs(currentUser.ID) privateGrants = PrivateGrantedUserIDs(currentUser.ID) + blockedIDs = BlockedUserIDs(currentUser) ) // Map them for easier lookup. var ( friendMap = map[uint64]interface{}{} privateMap = map[uint64]interface{}{} + blockedMap = map[uint64]interface{}{} ) for _, id := range friendIDs { @@ -33,6 +36,10 @@ func SetUserRelationships(currentUser *User, users []*User) error { privateMap[id] = nil } + for _, id := range blockedIDs { + blockedMap[id] = nil + } + // Inject the UserRelationships. for _, u := range users { if u.ID == currentUser.ID { @@ -49,6 +56,10 @@ func SetUserRelationships(currentUser *User, users []*User) error { if _, ok := privateMap[u.ID]; ok { u.UserRelationship.IsPrivateGranted = true } + + if _, ok := blockedMap[u.ID]; ok { + u.UserRelationship.IsBlocked = true + } } return nil } diff --git a/web/templates/forum/board_index.html b/web/templates/forum/board_index.html index 4536719..7993c14 100644 --- a/web/templates/forum/board_index.html +++ b/web/templates/forum/board_index.html @@ -111,12 +111,20 @@