Noah
93c13882aa
Finish implementing the basic forum features: * Pinned threads (admin or board owner only) * Edit Thread settings when you edit the top-most comment. * NoReply threads remove all the reply buttons. * Explicit forums and threads are filtered out unless opted-in (admins always see them). * Count the unique members who participated in each forum. * Get the most recently updated thread to show on forum list page. * Contact/Report page: handle receiving a comment ID to report on. Implement Likes & Notifications * Like buttons added to Photos and Profile Pages. Implemented via simple vanilla JS (likes.js) to make ajax requests to back-end to like/unlike. * Notifications: for your photo or profile being liked. If you unlike, the existing notifications about the like are revoked. * The notifications appear as an alert number in the nav bar and are read on the User Dashboard. Click to mark a notification as "read" or click the "mark all as read" button. Update DeleteUser to scrub likes, notifications, threads, and comments.
204 lines
4.5 KiB
Go
204 lines
4.5 KiB
Go
package models
|
|
|
|
import "git.kirsle.net/apps/gosocial/pkg/log"
|
|
|
|
// ForumStatistics queries for forum-level statistics.
|
|
type ForumStatistics struct {
|
|
RecentThread *Thread
|
|
Threads uint64
|
|
Posts uint64
|
|
Users uint64
|
|
}
|
|
|
|
type ForumStatsMap map[uint64]*ForumStatistics
|
|
|
|
// MapForumStatistics looks up statistics for a set of forums.
|
|
func MapForumStatistics(forums []*Forum) ForumStatsMap {
|
|
var (
|
|
result = ForumStatsMap{}
|
|
IDs = []uint64{}
|
|
)
|
|
|
|
// Collect forum IDs and initialize the map.
|
|
for _, forum := range forums {
|
|
IDs = append(IDs, forum.ID)
|
|
result[forum.ID] = &ForumStatistics{}
|
|
}
|
|
|
|
// Gather all the statistics.
|
|
result.generateThreadCount(IDs)
|
|
result.generatePostCount(IDs)
|
|
result.generateUserCount(IDs)
|
|
result.generateRecentThreads(IDs)
|
|
|
|
return result
|
|
}
|
|
|
|
// Has stats for this thread? (we should..)
|
|
func (ts ForumStatsMap) Has(threadID uint64) bool {
|
|
_, ok := ts[threadID]
|
|
return ok
|
|
}
|
|
|
|
// Get thread stats.
|
|
func (ts ForumStatsMap) Get(threadID uint64) *ForumStatistics {
|
|
if stats, ok := ts[threadID]; ok {
|
|
return stats
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Compute the count of threads in each of the forum 'IDs'.
|
|
func (ts ForumStatsMap) generateThreadCount(IDs []uint64) {
|
|
// Hold the result of the count/group by query.
|
|
type group struct {
|
|
ID uint64
|
|
Threads uint64
|
|
}
|
|
var groups = []group{}
|
|
|
|
// Count comments grouped by thread IDs.
|
|
err := DB.Table(
|
|
"threads",
|
|
).Select(
|
|
"forum_id AS id, count(id) AS threads",
|
|
).Where(
|
|
"forum_id IN ?",
|
|
IDs,
|
|
).Group("forum_id").Scan(&groups)
|
|
|
|
if err.Error != nil {
|
|
log.Error("MapForumStatistics: SQL error: %s", err.Error)
|
|
}
|
|
|
|
// Map the results in.
|
|
for _, row := range groups {
|
|
if stats, ok := ts[row.ID]; ok {
|
|
stats.Threads = row.Threads
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute the count of all posts in each of the forum 'IDs'.
|
|
func (ts ForumStatsMap) generatePostCount(IDs []uint64) {
|
|
type group struct {
|
|
ID uint64
|
|
Posts uint64
|
|
}
|
|
var groups = []group{}
|
|
|
|
err := DB.Table(
|
|
"comments",
|
|
).Joins(
|
|
"JOIN threads ON (table_name = 'threads' AND table_id = threads.id)",
|
|
).Joins(
|
|
"JOIN forums ON (threads.forum_id = forums.id)",
|
|
).Select(
|
|
"forums.id AS id, count(comments.id) AS posts",
|
|
).Where(
|
|
`table_name = 'threads' AND EXISTS (
|
|
SELECT 1
|
|
FROM threads
|
|
WHERE table_id = threads.id
|
|
AND threads.forum_id IN ?
|
|
)`,
|
|
IDs,
|
|
).Group("forums.id").Scan(&groups)
|
|
|
|
if err.Error != nil {
|
|
log.Error("SQL error collecting posts for forum: %s", err.Error)
|
|
}
|
|
|
|
// Map the results in.
|
|
for _, row := range groups {
|
|
if stats, ok := ts[row.ID]; ok {
|
|
stats.Posts = row.Posts
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute the count of all users in each of the forum 'IDs'.
|
|
func (ts ForumStatsMap) generateUserCount(IDs []uint64) {
|
|
type group struct {
|
|
ForumID uint64
|
|
Users uint64
|
|
}
|
|
var groups = []group{}
|
|
|
|
err := DB.Table(
|
|
"comments",
|
|
).Joins(
|
|
"JOIN threads ON (table_name = 'threads' AND table_id = threads.id)",
|
|
).Joins(
|
|
"JOIN forums ON (threads.forum_id = forums.id)",
|
|
).Select(
|
|
"forums.id AS forum_id, count(distinct(comments.user_id)) AS users",
|
|
).Where(
|
|
"forums.id IN ?",
|
|
IDs,
|
|
).Group("forums.id").Scan(&groups)
|
|
|
|
if err.Error != nil {
|
|
log.Error("SQL error collecting users for forum: %s", err.Error)
|
|
}
|
|
|
|
// Map the results in.
|
|
for _, row := range groups {
|
|
if stats, ok := ts[row.ForumID]; ok {
|
|
stats.Users = row.Users
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute the recent threads for each of the forum 'IDs'.
|
|
func (ts ForumStatsMap) generateRecentThreads(IDs []uint64) {
|
|
var threadIDs = []map[string]interface{}{}
|
|
err := DB.Table(
|
|
"threads",
|
|
).Select(
|
|
"forum_id, id AS thread_id, updated_at",
|
|
).Where(
|
|
`updated_at = (SELECT MAX(updated_at)
|
|
FROM threads t2
|
|
WHERE threads.forum_id = t2.forum_id)
|
|
AND threads.forum_id IN ?`,
|
|
IDs,
|
|
).Order(
|
|
"updated_at desc",
|
|
).Scan(&threadIDs)
|
|
|
|
if err.Error != nil {
|
|
log.Error("Getting most recent thread IDs: %s", err.Error)
|
|
}
|
|
|
|
// Map them easier.
|
|
var (
|
|
threadForumMap = map[uint64]uint64{}
|
|
allThreadIDs = []uint64{}
|
|
)
|
|
for _, row := range threadIDs {
|
|
if row["thread_id"] == nil || row["forum_id"] == nil {
|
|
continue
|
|
}
|
|
|
|
var (
|
|
threadID = uint64(row["thread_id"].(int64))
|
|
forumID = uint64(row["forum_id"].(int64))
|
|
)
|
|
|
|
allThreadIDs = append(allThreadIDs, threadID)
|
|
threadForumMap[threadID] = forumID
|
|
}
|
|
|
|
// Select and map these threads in.
|
|
if threadMap, err := GetThreads(allThreadIDs); err == nil {
|
|
for threadID, thread := range threadMap {
|
|
if forumID, ok := threadForumMap[threadID]; ok {
|
|
if stats, ok := ts[forumID]; ok {
|
|
stats.RecentThread = thread
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|