282 lines
6.9 KiB
Go
282 lines
6.9 KiB
Go
package routes
|
|
|
|
import (
|
|
"dk/internal/components"
|
|
"dk/internal/database"
|
|
"dk/internal/helpers/markdown"
|
|
"dk/internal/models/forum"
|
|
"dk/internal/models/users"
|
|
"fmt"
|
|
"strings"
|
|
|
|
sushi "git.sharkk.net/Sharkk/Sushi"
|
|
"git.sharkk.net/Sharkk/Sushi/auth"
|
|
)
|
|
|
|
// ThreadInfo combines forum thread with author and last reply info
|
|
type ThreadInfo struct {
|
|
Thread *forum.Forum
|
|
Author *users.User
|
|
AuthorClass string
|
|
LastReplyBy *users.User
|
|
}
|
|
|
|
// PostInfo combines a forum post/reply with its author
|
|
type PostInfo struct {
|
|
Post *forum.Forum
|
|
Author *users.User
|
|
AuthorClass string
|
|
Content string // Pre-processed markdown content
|
|
}
|
|
|
|
func RegisterForumRoutes(app *sushi.App) {
|
|
authed := app.Group("/forum")
|
|
authed.Use(auth.RequireAuth())
|
|
authed.Get("/", index)
|
|
authed.Get("/new", showNew)
|
|
authed.Post("/new", new)
|
|
authed.Get("/:id", showThread)
|
|
authed.Get("/:id/reply", showReply)
|
|
authed.Post("/:id/reply", reply)
|
|
}
|
|
|
|
func index(ctx sushi.Ctx) {
|
|
page := int(ctx.QueryArgs().GetUintOrZero("page"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
|
|
perPage := 30
|
|
offset := (page - 1) * perPage
|
|
|
|
// Get threads with pagination
|
|
var threads []*forum.Forum
|
|
err := database.Select(&threads, "SELECT * FROM forum WHERE parent = 0 ORDER BY last_post DESC, id DESC LIMIT %d OFFSET %d", perPage, offset)
|
|
if err != nil {
|
|
threads = make([]*forum.Forum, 0)
|
|
}
|
|
|
|
// Get total count for pagination
|
|
var totalCount int
|
|
database.Get(&totalCount, "SELECT COUNT(*) FROM forum WHERE parent = 0")
|
|
|
|
// Build thread info with authors and last reply info
|
|
var threadInfos []ThreadInfo
|
|
for _, thread := range threads {
|
|
author, _ := users.Find(thread.Author)
|
|
if author == nil {
|
|
author = &users.User{Username: "[Deleted]", ClassID: 1}
|
|
}
|
|
|
|
authorClass := author.Class().Name
|
|
|
|
// Get last reply author if there are replies
|
|
var lastReplyBy *users.User
|
|
if thread.Replies > 0 {
|
|
var lastReply forum.Forum
|
|
err := database.Get(&lastReply, "SELECT * FROM forum WHERE parent = %d ORDER BY posted DESC LIMIT 1", thread.ID)
|
|
if err == nil {
|
|
lastReplyBy, _ = users.Find(lastReply.Author)
|
|
}
|
|
}
|
|
|
|
threadInfos = append(threadInfos, ThreadInfo{
|
|
Thread: thread,
|
|
Author: author,
|
|
AuthorClass: authorClass,
|
|
LastReplyBy: lastReplyBy,
|
|
})
|
|
}
|
|
|
|
totalPages := (totalCount + perPage - 1) / perPage
|
|
|
|
components.RenderPage(ctx, "Forum", "forum/index.html", map[string]any{
|
|
"threadInfos": threadInfos,
|
|
"currentPage": page,
|
|
"totalPages": totalPages,
|
|
"hasNext": page < totalPages,
|
|
"hasPrev": page > 1,
|
|
})
|
|
}
|
|
|
|
func showNew(ctx sushi.Ctx) {
|
|
components.RenderPage(ctx, "New Forum Thread", "forum/new.html", map[string]any{})
|
|
}
|
|
|
|
func new(ctx sushi.Ctx) {
|
|
sess := ctx.GetCurrentSession()
|
|
|
|
title := strings.TrimSpace(ctx.Form("title").String())
|
|
content := strings.TrimSpace(ctx.Form("content").String())
|
|
|
|
if title == "" {
|
|
sess.SetFlash("error", "Thread title cannot be empty")
|
|
ctx.Redirect("/forum/new")
|
|
return
|
|
}
|
|
|
|
if content == "" {
|
|
sess.SetFlash("error", "Thread content cannot be empty")
|
|
ctx.Redirect("/forum/new")
|
|
return
|
|
}
|
|
|
|
user := ctx.GetCurrentUser().(*users.User)
|
|
|
|
thread := forum.New()
|
|
thread.Author = user.ID
|
|
thread.Title = title
|
|
thread.Content = content
|
|
|
|
database.Transaction(func() error {
|
|
return thread.Insert()
|
|
})
|
|
|
|
ctx.Redirect(fmt.Sprintf("/forum/%d", thread.ID))
|
|
}
|
|
|
|
func showThread(ctx sushi.Ctx) {
|
|
sess := ctx.GetCurrentSession()
|
|
id := ctx.Param("id").Int()
|
|
page := int(ctx.QueryArgs().GetUintOrZero("page"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
|
|
thread, err := forum.Find(id)
|
|
if err != nil {
|
|
sess.SetFlash("error", fmt.Sprintf("Forum thread %d not found", id))
|
|
ctx.Redirect("/forum")
|
|
return
|
|
}
|
|
|
|
if thread.Parent != 0 {
|
|
sess.SetFlash("error", fmt.Sprintf("Forum post %d is not a thread", id))
|
|
ctx.Redirect("/forum")
|
|
return
|
|
}
|
|
|
|
// Get thread author
|
|
threadAuthor, _ := users.Find(thread.Author)
|
|
if threadAuthor == nil {
|
|
threadAuthor = &users.User{Username: "[Deleted]", Level: 1, ClassID: 1}
|
|
}
|
|
threadAuthorClass := threadAuthor.Class().Name
|
|
threadContent := markdown.MarkdownToHTML(thread.Content)
|
|
|
|
// Get replies with pagination
|
|
perPage := 30
|
|
offset := (page - 1) * perPage
|
|
|
|
var replies []*forum.Forum
|
|
err = database.Select(&replies, "SELECT * FROM forum WHERE parent = %d ORDER BY posted ASC LIMIT %d OFFSET %d", id, perPage, offset)
|
|
if err != nil {
|
|
replies = make([]*forum.Forum, 0)
|
|
}
|
|
|
|
// Build reply info with authors and processed content
|
|
var replyInfos []PostInfo
|
|
for _, reply := range replies {
|
|
author, _ := users.Find(reply.Author)
|
|
if author == nil {
|
|
author = &users.User{Username: "[Deleted]", Level: 1, ClassID: 1}
|
|
}
|
|
authorClass := author.Class().Name
|
|
content := markdown.MarkdownToHTML(reply.Content)
|
|
|
|
replyInfos = append(replyInfos, PostInfo{
|
|
Post: reply,
|
|
Author: author,
|
|
AuthorClass: authorClass,
|
|
Content: content,
|
|
})
|
|
}
|
|
|
|
totalPages := (thread.Replies + perPage - 1) / perPage
|
|
if totalPages < 1 {
|
|
totalPages = 1
|
|
}
|
|
|
|
components.RenderPage(ctx, thread.Title, "forum/thread.html", map[string]any{
|
|
"thread": thread,
|
|
"threadContent": threadContent,
|
|
"threadAuthor": threadAuthor,
|
|
"threadAuthorClass": threadAuthorClass,
|
|
"replyInfos": replyInfos,
|
|
"currentPage": page,
|
|
"totalPages": totalPages,
|
|
"hasNext": page < totalPages,
|
|
"hasPrev": page > 1,
|
|
})
|
|
}
|
|
|
|
func showReply(ctx sushi.Ctx) {
|
|
sess := ctx.GetCurrentSession()
|
|
id := ctx.Param("id").Int()
|
|
|
|
thread, err := forum.Find(id)
|
|
if err != nil {
|
|
sess.SetFlash("error", fmt.Sprintf("Forum thread %d not found", id))
|
|
ctx.Redirect("/forum")
|
|
return
|
|
}
|
|
|
|
if thread.Parent != 0 {
|
|
sess.SetFlash("error", fmt.Sprintf("Forum post %d is not a thread", id))
|
|
ctx.Redirect("/forum")
|
|
return
|
|
}
|
|
|
|
components.RenderPage(ctx, "Reply to "+thread.Title, "forum/reply.html", map[string]any{
|
|
"thread": thread,
|
|
})
|
|
}
|
|
|
|
func reply(ctx sushi.Ctx) {
|
|
sess := ctx.GetCurrentSession()
|
|
id := ctx.Param("id").Int()
|
|
|
|
thread, err := forum.Find(id)
|
|
if err != nil {
|
|
sess.SetFlash("error", fmt.Sprintf("Forum thread %d not found", id))
|
|
ctx.Redirect("/forum")
|
|
return
|
|
}
|
|
|
|
if thread.Parent != 0 {
|
|
sess.SetFlash("error", fmt.Sprintf("Forum post %d is not a thread", id))
|
|
ctx.Redirect("/forum")
|
|
return
|
|
}
|
|
|
|
content := strings.TrimSpace(ctx.Form("content").String())
|
|
if content == "" {
|
|
sess.SetFlash("error", "Reply content cannot be empty")
|
|
ctx.Redirect(fmt.Sprintf("/forum/%d/reply", id))
|
|
return
|
|
}
|
|
|
|
user := ctx.GetCurrentUser().(*users.User)
|
|
|
|
reply := forum.New()
|
|
reply.Author = user.ID
|
|
reply.Parent = thread.ID
|
|
reply.Title = "Re: " + thread.Title
|
|
reply.Content = content
|
|
|
|
database.Transaction(func() error {
|
|
if err := reply.Insert(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update thread reply count and last post time
|
|
thread.IncrementReplies()
|
|
thread.UpdateLastPost()
|
|
|
|
return database.Exec("UPDATE forum SET replies = %d, last_post = %d WHERE id = %d",
|
|
thread.Replies, thread.LastPost, thread.ID)
|
|
})
|
|
|
|
ctx.Redirect(fmt.Sprintf("/forum/%d", thread.ID))
|
|
}
|