website/pkg/models/forum_recent.go
Noah 500456c05e Recent Forum Posts
* Add a "Newest" tab to the Forums landing page to order ALL forum posts
  (comments) by most recent, paginated.
* Add a "Views" cooldown in Redis: viewing the same post multiple times
  within 1 hour doesn't ++ the view count with every page load, per user
  per thread ID.
* Update the paginators to handle unlimited numbers of pages: shows max
  7 page buttons with your current page towards the middle.
* General ability to jump to the "last page" of anything: use a negative
  page size like ?page=-1 and it acts like the last page.
2022-08-30 22:13:57 -07:00

153 lines
3.7 KiB
Go

package models
import (
"strings"
"time"
"code.nonshy.com/nonshy/website/pkg/log"
)
// RecentPost drives the "Forums / Newest" page - carrying all forum comments
// on all threads sorted by date.
type RecentPost struct {
CommentID uint64
ThreadID uint64
ForumID uint64
UpdatedAt time.Time
Thread *Thread
Comment *Comment
Forum *Forum
}
// PaginateRecentPosts returns all of the comments on a forum paginated.
func PaginateRecentPosts(user *User, categories []string, pager *Pagination) ([]*RecentPost, error) {
var (
result = []*RecentPost{}
query = (&Comment{}).Preload()
wheres = []string{"table_name = 'threads'"}
placeholders = []interface{}{}
)
if categories != nil && len(categories) > 0 {
wheres = append(wheres, "forums.category IN ?")
placeholders = append(placeholders, categories)
}
// Hide explicit forum if user hasn't opted into it.
if !user.Explicit && !user.IsAdmin {
wheres = append(wheres, "explicit = false")
}
// Get the page of recent forum comment IDs of all time.
type scanner struct {
CommentID uint64
ThreadID *uint64
ForumID *uint64
}
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")
// 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)
if query.Error != nil {
return nil, query.Error
}
// Ingest the results.
var (
commentIDs = []uint64{} // collect distinct IDs
threadIDs = []uint64{}
forumIDs = []uint64{}
seenComments = map[uint64]interface{}{} // deduplication
seenThreads = map[uint64]interface{}{}
seenForums = map[uint64]interface{}{}
mapCommentRC = map[uint64]*RecentPost{} // map commentID to result
)
for _, row := range scan {
// Upsert the result set.
var rp *RecentPost
if existing, ok := mapCommentRC[row.CommentID]; ok {
rp = existing
} else {
rp = &RecentPost{
CommentID: row.CommentID,
}
mapCommentRC[row.CommentID] = rp
result = append(result, rp)
}
// Got a thread ID?
if row.ThreadID != nil {
rp.ThreadID = *row.ThreadID
if _, ok := seenThreads[rp.ThreadID]; !ok {
seenThreads[rp.ThreadID] = nil
threadIDs = append(threadIDs, rp.ThreadID)
}
}
// Got a forum ID?
if row.ForumID != nil {
rp.ForumID = *row.ForumID
if _, ok := seenForums[rp.ForumID]; !ok {
seenForums[rp.ForumID] = nil
forumIDs = append(forumIDs, rp.ForumID)
}
}
// Collect distinct comment IDs.
if _, ok := seenComments[rp.CommentID]; !ok {
seenComments[rp.CommentID] = nil
commentIDs = append(commentIDs, rp.CommentID)
}
}
// Load all of the distinct comments, threads and forums.
var (
comments = map[uint64]*Comment{}
threads = map[uint64]*Thread{}
forums = map[uint64]*Forum{}
)
if len(commentIDs) > 0 {
comments, _ = GetComments(commentIDs)
}
if len(threadIDs) > 0 {
threads, _ = GetThreads(threadIDs)
}
if len(forumIDs) > 0 {
forums, _ = GetForums(forumIDs)
}
// Merge all the objects back in.
for _, rc := range result {
if com, ok := comments[rc.CommentID]; ok {
rc.Comment = com
}
if thr, ok := threads[rc.ThreadID]; ok {
rc.Thread = thr
} else {
log.Error("RecentPosts: didn't find thread ID %d in map!")
}
if f, ok := forums[rc.ForumID]; ok {
rc.Forum = f
}
}
return result, nil
}