/* Package forum is the active record implementation for forum posts and threads in the game. The forum package provides a complete forum system with thread and reply functionality, search capabilities, and comprehensive post management. It supports hierarchical discussions with parent/child relationships between posts. # Basic Usage To retrieve a forum post by ID: post, err := forum.Find(db, 1) if err != nil { log.Fatal(err) } fmt.Printf("[%s] %s by user %d\n", post.PostedTime().Format("Jan 2"), post.Title, post.Author) To get all forum threads (top-level posts): threads, err := forum.Threads(db) if err != nil { log.Fatal(err) } for _, thread := range threads { fmt.Printf("Thread: %s (%d replies)\n", thread.Title, thread.Replies) } To get replies to a specific thread: replies, err := forum.ByParent(db, threadID) if err != nil { log.Fatal(err) } To search forum posts: results, err := forum.Search(db, "strategy") if err != nil { log.Fatal(err) } # Creating Posts with Builder Pattern The package provides a fluent builder interface for creating new forum posts: ## Creating a New Thread thread, err := forum.NewBuilder(db). WithAuthor(userID). WithTitle("New Strategy Discussion"). WithContent("What are your thoughts on the best character builds?"). AsThread(). Create() if err != nil { log.Fatal(err) } fmt.Printf("Created thread with ID: %d\n", thread.ID) ## Creating a Reply reply, err := forum.NewBuilder(db). WithAuthor(userID). WithTitle("Re: Strategy Discussion"). WithContent("I think mage builds are overpowered right now."). AsReply(parentThreadID). Create() if err != nil { log.Fatal(err) } The builder automatically sets timestamps to the current time if not specified. # Updating Posts Forum posts can be modified and saved back to the database: post, _ := forum.Find(db, 1) post.Title = "[UPDATED] " + post.Title post.Content = post.Content + "\n\nEDIT: Added clarification." post.UpdateLastPost() // Update last activity timestamp err := post.Save() if err != nil { log.Fatal(err) } # Deleting Posts Posts can be removed from the database: post, _ := forum.Find(db, 1) err := post.Delete() if err != nil { log.Fatal(err) } # Database Schema The forum table has the following structure: CREATE TABLE forum ( id INTEGER PRIMARY KEY AUTOINCREMENT, posted INTEGER NOT NULL DEFAULT (unixepoch()), last_post INTEGER NOT NULL DEFAULT (unixepoch()), author INTEGER NOT NULL, parent INTEGER NOT NULL DEFAULT 0, replies INTEGER NOT NULL DEFAULT 0, title TEXT NOT NULL, content TEXT NOT NULL ) Where: - id: Unique identifier for the forum post - posted: Unix timestamp when the post was originally created - last_post: Unix timestamp of the most recent activity on this thread/post - author: User ID of the post author - parent: Parent post ID (0 for top-level threads, >0 for replies) - replies: Number of direct replies to this post - title: Post title/subject line - content: Post content/body text # Thread and Reply System ## Thread Structure The forum uses a parent/child hierarchy: // Top-level threads have parent = 0 if post.IsThread() { fmt.Println("This is a main thread") } // Replies have parent > 0 if post.IsReply() { fmt.Printf("This is a reply to post %d\n", post.Parent) } ## Working with Threads Get all top-level threads: threads, _ := forum.Threads(db) fmt.Println("=== Forum Threads ===") for _, thread := range threads { age := thread.ActivityAge() var activityStr string if age < time.Hour { activityStr = fmt.Sprintf("%d minutes ago", int(age.Minutes())) } else if age < 24*time.Hour { activityStr = fmt.Sprintf("%d hours ago", int(age.Hours())) } else { activityStr = fmt.Sprintf("%d days ago", int(age.Hours()/24)) } fmt.Printf("📌 %s (%d replies, last activity %s)\n", thread.Title, thread.Replies, activityStr) } ## Working with Replies Get all replies to a thread: thread, _ := forum.Find(db, threadID) replies, _ := thread.GetReplies() fmt.Printf("=== %s ===\n", thread.Title) fmt.Printf("Posted by user %d on %s\n\n", thread.Author, thread.PostedTime().Format("Jan 2, 2006")) fmt.Println(thread.Content) if len(replies) > 0 { fmt.Printf("\n--- %d Replies ---\n", len(replies)) for i, reply := range replies { fmt.Printf("[%d] by user %d on %s:\n%s\n\n", i+1, reply.Author, reply.PostedTime().Format("Jan 2 15:04"), reply.Content) } } ## Navigation Between Posts Navigate the thread hierarchy: reply, _ := forum.Find(db, replyID) // Get the parent thread thread, err := reply.GetThread() if err == nil { fmt.Printf("This reply belongs to thread: %s\n", thread.Title) } // Get all sibling replies siblings, _ := forum.ByParent(db, reply.Parent) fmt.Printf("This thread has %d total replies\n", len(siblings)) # Search and Filtering ## Text Search Search within titles and content: // Search for posts about "pvp" pvpPosts, err := forum.Search(db, "pvp") if err != nil { log.Fatal(err) } fmt.Printf("Found %d posts about PvP:\n", len(pvpPosts)) for _, post := range pvpPosts { fmt.Printf("- %s: %s\n", post.Title, post.Preview(60)) } Search is case-insensitive and searches both titles and content. ## Author-Based Queries Find posts by specific users: // Get all posts by a user userPosts, err := forum.ByAuthor(db, userID) if err != nil { log.Fatal(err) } fmt.Printf("User %d has made %d posts:\n", userID, len(userPosts)) for _, post := range userPosts { postType := "thread" if post.IsReply() { postType = "reply" } fmt.Printf("- [%s] %s (%s ago)\n", postType, post.Title, post.PostAge()) } ## Activity-Based Queries Find recent activity: // Get recent forum activity recentPosts, err := forum.Recent(db, 20) if err != nil { log.Fatal(err) } // Get activity since user's last visit lastVisit := getUserLastVisit(userID) newActivity, err := forum.Since(db, lastVisit) if len(newActivity) > 0 { fmt.Printf("There have been %d new posts since your last visit\n", len(newActivity)) } # Post Management ## Reply Count Management Manage reply counts when posts are created or deleted: // When creating a reply reply, err := forum.NewBuilder(db). WithAuthor(userID). WithContent("Great point!"). AsReply(parentID). Create() if err == nil { // Update parent thread's reply count and last activity parentThread, _ := forum.Find(db, parentID) parentThread.IncrementReplies() parentThread.UpdateLastPost() parentThread.Save() } // When deleting a reply replyToDelete, _ := forum.Find(db, replyID) parentID := replyToDelete.Parent replyToDelete.Delete() // Update parent thread if parentID > 0 { parentThread, _ := forum.Find(db, parentID) parentThread.DecrementReplies() parentThread.Save() } ## Content Analysis Analyze post content: post, _ := forum.Find(db, postID) // Basic content metrics fmt.Printf("Post length: %d characters\n", post.Length()) fmt.Printf("Word count: %d words\n", post.WordCount()) // Check for specific terms if post.Contains("bug") { fmt.Println("This post mentions a bug") } // Generate preview for listings preview := post.Preview(100) fmt.Printf("Preview: %s\n", preview) ## Time Analysis Track posting and activity patterns: post, _ := forum.Find(db, postID) postAge := post.PostAge() activityAge := post.ActivityAge() fmt.Printf("Post created %v ago\n", postAge) fmt.Printf("Last activity %v ago\n", activityAge) if post.IsRecentActivity() { fmt.Println("This thread has recent activity") } # Forum Display Patterns ## Thread Listing Display forum index: func displayForumIndex(db *database.DB) { threads, _ := forum.Threads(db) fmt.Println("=== Game Forum ===") fmt.Printf("%-40s %-8s %-15s\n", "Thread", "Replies", "Last Activity") fmt.Println(strings.Repeat("-", 65)) for _, thread := range threads { title := thread.Title if len(title) > 37 { title = title[:37] + "..." } age := thread.ActivityAge() var ageStr string if age < time.Hour { ageStr = fmt.Sprintf("%dm ago", int(age.Minutes())) } else if age < 24*time.Hour { ageStr = fmt.Sprintf("%dh ago", int(age.Hours())) } else { ageStr = fmt.Sprintf("%dd ago", int(age.Hours()/24)) } fmt.Printf("%-40s %-8d %-15s\n", title, thread.Replies, ageStr) } } ## Thread View Display a complete thread with replies: func displayThread(db *database.DB, threadID int) error { thread, err := forum.Find(db, threadID) if err != nil { return err } if !thread.IsThread() { return fmt.Errorf("post %d is not a thread", threadID) } // Display thread fmt.Printf("=== %s ===\n", thread.Title) fmt.Printf("By user %d on %s\n\n", thread.Author, thread.PostedTime().Format("January 2, 2006 at 3:04 PM")) fmt.Println(thread.Content) // Display replies replies, _ := thread.GetReplies() if len(replies) > 0 { fmt.Printf("\n--- %d Replies ---\n\n", len(replies)) for i, reply := range replies { fmt.Printf("#%d by user %d on %s:\n", i+1, reply.Author, reply.PostedTime().Format("Jan 2 at 3:04 PM")) fmt.Println(reply.Content) fmt.Println() } } return nil } # Moderation Features ## Content Moderation Tools for forum moderation: // Flag posts for review func moderatePost(db *database.DB, postID int) { post, err := forum.Find(db, postID) if err != nil { return } // Check for very short posts (potential spam) if post.WordCount() < 3 { fmt.Printf("Short post flagged: %s\n", post.Preview(30)) } // Check for very long posts if post.Length() > 5000 { fmt.Printf("Very long post from user %d\n", post.Author) } // Check for specific terms suspiciousTerms := []string{"spam", "hack", "cheat"} for _, term := range suspiciousTerms { if post.Contains(term) { fmt.Printf("Post contains suspicious term '%s'\n", term) } } } ## User Activity Analysis Analyze user forum behavior: func analyzeUserActivity(db *database.DB, userID int) { posts, _ := forum.ByAuthor(db, userID) fmt.Printf("User %d forum activity:\n", userID) fmt.Printf("- Total posts: %d\n", len(posts)) threadCount := 0 replyCount := 0 totalWords := 0 for _, post := range posts { if post.IsThread() { threadCount++ } else { replyCount++ } totalWords += post.WordCount() } fmt.Printf("- Threads started: %d\n", threadCount) fmt.Printf("- Replies posted: %d\n", replyCount) if len(posts) > 0 { avgWords := totalWords / len(posts) fmt.Printf("- Average words per post: %d\n", avgWords) latest := posts[0] // ByAuthor returns newest first fmt.Printf("- Last post: %s (%s ago)\n", latest.Title, latest.PostAge()) } } # Performance Considerations ## Efficient Queries Optimize database queries for forum performance: // Use specific queries for common operations threads, _ := forum.Threads(db) // More efficient than filtering All() // Limit results for pagination recentPosts, _ := forum.Recent(db, 25) // Get page worth of data // Use specific parent queries replies, _ := forum.ByParent(db, threadID) // Efficient for thread display ## Caching Strategies Cache frequently accessed data: // Cache popular threads var popularThreadsCache []*forum.Forum var cacheTime time.Time func getPopularThreads(db *database.DB) []*forum.Forum { // Refresh cache every 5 minutes if time.Since(cacheTime) > 5*time.Minute { threads, _ := forum.Threads(db) // Sort by activity (replies + recent posts) sort.Slice(threads, func(i, j int) bool { scoreI := threads[i].Replies scoreJ := threads[j].Replies // Bonus for recent activity if threads[i].IsRecentActivity() { scoreI += 10 } if threads[j].IsRecentActivity() { scoreJ += 10 } return scoreI > scoreJ }) // Cache top 10 if len(threads) > 10 { threads = threads[:10] } popularThreadsCache = threads cacheTime = time.Now() } return popularThreadsCache } # Integration Examples ## User Notifications Integrate with notification system: func notifyNewReply(db *database.DB, replyID int) { reply, err := forum.Find(db, replyID) if err != nil { return } // Get the parent thread thread, err := reply.GetThread() if err != nil { return } // Notify thread author if different from reply author if thread.Author != reply.Author { message := fmt.Sprintf("New reply to your thread '%s'", thread.Title) sendNotification(thread.Author, message) } // Notify other participants in the thread allReplies, _ := thread.GetReplies() participants := make(map[int]bool) participants[thread.Author] = true for _, r := range allReplies { if r.Author != reply.Author && !participants[r.Author] { message := fmt.Sprintf("New activity in thread '%s'", thread.Title) sendNotification(r.Author, message) participants[r.Author] = true } } } ## Search Integration Provide advanced search capabilities: func advancedSearch(db *database.DB, query string, authorID int, onlyThreads bool, since time.Time) []*forum.Forum { var results []*forum.Forum // Start with text search if query != "" { textResults, _ := forum.Search(db, query) results = append(results, textResults...) } else { allPosts, _ := forum.All(db) results = allPosts } // Apply filters var filtered []*forum.Forum sinceUnix := since.Unix() for _, post := range results { // Author filter if authorID > 0 && post.Author != authorID { continue } // Thread-only filter if onlyThreads && !post.IsThread() { continue } // Time filter if post.LastPost < sinceUnix { continue } filtered = append(filtered, post) } return filtered } # Error Handling Common error scenarios and handling: post, err := forum.Find(db, postID) if err != nil { // Handle post not found or database issues log.Printf("Failed to find post %d: %v", postID, err) return } // Validate post relationships if post.IsReply() { parentThread, err := post.GetThread() if err != nil { log.Printf("Warning: Reply %d has invalid parent %d", post.ID, post.Parent) } } // Save with error handling if err := post.Save(); err != nil { log.Printf("Failed to save post %d: %v", post.ID, err) } The forum package provides a complete forum system with hierarchical discussions, search capabilities, and comprehensive post management suitable for game communities. */ package forum