diff --git a/src/templates/src/forum_category.html b/src/templates/src/forum_category.html index 5921b407..cf5dc121 100644 --- a/src/templates/src/forum_category.html +++ b/src/templates/src/forum_category.html @@ -2,21 +2,46 @@ {{ define "content" }}
-
-
- {{ if .User }} - + New Thread - Mark threads here as read - {{ else }} - Log in to post a new thread + {{ range .Subcategories }} +
+

+ + {{ .Name }} »
+
+

+ {{ range .Threads }} + {{ template "thread_list_item.html" . }} + {{ end }} + {{ $more := sub .TotalThreads 3 }} + {{ if gt $more 0 }} + {{ end }}
-
- {{ template "pagination.html" .Pagination }} -
+ {{ end }} +
+ {{ template "forum_category_options" . }}
{{ range .Threads }} {{ template "thread_list_item.html" . }} {{ end }} +
+ {{ template "forum_category_options" . }} +
{{ end }} + +{{ define "forum_category_options" }} +
+ {{ if .User }} + + New Thread + Mark threads here as read + {{ else }} + Log in to post a new thread + {{ end }} +
+
+ {{ template "pagination.html" .Pagination }} +
+{{ end }} \ No newline at end of file diff --git a/src/website/forums.go b/src/website/forums.go index 1064e77f..8397b2c7 100644 --- a/src/website/forums.go +++ b/src/website/forums.go @@ -9,22 +9,28 @@ import ( "strings" "time" - "git.handmade.network/hmn/hmn/src/hmnurl" - "git.handmade.network/hmn/hmn/src/templates" - - "github.com/jackc/pgx/v4/pgxpool" - "git.handmade.network/hmn/hmn/src/db" + "git.handmade.network/hmn/hmn/src/hmnurl" "git.handmade.network/hmn/hmn/src/models" "git.handmade.network/hmn/hmn/src/oops" + "git.handmade.network/hmn/hmn/src/templates" + "github.com/jackc/pgx/v4/pgxpool" ) type forumCategoryData struct { templates.BaseData - CategoryUrl string - Threads []templates.ThreadListItem - Pagination templates.Pagination + CategoryUrl string + Threads []templates.ThreadListItem + Pagination templates.Pagination + Subcategories []forumSubcategoryData +} + +type forumSubcategoryData struct { + Name string + Url string + Threads []templates.ThreadListItem + TotalThreads int } func ForumCategory(c *RequestContext) ResponseData { @@ -35,6 +41,7 @@ func ForumCategory(c *RequestContext) ResponseData { currentCatId := fetchCatIdFromSlugs(c.Context(), c.Conn, catSlugs, c.CurrentProject.ID) categoryUrls := GetProjectCategoryUrls(c.Context(), c.Conn, c.CurrentProject.ID) + c.Perf.StartBlock("SQL", "Fetch count of page threads") numThreads, err := db.QueryInt(c.Context(), c.Conn, ` SELECT COUNT(*) @@ -48,6 +55,7 @@ func ForumCategory(c *RequestContext) ResponseData { if err != nil { panic(oops.New(err, "failed to get count of threads")) } + c.Perf.EndBlock() numPages := int(math.Ceil(float64(numThreads) / threadsPerPage)) @@ -71,7 +79,8 @@ func ForumCategory(c *RequestContext) ResponseData { currentUserId = &c.CurrentUser.ID } - type mainPostsQueryResult struct { + c.Perf.StartBlock("SQL", "Fetch page threads") + type threadQueryResult struct { Thread models.Thread `db:"thread"` FirstPost models.Post `db:"firstpost"` LastPost models.Post `db:"lastpost"` @@ -80,7 +89,7 @@ func ForumCategory(c *RequestContext) ResponseData { ThreadLastReadTime *time.Time `db:"tlri.lastread"` CatLastReadTime *time.Time `db:"clri.lastread"` } - itMainThreads, err := db.Query(c.Context(), c.Conn, mainPostsQueryResult{}, + itMainThreads, err := db.Query(c.Context(), c.Conn, threadQueryResult{}, ` SELECT $columns FROM @@ -111,12 +120,10 @@ func ForumCategory(c *RequestContext) ResponseData { if err != nil { panic(oops.New(err, "failed to fetch threads")) } + c.Perf.EndBlock() defer itMainThreads.Close() - var threads []templates.ThreadListItem - for _, irow := range itMainThreads.ToSlice() { - row := irow.(*mainPostsQueryResult) - + makeThreadListItem := func(row *threadQueryResult) templates.ThreadListItem { hasRead := false if row.ThreadLastReadTime != nil && row.ThreadLastReadTime.After(row.LastPost.PostDate) { hasRead = true @@ -124,7 +131,7 @@ func ForumCategory(c *RequestContext) ResponseData { hasRead = true } - threads = append(threads, templates.ThreadListItem{ + return templates.ThreadListItem{ Title: row.Thread.Title, Url: ThreadUrl(row.Thread, models.CatKindForum, categoryUrls[currentCatId]), @@ -134,45 +141,111 @@ func ForumCategory(c *RequestContext) ResponseData { LastDate: row.LastPost.PostDate, Unread: !hasRead, - }) + } + } + + var threads []templates.ThreadListItem + for _, irow := range itMainThreads.ToSlice() { + row := irow.(*threadQueryResult) + threads = append(threads, makeThreadListItem(row)) } // --------------------- // Subcategory things // --------------------- - //c.Perf.StartBlock("SQL", "Fetch subcategories") - //type queryResult struct { - // Cat models.Category `db:"cat"` - //} - //itSubcats, err := db.Query(c.Context(), c.Conn, queryResult{}, - // ` - // WITH current AS ( - // SELECT id - // FROM handmade_category - // WHERE - // slug = $1 - // AND kind = $2 - // AND project_id = $3 - // ) - // SELECT $columns - // FROM - // handmade_category AS cat, - // current - // WHERE - // cat.id = current.id - // OR cat.parent_id = current.id - // `, - // catSlug, - // models.CatKindForum, - // c.CurrentProject.ID, - //) - //if err != nil { - // panic(oops.New(err, "failed to fetch subcategories")) - //} - //c.Perf.EndBlock() + var subcats []forumSubcategoryData + if page == 1 { + c.Perf.StartBlock("SQL", "Fetch subcategories") + type subcatQueryResult struct { + Cat models.Category `db:"cat"` + } + itSubcats, err := db.Query(c.Context(), c.Conn, subcatQueryResult{}, + ` + SELECT $columns + FROM + handmade_category AS cat + WHERE + cat.parent_id = $1 + `, + currentCatId, + ) + if err != nil { + panic(oops.New(err, "failed to fetch subcategories")) + } + defer itSubcats.Close() + c.Perf.EndBlock() - //_ = itSubcats // TODO: Actually query subcategory post data + for _, irow := range itSubcats.ToSlice() { + catRow := irow.(*subcatQueryResult) + + c.Perf.StartBlock("SQL", "Fetch count of subcategory threads") + numThreads, err := db.QueryInt(c.Context(), c.Conn, + ` + SELECT COUNT(*) + FROM handmade_thread AS thread + WHERE + thread.category_id = $1 + AND NOT thread.deleted + `, + catRow.Cat.ID, + ) + if err != nil { + panic(oops.New(err, "failed to get count of threads")) + } + c.Perf.EndBlock() + + c.Perf.StartBlock("SQL", "Fetch subcategory threads") + itThreads, err := db.Query(c.Context(), c.Conn, threadQueryResult{}, + ` + SELECT $columns + FROM + handmade_thread AS thread + JOIN handmade_post AS firstpost ON thread.first_id = firstpost.id + JOIN handmade_post AS lastpost ON thread.last_id = lastpost.id + LEFT JOIN auth_user AS firstuser ON firstpost.author_id = firstuser.id + LEFT JOIN auth_user AS lastuser ON lastpost.author_id = lastuser.id + LEFT JOIN handmade_threadlastreadinfo AS tlri ON ( + tlri.thread_id = thread.id + AND tlri.user_id = $2 + ) + LEFT JOIN handmade_categorylastreadinfo AS clri ON ( + clri.category_id = $1 + AND clri.user_id = $2 + ) + WHERE + thread.category_id = $1 + AND NOT thread.deleted + ORDER BY lastpost.postdate DESC + LIMIT 3 + `, + catRow.Cat.ID, + currentUserId, + ) + if err != nil { + panic(err) + } + defer itThreads.Close() + c.Perf.EndBlock() + + var threads []templates.ThreadListItem + for _, irow := range itThreads.ToSlice() { + threadRow := irow.(*threadQueryResult) + threads = append(threads, makeThreadListItem(threadRow)) + } + + subcats = append(subcats, forumSubcategoryData{ + Name: *catRow.Cat.Name, + Url: categoryUrls[catRow.Cat.ID], + Threads: threads, + TotalThreads: numThreads, + }) + } + } + + // --------------------- + // Template assembly + // --------------------- baseData := getBaseData(c) baseData.Title = *c.CurrentProject.Name + " Forums" @@ -202,6 +275,7 @@ func ForumCategory(c *RequestContext) ResponseData { NextUrl: fmt.Sprintf("%s/%d", categoryUrls[currentCatId], page+1), PreviousUrl: fmt.Sprintf("%s/%d", categoryUrls[currentCatId], page-1), }, + Subcategories: subcats, }, c.Perf) if err != nil { panic(err) diff --git a/src/website/routes.go b/src/website/routes.go index cf5b84da..f13b6b69 100644 --- a/src/website/routes.go +++ b/src/website/routes.go @@ -73,7 +73,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt }) mainRoutes.GET(`^/feed(/(?P.+)?)?$`, Feed) - mainRoutes.GET(`^/(?Pforums(/[a-zA-Z]+?)*)(/(?P\d+))?$`, ForumCategory) + mainRoutes.GET(`^/(?Pforums(/[^\d]+?)*)(/(?P\d+))?$`, ForumCategory) // mainRoutes.GET(`^/(?Pforums(/cat)*)/t/(?P\d+)/p/(?P\d+)$`, ForumPost) mainRoutes.GET("^/assets/project.css$", ProjectCSS)