From 1bf846e78bc0b595173a9fd2629d96a4b86261ae Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Tue, 20 Aug 2024 19:31:56 -0700 Subject: [PATCH] Forum Admin Page for Regular Users Allow regular (non-admin) users access to the Manage Forums page so they can create and manage their own forums. Things that were already working: * The admin forum page was already anticipating regular LoginRequired credential * Users only see their owned forums, while admins can see and manage ALL forums Improvements made to the Forum Admin page: * Change the title color from admin-red to user-blue. * Add ability to search (filter) and sort the forums. Other changes: * Turn the Forum tab bar into a reusable component. --- pkg/config/config.go | 3 ++ pkg/controller/forum/manage.go | 35 +++++++++++++- pkg/controller/forum/newest.go | 9 ++-- pkg/controller/forum/search.go | 9 ++-- pkg/models/forum.go | 35 ++++++++++---- pkg/router/router.go | 4 +- pkg/templates/template_vars.go | 3 ++ pkg/templates/templates.go | 1 + web/templates/forum/admin.html | 63 +++++++++++++++++++++++++- web/templates/forum/index.html | 33 +++----------- web/templates/forum/newest.html | 23 +--------- web/templates/forum/search.html | 23 +--------- web/templates/partials/forum_tabs.html | 32 +++++++++++++ 13 files changed, 183 insertions(+), 90 deletions(-) create mode 100644 web/templates/partials/forum_tabs.html diff --git a/pkg/config/config.go b/pkg/config/config.go index 127b07f..32322fc 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -122,6 +122,9 @@ const ( // rapidly it does not increment the view counter more. ThreadViewDebounceRedisKey = "debounce-view/user=%d/thr=%d" ThreadViewDebounceCooldown = 1 * time.Hour + + // Enable user-owned forums (feature flag) + UserForumsEnabled = true ) // Poll settings diff --git a/pkg/controller/forum/manage.go b/pkg/controller/forum/manage.go index 7405426..33905ca 100644 --- a/pkg/controller/forum/manage.go +++ b/pkg/controller/forum/manage.go @@ -12,7 +12,31 @@ import ( // Manage page for forums -- admin only for now but may open up later. func Manage() http.HandlerFunc { tmpl := templates.Must("forum/admin.html") + + // Whitelist for ordering options. + var sortWhitelist = []string{ + "updated_at desc", + "created_at desc", + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ( + searchTerm = r.FormValue("q") + sort = r.FormValue("sort") + sortOK bool + ) + + // Sort options. + for _, v := range sortWhitelist { + if sort == v { + sortOK = true + break + } + } + if !sortOK { + sort = sortWhitelist[0] + } + // Get the current user. currentUser, err := session.CurrentUser(r) if err != nil { @@ -21,15 +45,18 @@ func Manage() http.HandlerFunc { return } + // Parse their search term. + var search = models.ParseSearchString(searchTerm) + // Get forums the user owns or can manage. var pager = &models.Pagination{ Page: 1, PerPage: config.PageSizeForumAdmin, - Sort: "updated_at desc", + Sort: sort, } pager.ParsePage(r) - forums, err := models.PaginateOwnedForums(currentUser.ID, currentUser.IsAdmin, pager) + forums, err := models.PaginateOwnedForums(currentUser.ID, currentUser.IsAdmin, search, pager) if err != nil { session.FlashError(w, r, "Couldn't paginate owned forums: %s", err) templates.Redirect(w, "/") @@ -39,6 +66,10 @@ func Manage() http.HandlerFunc { var vars = map[string]interface{}{ "Pager": pager, "Forums": forums, + + // Search filters. + "SearchTerm": searchTerm, + "Sort": sort, } if err := tmpl.Execute(w, r, vars); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/pkg/controller/forum/newest.go b/pkg/controller/forum/newest.go index 665cf29..e98581e 100644 --- a/pkg/controller/forum/newest.go +++ b/pkg/controller/forum/newest.go @@ -52,10 +52,11 @@ func Newest() http.HandlerFunc { } var vars = map[string]interface{}{ - "Pager": pager, - "RecentPosts": posts, - "PhotoMap": photos, - "AllComments": allComments, + "CurrentForumTab": "newest", + "Pager": pager, + "RecentPosts": posts, + "PhotoMap": photos, + "AllComments": allComments, } if err := tmpl.Execute(w, r, vars); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/pkg/controller/forum/search.go b/pkg/controller/forum/search.go index 0a884c0..0c8f060 100644 --- a/pkg/controller/forum/search.go +++ b/pkg/controller/forum/search.go @@ -100,10 +100,11 @@ func Search() http.HandlerFunc { } var vars = map[string]interface{}{ - "Pager": pager, - "Comments": posts, - "ThreadMap": threadMap, - "PhotoMap": photos, + "CurrentForumTab": "search", + "Pager": pager, + "Comments": posts, + "ThreadMap": threadMap, + "PhotoMap": photos, "SearchTerm": searchTerm, "ByUsername": byUsername, diff --git a/pkg/models/forum.go b/pkg/models/forum.go index e9fd09a..2f45885 100644 --- a/pkg/models/forum.go +++ b/pkg/models/forum.go @@ -116,20 +116,39 @@ func PaginateForums(user *User, categories []string, pager *Pagination) ([]*Foru } // PaginateOwnedForums returns forums the user owns (or all forums to admins). -func PaginateOwnedForums(userID uint64, isAdmin bool, pager *Pagination) ([]*Forum, error) { +func PaginateOwnedForums(userID uint64, isAdmin bool, search *Search, pager *Pagination) ([]*Forum, error) { var ( - fs = []*Forum{} - query = (&Forum{}).Preload() + fs = []*Forum{} + query = (&Forum{}).Preload() + wheres = []string{} + placeholders = []interface{}{} ) + // Users see only their owned forums. if !isAdmin { - query = query.Where( - "owner_id = ?", - userID, - ) + wheres = append(wheres, "owner_id = ?") + placeholders = append(placeholders, userID) } - query = query.Order(pager.Sort) + // Apply their search terms. + if search != nil { + for _, term := range search.Includes { + var ilike = "%" + strings.ToLower(term) + "%" + wheres = append(wheres, "(fragment ILIKE ? OR title ILIKE ? OR description ILIKE ?)") + placeholders = append(placeholders, ilike, ilike, ilike) + } + for _, term := range search.Excludes { + var ilike = "%" + strings.ToLower(term) + "%" + wheres = append(wheres, "(fragment NOT ILIKE ? AND title NOT ILIKE ? AND description NOT ILIKE ?)") + placeholders = append(placeholders, ilike, ilike, ilike) + } + } + + query = query.Where( + strings.Join(wheres, " AND "), + placeholders..., + ).Order(pager.Sort) + query.Model(&Forum{}).Count(&pager.Total) result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&fs) return fs, result.Error diff --git a/pkg/router/router.go b/pkg/router/router.go index 7a17af9..aa9131a 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -100,8 +100,8 @@ func New() http.Handler { mux.Handle("/admin/user-action", middleware.AdminRequired("", admin.UserActions())) mux.Handle("/admin/maintenance", middleware.AdminRequired(config.ScopeMaintenance, admin.Maintenance())) mux.Handle("/admin/add-user", middleware.AdminRequired(config.ScopeUserCreate, admin.AddUser())) - mux.Handle("/forum/admin", middleware.AdminRequired(config.ScopeForumAdmin, forum.Manage())) - mux.Handle("/forum/admin/edit", middleware.AdminRequired(config.ScopeForumAdmin, forum.AddEdit())) + mux.Handle("/forum/admin", middleware.CertRequired(forum.Manage())) + mux.Handle("/forum/admin/edit", middleware.CertRequired(forum.AddEdit())) mux.Handle("/admin/photo/mark-explicit", middleware.AdminRequired("", admin.MarkPhotoExplicit())) mux.Handle("GET /admin/changelog", middleware.AdminRequired(config.ScopeChangeLog, admin.ChangeLog())) diff --git a/pkg/templates/template_vars.go b/pkg/templates/template_vars.go index f73e8f8..4a8259b 100644 --- a/pkg/templates/template_vars.go +++ b/pkg/templates/template_vars.go @@ -23,6 +23,9 @@ func MergeVars(r *http.Request, m map[string]interface{}) { // Integrations m["TurnstileCAPTCHA"] = config.Current.Turnstile + // Feature flags + m["FeatureUserForumsEnabled"] = config.UserForumsEnabled + if r == nil { return } diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go index bdf98c1..259ae7b 100644 --- a/pkg/templates/templates.go +++ b/pkg/templates/templates.go @@ -167,6 +167,7 @@ var baseTemplates = []string{ config.TemplatePath + "/partials/right_click.html", config.TemplatePath + "/partials/mark_explicit.html", config.TemplatePath + "/partials/themes.html", + config.TemplatePath + "/partials/forum_tabs.html", } // templates returns a template chain with the base templates preceding yours. diff --git a/web/templates/forum/admin.html b/web/templates/forum/admin.html index cb4d86b..8c47da8 100644 --- a/web/templates/forum/admin.html +++ b/web/templates/forum/admin.html @@ -1,11 +1,11 @@ {{define "title"}}Forums{{end}} {{define "content"}}
-
+

- + Forum Administration

@@ -22,6 +22,65 @@
+
+
+ +
+ +
+
+ +
+
+ + +

+ Tip: you can "quote exact phrases" and + -exclude words (or + -"exclude phrases") from your search. +

+
+
+ +
+
+ +
+ +
+
+
+ +
+ + Reset + +
+
+
+
+ +
+
+

Found {{.Pager.Total}} forum{{Pluralize64 .Pager.Total}} you can manage (page {{.Pager.Page}} of {{.Pager.Pages}}). diff --git a/web/templates/forum/index.html b/web/templates/forum/index.html index 459b9f4..aad401d 100644 --- a/web/templates/forum/index.html +++ b/web/templates/forum/index.html @@ -5,18 +5,18 @@

-
+

Forums

- {{if .CurrentUser.HasAdminScope "admin.forum.manage"}} + {{if or .FeatureUserForumsEnabled (.CurrentUser.HasAdminScope "admin.forum.manage")}} @@ -29,27 +29,8 @@ {{$Root := .}} - + +{{template "ForumTabs" .}} {{range .Categories}}
diff --git a/web/templates/forum/newest.html b/web/templates/forum/newest.html index 512c2ca..02a5f82 100644 --- a/web/templates/forum/newest.html +++ b/web/templates/forum/newest.html @@ -16,27 +16,8 @@ {{$Root := .}} - + +{{template "ForumTabs" .}}
Found {{FormatNumberCommas .Pager.Total}} {{if .AllComments}}posts{{else}}threads{{end}} (page {{.Pager.Page}} of {{.Pager.Pages}}) diff --git a/web/templates/forum/search.html b/web/templates/forum/search.html index cb301a3..f88afc8 100644 --- a/web/templates/forum/search.html +++ b/web/templates/forum/search.html @@ -16,27 +16,8 @@ {{$Root := .}} - + +{{template "ForumTabs" .}}
diff --git a/web/templates/partials/forum_tabs.html b/web/templates/partials/forum_tabs.html new file mode 100644 index 0000000..51bfcb6 --- /dev/null +++ b/web/templates/partials/forum_tabs.html @@ -0,0 +1,32 @@ + +{{define "ForumTabs"}} + +{{end}}