From 3c0473c633fbff0f6fae203eb3b1039cca1aeaed Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 14 Feb 2024 21:38:20 -0800 Subject: [PATCH] WIP: Deduplicate threads on Newest forum tab --- pkg/controller/forum/newest.go | 8 ++- pkg/models/forum_recent.go | 81 ++++++++++++++++++++----- web/templates/account/inner_circle.html | 2 +- web/templates/forum/newest.html | 11 +++- 4 files changed, 84 insertions(+), 18 deletions(-) diff --git a/pkg/controller/forum/newest.go b/pkg/controller/forum/newest.go index 6ff075f..665cf29 100644 --- a/pkg/controller/forum/newest.go +++ b/pkg/controller/forum/newest.go @@ -14,6 +14,11 @@ import ( func Newest() http.HandlerFunc { tmpl := templates.Must("forum/newest.html") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Query parameters. + var ( + allComments = r.FormValue("all") == "true" + ) + // Get the current user. currentUser, err := session.CurrentUser(r) if err != nil { @@ -29,7 +34,7 @@ func Newest() http.HandlerFunc { } pager.ParsePage(r) - posts, err := models.PaginateRecentPosts(currentUser, config.ForumCategories, pager) + posts, err := models.PaginateRecentPosts(currentUser, config.ForumCategories, allComments, pager) if err != nil { session.FlashError(w, r, "Couldn't paginate forums: %s", err) templates.Redirect(w, "/") @@ -50,6 +55,7 @@ func Newest() http.HandlerFunc { "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/models/forum_recent.go b/pkg/models/forum_recent.go index cfa4928..195c358 100644 --- a/pkg/models/forum_recent.go +++ b/pkg/models/forum_recent.go @@ -1,9 +1,11 @@ package models import ( + "fmt" "strings" "time" + "code.nonshy.com/nonshy/website/pkg/config" "code.nonshy.com/nonshy/website/pkg/log" ) @@ -20,7 +22,7 @@ type RecentPost struct { } // PaginateRecentPosts returns all of the comments on a forum paginated. -func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([]*RecentPost, error) { +func PaginateRecentPosts(user *User, categories []string, allComments bool, pager *Pagination) ([]*RecentPost, error) { var ( result = []*RecentPost{} query = (&Comment{}).Preload() @@ -65,24 +67,73 @@ func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([] CommentID uint64 ThreadID *uint64 ForumID *uint64 + UpdatedAt time.Time } var scan []scanner - query = DB.Table("comments").Select( - `comments.id AS comment_id, - threads.id AS thread_id, - forums.id AS forum_id`, - ).Joins( - "LEFT OUTER JOIN threads ON (table_name = 'threads' AND table_id = threads.id)", - ).Joins( - "LEFT OUTER JOIN forums ON (threads.forum_id = forums.id)", - ).Where( - strings.Join(wheres, " AND "), - placeholders..., - ).Order("comments.updated_at desc") + + // Deduplicate forum threads: if one thread is BLOWING UP with replies, we should only + // mention the thread once and show the newest comment so it doesn't spam the whole page. + if config.Current.Database.IsPostgres && !allComments { + // Deduplicating is supported in Postgres but not SQLite. We also need a custom + // total count query which is 99% the same as the select query except for the + // SELECT and ORDER BY bookends. + subquery := fmt.Sprintf(` + FROM ( + SELECT DISTINCT ON (threads.id) + comments.id AS comment_id, + threads.id AS thread_id, + forums.id AS forum_id, + comments.updated_at AS updated_at + FROM comments + LEFT OUTER JOIN threads ON (table_name='threads' AND table_id=threads.id) + LEFT OUTER JOIN forums ON (threads.forum_id=forums.id) + WHERE %s + ORDER BY threads.id + ) AS subquery + `, strings.Join(wheres, " AND ")) + + query = DB.Raw(fmt.Sprintf(` + SELECT comment_id, thread_id, forum_id, updated_at + %s + ORDER BY subquery.updated_at DESC + OFFSET %d LIMIT %d + `, subquery, pager.GetOffset(), pager.PerPage), placeholders...) + + // Get a count of records. + DB.Raw(fmt.Sprintf("SELECT count(*) %s", subquery), placeholders...).Count(&pager.Total) + + query = query.Find(&scan) + if query.Error != nil { + return nil, query.Error + } + } else { + // SQLite/non-Postgres doesn't support DISTINCT ON, this is the old query which + // shows objectively all comments and a popular thread may dominate the page. + query = DB.Table("comments").Select( + `comments.id AS comment_id, + threads.id AS thread_id, + forums.id AS forum_id, + comments.updated_at AS updated_at`, + ).Joins( + "LEFT OUTER JOIN threads ON (table_name = 'threads' AND table_id = threads.id)", + ).Joins( + "LEFT OUTER JOIN forums ON (threads.forum_id = forums.id)", + ).Where( + strings.Join(wheres, " AND "), + placeholders..., + ).Order("comments.updated_at desc") + query.Model(&Comment{}).Count(&pager.Total) + + // Execute the query. + query = query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&scan) + if query.Error != nil { + return nil, query.Error + } + } // Get the total for the pager and scan the page of ID sets. - query.Model(&Comment{}).Count(&pager.Total) - query = query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&scan) + // query.Model(&Comment{}).Count(&pager.Total) + // query = query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&scan) if query.Error != nil { return nil, query.Error } diff --git a/web/templates/account/inner_circle.html b/web/templates/account/inner_circle.html index 3e909e3..7779b9c 100644 --- a/web/templates/account/inner_circle.html +++ b/web/templates/account/inner_circle.html @@ -99,7 +99,7 @@
  • On the - Forums + Forums you can access exclusive inner circle-only boards.
  • diff --git a/web/templates/forum/newest.html b/web/templates/forum/newest.html index 072fc30..bd46ff4 100644 --- a/web/templates/forum/newest.html +++ b/web/templates/forum/newest.html @@ -39,7 +39,16 @@
    - Found {{FormatNumberCommas .Pager.Total}} posts (page {{.Pager.Page}} of {{.Pager.Pages}}) + Found {{FormatNumberCommas .Pager.Total}} {{if .AllComments}}posts{{else}}threads{{end}} (page {{.Pager.Page}} of {{.Pager.Pages}}) + +
    + {{if not .AllComments}} + + Showing only the latest comment per thread. Show all comments instead? + {{else}} + Showing all forum posts by most recent. Deduplicate by thread? + {{end}} +