8a321eb8d2
* Private forums: CanBeSeenBy moderators, approved followers, its owner and admin users. * Note: the endpoint to subscribe to the forum won't allow users to follow the private forum, so approved followers can not be created at this time, except by adding them as moderators. * Admins: when creating a forum they can choose "no category" to create it as an unofficial community forum. * Code cleanup * More feature flag checking
236 lines
5.9 KiB
Go
236 lines
5.9 KiB
Go
package models
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/config"
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ForumMembership table.
|
|
//
|
|
// Unique key constraint pairs user_id and forum_id.
|
|
type ForumMembership struct {
|
|
ID uint64 `gorm:"primaryKey"`
|
|
UserID uint64 `gorm:"uniqueIndex:idx_forum_membership"`
|
|
User User `gorm:"foreignKey:user_id"`
|
|
ForumID uint64 `gorm:"uniqueIndex:idx_forum_membership"`
|
|
Forum Forum `gorm:"foreignKey:forum_id"`
|
|
Approved bool `gorm:"index"`
|
|
IsModerator bool `gorm:"index"`
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// Preload related tables for the forum (classmethod).
|
|
func (f *ForumMembership) Preload() *gorm.DB {
|
|
return DB.Preload("User").Preload("Forum")
|
|
}
|
|
|
|
// CreateForumMembership subscribes the user to a forum.
|
|
func CreateForumMembership(user *User, forum *Forum) (*ForumMembership, error) {
|
|
var (
|
|
f = &ForumMembership{
|
|
User: *user,
|
|
Forum: *forum,
|
|
Approved: true,
|
|
}
|
|
result = DB.Create(f)
|
|
)
|
|
return f, result.Error
|
|
}
|
|
|
|
// GetForumMembership looks up a forum membership, returning an error if one is not found.
|
|
func GetForumMembership(user *User, forum *Forum) (*ForumMembership, error) {
|
|
var (
|
|
f = &ForumMembership{}
|
|
result = f.Preload().Where(
|
|
"user_id = ? AND forum_id = ?",
|
|
user.ID, forum.ID,
|
|
).First(&f)
|
|
)
|
|
return f, result.Error
|
|
}
|
|
|
|
// AddModerator appoints a moderator to the forum, returning that user's ForumMembership.
|
|
//
|
|
// If the target is not following the forum, a ForumMembership is created, marked as a moderator and returned.
|
|
func (f *Forum) AddModerator(user *User) (*ForumMembership, error) {
|
|
var fm *ForumMembership
|
|
if found, err := GetForumMembership(user, f); err != nil {
|
|
fm = &ForumMembership{
|
|
User: *user,
|
|
Forum: *f,
|
|
Approved: true,
|
|
}
|
|
} else {
|
|
fm = found
|
|
}
|
|
|
|
// They are already a moderator?
|
|
if fm.IsModerator {
|
|
return fm, errors.New("they are already a moderator of this forum")
|
|
}
|
|
|
|
fm.IsModerator = true
|
|
err := fm.Save()
|
|
return fm, err
|
|
}
|
|
|
|
// CanBeSeenBy checks whether the user can see a private forum.
|
|
//
|
|
// Admins, owners, moderators and approved followers can see it.
|
|
//
|
|
// Note: this may invoke a DB query to check for moderator.
|
|
func (f *Forum) CanBeSeenBy(user *User) bool {
|
|
if !f.Private || user.IsAdmin || user.ID == f.OwnerID {
|
|
return true
|
|
}
|
|
|
|
if fm, err := GetForumMembership(user, f); err == nil {
|
|
return fm.Approved || fm.IsModerator
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// CanBeModeratedBy checks whether the user can moderate this forum.
|
|
//
|
|
// Admins, owners and moderators can do so.
|
|
//
|
|
// Note: this may invoke a DB query to check for moderator.
|
|
func (f *Forum) CanBeModeratedBy(user *User) bool {
|
|
if user.HasAdminScope(config.ScopeForumModerator) || f.OwnerID == user.ID {
|
|
return true
|
|
}
|
|
|
|
if fm, err := GetForumMembership(user, f); err == nil {
|
|
return fm.IsModerator
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// RemoveModerator will unset a user's moderator flag on this forum.
|
|
func (f *Forum) RemoveModerator(user *User) (*ForumMembership, error) {
|
|
fm, err := GetForumMembership(user, f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fm.IsModerator = false
|
|
err = fm.Save()
|
|
return fm, err
|
|
}
|
|
|
|
// GetModerators loads all of the moderators of a forum, ordered alphabetically by username.
|
|
func (f *Forum) GetModerators() ([]*User, error) {
|
|
// Find all forum memberships that moderate us.
|
|
var (
|
|
fm = []*ForumMembership{}
|
|
result = (&ForumMembership{}).Preload().Where(
|
|
"forum_id = ? AND is_moderator IS TRUE",
|
|
f.ID,
|
|
).Find(&fm)
|
|
)
|
|
if result.Error != nil {
|
|
log.Error("Forum(%d).GetModerators(): %s", f.ID, result.Error)
|
|
return nil, nil
|
|
}
|
|
|
|
// Load these users.
|
|
var userIDs = []uint64{}
|
|
for _, row := range fm {
|
|
userIDs = append(userIDs, row.UserID)
|
|
}
|
|
|
|
return GetUsersAlphabetically(userIDs)
|
|
}
|
|
|
|
// IsForumSubscribed checks if the current user subscribes to this forum.
|
|
func IsForumSubscribed(user *User, forum *Forum) bool {
|
|
f, _ := GetForumMembership(user, forum)
|
|
return f.UserID == user.ID
|
|
}
|
|
|
|
// HasForumSubscriptions returns if the current user has at least one forum membership.
|
|
func (u *User) HasForumSubscriptions() bool {
|
|
var count int64
|
|
DB.Model(&ForumMembership{}).Where(
|
|
"user_id = ?",
|
|
u.ID,
|
|
).Count(&count)
|
|
return count > 0
|
|
}
|
|
|
|
// Save a forum membership.
|
|
func (f *ForumMembership) Save() error {
|
|
return DB.Save(f).Error
|
|
}
|
|
|
|
// Delete a forum membership.
|
|
func (f *ForumMembership) Delete() error {
|
|
return DB.Delete(f).Error
|
|
}
|
|
|
|
// PaginateForumMemberships paginates over a user's ForumMemberships.
|
|
func PaginateForumMemberships(user *User, pager *Pagination) ([]*ForumMembership, error) {
|
|
var (
|
|
fs = []*ForumMembership{}
|
|
query = (&ForumMembership{}).Preload()
|
|
wheres = []string{}
|
|
placeholders = []interface{}{}
|
|
)
|
|
|
|
query = query.Where(
|
|
strings.Join(wheres, " AND "),
|
|
placeholders...,
|
|
).Order(pager.Sort)
|
|
|
|
query.Model(&ForumMembership{}).Count(&pager.Total)
|
|
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&fs)
|
|
return fs, result.Error
|
|
}
|
|
|
|
// ForumMembershipMap maps table IDs to Likes metadata.
|
|
type ForumMembershipMap map[uint64]bool
|
|
|
|
// Get like stats from the map.
|
|
func (fm ForumMembershipMap) Get(id uint64) bool {
|
|
return fm[id]
|
|
}
|
|
|
|
// MapForumMemberships looks up a user's memberships in bulk.
|
|
func MapForumMemberships(user *User, forums []*Forum) ForumMembershipMap {
|
|
var (
|
|
result = ForumMembershipMap{}
|
|
forumIDs = []uint64{}
|
|
)
|
|
|
|
// Initialize the result set.
|
|
for _, forum := range forums {
|
|
result[forum.ID] = false
|
|
forumIDs = append(forumIDs, forum.ID)
|
|
}
|
|
|
|
// Map the forum IDs the user subscribes to.
|
|
var followIDs = []uint64{}
|
|
if res := DB.Model(&ForumMembership{}).Select(
|
|
"forum_id",
|
|
).Where(
|
|
"user_id = ? AND forum_id IN ?",
|
|
user.ID, forumIDs,
|
|
).Scan(&followIDs); res.Error != nil {
|
|
log.Error("MapForumMemberships: %s", res.Error)
|
|
}
|
|
|
|
for _, forumID := range followIDs {
|
|
result[forumID] = true
|
|
}
|
|
|
|
return result
|
|
}
|