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() blockedUserIDs = BlockedUserIDs(user) wheres = []string{"table_name = 'threads'"} placeholders = []interface{}{} ) if 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, "forums.explicit = false") } // Circle membership. if !user.IsInnerCircle() { wheres = append(wheres, "forums.inner_circle is not true") } // Blocked users? if len(blockedUserIDs) > 0 { wheres = append(wheres, "comments.user_id NOT IN ?") placeholders = append(placeholders, blockedUserIDs) } // Don't show comments from banned or disabled accounts. wheres = append(wheres, ` EXISTS ( SELECT 1 FROM users WHERE users.id = comments.user_id AND users.status = 'active' ) `) // 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, _ = GetThreadsAsUser(user, threadIDs) } if len(forumIDs) > 0 { forums, _ = GetForums(forumIDs) } // Collect comments so we can inject UserRelationships in efficiently. var ( coms = []*Comment{} thrs = []*Thread{} ) // Merge all the objects back in. for _, rc := range result { if com, ok := comments[rc.CommentID]; ok { rc.Comment = com coms = append(coms, com) } if thr, ok := threads[rc.ThreadID]; ok { rc.Thread = thr thrs = append(thrs, thr) } else { log.Error("RecentPosts: didn't find thread ID %d in map!", rc.ThreadID) // Create a dummy placeholder Thread (e.g.: the thread originator has been // banned or disabled, but the thread summary is shown on the new comment view) rc.Thread = &Thread{ Comment: Comment{ Message: "[unavailable]", }, } } if f, ok := forums[rc.ForumID]; ok { rc.Forum = f } } // Inject user relationships into all comment users now. SetUserRelationshipsInComments(user, coms) SetUserRelationshipsInThreads(user, thrs) return result, nil }