User-owned Forum Improvements

* 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
This commit is contained in:
Noah Petherbridge 2024-08-21 22:25:59 -07:00
parent 28d1e284ab
commit 8a321eb8d2
7 changed files with 66 additions and 23 deletions

View File

@ -37,7 +37,7 @@ func Forum() http.HandlerFunc {
}
// Is it a private forum?
if forum.Private && !currentUser.IsAdmin {
if !forum.CanBeSeenBy(currentUser) {
templates.NotFoundPage(w, r)
return
}

View File

@ -51,17 +51,19 @@ func Landing() http.HandlerFunc {
categorized := models.CategorizeForums(forums, config.ForumCategories)
// Inject the "My List" Category if the user subscribes to forums.
myList, err := models.PaginateForums(currentUser, nil, nil, true, pager)
if err != nil {
session.FlashError(w, r, "Couldn't get your followed forums: %s", err)
} else {
forums = append(forums, myList...)
categorized = append([]*models.CategorizedForum{
{
Category: "My List",
Forums: myList,
},
}, categorized...)
if config.UserForumsEnabled {
myList, err := models.PaginateForums(currentUser, nil, nil, true, pager)
if err != nil {
session.FlashError(w, r, "Couldn't get your followed forums: %s", err)
} else {
forums = append(forums, myList...)
categorized = append([]*models.CategorizedForum{
{
Category: "My List",
Forums: myList,
},
}, categorized...)
}
}
// Map statistics for these forums.

View File

@ -52,17 +52,14 @@ func Thread() http.HandlerFunc {
}
// Is it a private forum?
if forum.Private && !currentUser.IsAdmin {
if !forum.CanBeSeenBy(currentUser) {
templates.NotFoundPage(w, r)
return
}
// Can we moderate this forum? (from a user-owned forum perspective,
// e.g. can we delete threads and posts, not edit them)
var canModerate bool
if currentUser.HasAdminScope(config.ScopeForumModerator) || forum.OwnerID == currentUser.ID {
canModerate = true
}
var canModerate = forum.CanBeModeratedBy(currentUser)
// Ping the view count on this thread.
if err := thread.View(currentUser.ID); err != nil {

View File

@ -5,6 +5,7 @@ import (
"strings"
"time"
"code.nonshy.com/nonshy/website/pkg/config"
"code.nonshy.com/nonshy/website/pkg/log"
"gorm.io/gorm"
)
@ -79,6 +80,40 @@ func (f *Forum) AddModerator(user *User) (*ForumMembership, error) {
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)

View File

@ -93,11 +93,18 @@
</label>
<div class="select is-fullwidth">
<select name="category" id="category">
{{range .Categories}}
<option value="{{.}}"{{if and $Root.EditForum (eq $Root.EditForum.Category .)}} selected{{end}}>
{{.}}
</option>
{{end}}
<optgroup label="Community Forum">
<option value=""{{if and $Root.EditForum (eq $Root.EditForum.Category "")}} selected{{end}}>
This will be a community forum (no category set)
</option>
</optgroup>
<optgroup label="Official Forums">
{{range .Categories}}
<option value="{{.}}"{{if and $Root.EditForum (eq $Root.EditForum.Category .)}} selected{{end}}>
{{.}}
</option>
{{end}}
</optgroup>
</select>
</div>
</div>

View File

@ -97,7 +97,7 @@
<div class="column">
<h1 class="title">{{.Title}}</h1>
<h2 class="subtitle">
/f/{{.Fragment}}
<a href="/f/{{.Fragment}}">/f/{{.Fragment}}</a>
{{if .Category}}<span class="ml-4">{{.Category}}</span>{{end}}
<span class="ml-4">
by <strong><a href="/u/{{.Owner.Username}}">{{.Owner.Username}}</a></strong>

View File

@ -16,11 +16,13 @@ Variables that your template should set:
Categories
</a>
</li>
{{if .FeatureUserForumsEnabled}}
<li {{if eq .CurrentForumTab "explore" }}class="is-active"{{end}}>
<a href="/forum/explore">
Explore
</a>
</li>
{{end}}
<li {{if eq .CurrentForumTab "newest" }}class="is-active"{{end}}>
<a href="/forum/newest">
Newest