2021-04-29 04:52:27 +00:00
package website
import (
2021-08-28 17:07:45 +00:00
"context"
2021-07-20 02:35:22 +00:00
"fmt"
2021-04-29 04:52:27 +00:00
"net/http"
"strconv"
"strings"
"time"
"git.handmade.network/hmn/hmn/src/db"
2021-05-03 23:59:43 +00:00
"git.handmade.network/hmn/hmn/src/hmnurl"
2021-08-28 17:07:45 +00:00
"git.handmade.network/hmn/hmn/src/logging"
2021-04-29 04:52:27 +00:00
"git.handmade.network/hmn/hmn/src/models"
"git.handmade.network/hmn/hmn/src/oops"
2021-05-03 23:59:43 +00:00
"git.handmade.network/hmn/hmn/src/templates"
2021-05-06 05:57:14 +00:00
"git.handmade.network/hmn/hmn/src/utils"
2021-04-29 04:52:27 +00:00
)
2021-07-30 03:40:47 +00:00
type forumData struct {
2021-05-03 14:51:07 +00:00
templates . BaseData
2021-07-30 03:40:47 +00:00
NewThreadUrl string
MarkReadUrl string
Threads [ ] templates . ThreadListItem
Pagination templates . Pagination
Subforums [ ] forumSubforumData
2021-05-03 23:59:43 +00:00
}
2021-07-30 03:40:47 +00:00
type forumSubforumData struct {
2021-05-03 23:59:43 +00:00
Name string
Url string
Threads [ ] templates . ThreadListItem
TotalThreads int
2021-05-03 14:51:07 +00:00
}
2021-07-30 22:32:19 +00:00
type editActionType string
2021-07-20 02:35:22 +00:00
type editorData struct {
templates . BaseData
SubmitUrl string
SubmitLabel string
2021-07-30 22:32:19 +00:00
// The following are filled out automatically by the
// getEditorDataFor* functions.
Title string
CanEditTitle bool
IsEditing bool
2021-07-22 01:41:23 +00:00
EditInitialContents string
2021-07-30 22:32:19 +00:00
PostReplyingTo * templates . Post
}
2021-07-22 01:41:23 +00:00
2021-07-30 22:32:19 +00:00
func getEditorDataForNew ( baseData templates . BaseData , replyPost * templates . Post ) editorData {
result := editorData {
BaseData : baseData ,
CanEditTitle : replyPost == nil ,
PostReplyingTo : replyPost ,
}
if replyPost != nil {
result . Title = "Replying to post"
}
return result
}
func getEditorDataForEdit ( baseData templates . BaseData , p postAndRelatedModels ) editorData {
return editorData {
BaseData : baseData ,
Title : p . Thread . Title ,
CanEditTitle : p . Thread . FirstID == p . Post . ID ,
IsEditing : true ,
EditInitialContents : p . CurrentVersion . TextRaw ,
}
2021-07-20 02:35:22 +00:00
}
2021-07-30 03:40:47 +00:00
func Forum ( c * RequestContext ) ResponseData {
2021-04-29 04:52:27 +00:00
const threadsPerPage = 25
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-05-11 22:53:23 +00:00
return FourOhFour ( c )
}
2021-07-30 03:40:47 +00:00
currentSubforumSlugs := cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID )
2021-04-29 04:52:27 +00:00
2021-05-03 23:59:43 +00:00
c . Perf . StartBlock ( "SQL" , "Fetch count of page threads" )
2021-04-29 04:52:27 +00:00
numThreads , err := db . QueryInt ( c . Context ( ) , c . Conn ,
`
SELECT COUNT ( * )
FROM handmade_thread AS thread
WHERE
2021-07-30 03:40:47 +00:00
thread . subforum_id = $ 1
2021-04-29 04:52:27 +00:00
AND NOT thread . deleted
` ,
2021-07-30 03:40:47 +00:00
cd . SubforumID ,
2021-04-29 04:52:27 +00:00
)
if err != nil {
panic ( oops . New ( err , "failed to get count of threads" ) )
}
2021-05-03 23:59:43 +00:00
c . Perf . EndBlock ( )
2021-04-29 04:52:27 +00:00
2021-09-01 18:25:09 +00:00
numPages := utils . NumPages ( numThreads , threadsPerPage )
2021-08-03 01:52:46 +00:00
page , ok := ParsePageNumber ( c , "page" , numPages )
if ! ok {
c . Redirect ( hmnurl . BuildForum ( c . CurrentProject . Slug , currentSubforumSlugs , page ) , http . StatusSeeOther )
2021-04-29 04:52:27 +00:00
}
howManyThreadsToSkip := ( page - 1 ) * threadsPerPage
var currentUserId * int
if c . CurrentUser != nil {
currentUserId = & c . CurrentUser . ID
}
2021-05-03 23:59:43 +00:00
c . Perf . StartBlock ( "SQL" , "Fetch page threads" )
type threadQueryResult struct {
2021-04-29 04:52:27 +00:00
Thread models . Thread ` db:"thread" `
FirstPost models . Post ` db:"firstpost" `
LastPost models . Post ` db:"lastpost" `
2021-05-03 14:51:07 +00:00
FirstUser * models . User ` db:"firstuser" `
LastUser * models . User ` db:"lastuser" `
2021-04-29 04:52:27 +00:00
ThreadLastReadTime * time . Time ` db:"tlri.lastread" `
2021-07-30 03:40:47 +00:00
ForumLastReadTime * time . Time ` db:"slri.lastread" `
2021-04-29 04:52:27 +00:00
}
2021-05-03 23:59:43 +00:00
itMainThreads , err := db . Query ( c . Context ( ) , c . Conn , threadQueryResult { } ,
2021-04-29 04:52:27 +00:00
`
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
2021-05-03 14:51:07 +00:00
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 (
2021-04-29 04:52:27 +00:00
tlri . thread_id = thread . id
AND tlri . user_id = $ 2
)
2021-07-30 03:40:47 +00:00
LEFT JOIN handmade_subforumlastreadinfo AS slri ON (
slri . subforum_id = $ 1
AND slri . user_id = $ 2
2021-04-29 04:52:27 +00:00
)
WHERE
2021-07-30 03:40:47 +00:00
thread . subforum_id = $ 1
2021-04-29 04:52:27 +00:00
AND NOT thread . deleted
ORDER BY lastpost . postdate DESC
LIMIT $ 3 OFFSET $ 4
` ,
2021-07-30 03:40:47 +00:00
cd . SubforumID ,
2021-04-29 04:52:27 +00:00
currentUserId ,
threadsPerPage ,
howManyThreadsToSkip ,
)
if err != nil {
panic ( oops . New ( err , "failed to fetch threads" ) )
}
2021-05-03 23:59:43 +00:00
c . Perf . EndBlock ( )
2021-05-03 14:51:07 +00:00
defer itMainThreads . Close ( )
2021-04-29 04:52:27 +00:00
2021-05-03 23:59:43 +00:00
makeThreadListItem := func ( row * threadQueryResult ) templates . ThreadListItem {
2021-05-03 22:53:28 +00:00
hasRead := false
if row . ThreadLastReadTime != nil && row . ThreadLastReadTime . After ( row . LastPost . PostDate ) {
hasRead = true
2021-07-30 03:40:47 +00:00
} else if row . ForumLastReadTime != nil && row . ForumLastReadTime . After ( row . LastPost . PostDate ) {
2021-05-03 22:53:28 +00:00
hasRead = true
}
2021-05-03 23:59:43 +00:00
return templates . ThreadListItem {
2021-05-11 22:53:23 +00:00
Title : row . Thread . Title ,
2021-07-30 03:40:47 +00:00
Url : hmnurl . BuildForumThread ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( * row . Thread . SubforumID ) , row . Thread . ID , row . Thread . Title , 1 ) ,
2021-05-25 13:12:20 +00:00
FirstUser : templates . UserToTemplate ( row . FirstUser , c . Theme ) ,
2021-05-03 14:51:07 +00:00
FirstDate : row . FirstPost . PostDate ,
2021-05-25 13:12:20 +00:00
LastUser : templates . UserToTemplate ( row . LastUser , c . Theme ) ,
2021-05-03 14:51:07 +00:00
LastDate : row . LastPost . PostDate ,
2021-05-03 22:53:28 +00:00
Unread : ! hasRead ,
2021-05-03 23:59:43 +00:00
}
}
var threads [ ] templates . ThreadListItem
for _ , irow := range itMainThreads . ToSlice ( ) {
row := irow . ( * threadQueryResult )
threads = append ( threads , makeThreadListItem ( row ) )
2021-04-29 04:52:27 +00:00
}
// ---------------------
2021-07-30 03:40:47 +00:00
// Subforum things
2021-04-29 04:52:27 +00:00
// ---------------------
2021-07-30 03:40:47 +00:00
var subforums [ ] forumSubforumData
2021-05-03 23:59:43 +00:00
if page == 1 {
2021-07-30 03:40:47 +00:00
subforumNodes := cd . SubforumTree [ cd . SubforumID ] . Children
2021-05-03 23:59:43 +00:00
2021-07-30 03:40:47 +00:00
for _ , sfNode := range subforumNodes {
c . Perf . StartBlock ( "SQL" , "Fetch count of subforum threads" )
2021-05-03 23:59:43 +00:00
numThreads , err := db . QueryInt ( c . Context ( ) , c . Conn ,
`
SELECT COUNT ( * )
FROM handmade_thread AS thread
WHERE
2021-07-30 03:40:47 +00:00
thread . subforum_id = $ 1
2021-05-03 23:59:43 +00:00
AND NOT thread . deleted
` ,
2021-07-30 03:40:47 +00:00
sfNode . ID ,
2021-05-03 23:59:43 +00:00
)
if err != nil {
panic ( oops . New ( err , "failed to get count of threads" ) )
}
c . Perf . EndBlock ( )
2021-07-30 03:40:47 +00:00
c . Perf . StartBlock ( "SQL" , "Fetch subforum threads" )
2021-05-03 23:59:43 +00:00
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
)
2021-07-30 03:40:47 +00:00
LEFT JOIN handmade_subforumlastreadinfo AS slri ON (
slri . subforum_id = $ 1
AND slri . user_id = $ 2
2021-05-03 23:59:43 +00:00
)
WHERE
2021-07-30 03:40:47 +00:00
thread . subforum_id = $ 1
2021-05-03 23:59:43 +00:00
AND NOT thread . deleted
ORDER BY lastpost . postdate DESC
LIMIT 3
` ,
2021-07-30 03:40:47 +00:00
sfNode . ID ,
2021-05-03 23:59:43 +00:00
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 ) )
}
2021-07-30 03:40:47 +00:00
subforums = append ( subforums , forumSubforumData {
Name : sfNode . Name ,
Url : hmnurl . BuildForum ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( sfNode . ID ) , 1 ) ,
2021-05-03 23:59:43 +00:00
Threads : threads ,
TotalThreads : numThreads ,
} )
}
}
// ---------------------
// Template assembly
// ---------------------
2021-05-03 14:51:07 +00:00
2021-09-01 18:25:09 +00:00
baseData := getBaseData (
c ,
fmt . Sprintf ( "%s Forums" , c . CurrentProject . Name ) ,
SubforumBreadcrumbs ( cd . LineageBuilder , c . CurrentProject , cd . SubforumID ) ,
)
2021-05-11 22:53:23 +00:00
2021-05-03 14:51:07 +00:00
var res ResponseData
2021-07-30 03:40:47 +00:00
res . MustWriteTemplate ( "forum.html" , forumData {
2021-05-11 22:53:23 +00:00
BaseData : baseData ,
2021-06-12 03:51:07 +00:00
NewThreadUrl : hmnurl . BuildForumNewThread ( c . CurrentProject . Slug , currentSubforumSlugs , false ) ,
2021-08-28 17:07:45 +00:00
MarkReadUrl : hmnurl . BuildForumMarkRead ( c . CurrentProject . Slug , cd . SubforumID ) ,
2021-05-11 22:53:23 +00:00
Threads : threads ,
2021-05-03 22:45:17 +00:00
Pagination : templates . Pagination {
Current : page ,
Total : numPages ,
2021-07-30 03:40:47 +00:00
FirstUrl : hmnurl . BuildForum ( c . CurrentProject . Slug , currentSubforumSlugs , 1 ) ,
LastUrl : hmnurl . BuildForum ( c . CurrentProject . Slug , currentSubforumSlugs , numPages ) ,
NextUrl : hmnurl . BuildForum ( c . CurrentProject . Slug , currentSubforumSlugs , utils . IntClamp ( 1 , page + 1 , numPages ) ) ,
PreviousUrl : hmnurl . BuildForum ( c . CurrentProject . Slug , currentSubforumSlugs , utils . IntClamp ( 1 , page - 1 , numPages ) ) ,
2021-05-03 22:45:17 +00:00
} ,
2021-07-30 03:40:47 +00:00
Subforums : subforums ,
2021-05-03 14:51:07 +00:00
} , c . Perf )
return res
}
2021-07-30 03:40:47 +00:00
func ForumMarkRead ( c * RequestContext ) ResponseData {
c . Perf . StartBlock ( "SQL" , "Fetch subforum tree" )
subforumTree := models . GetFullSubforumTree ( c . Context ( ) , c . Conn )
lineageBuilder := models . MakeSubforumLineageBuilder ( subforumTree )
2021-07-23 19:00:37 +00:00
c . Perf . EndBlock ( )
2021-07-30 03:40:47 +00:00
sfId , err := strconv . Atoi ( c . PathParams [ "sfid" ] )
2021-07-23 19:00:37 +00:00
if err != nil {
return FourOhFour ( c )
}
tx , err := c . Conn . Begin ( c . Context ( ) )
if err != nil {
panic ( err )
}
defer tx . Rollback ( c . Context ( ) )
2021-07-30 03:40:47 +00:00
sfIds := [ ] int { sfId }
if sfId == 0 {
2021-07-30 04:48:30 +00:00
// Mark literally everything as read
_ , err := tx . Exec ( c . Context ( ) ,
2021-07-23 19:00:37 +00:00
`
2021-07-30 04:48:30 +00:00
UPDATE auth_user
SET marked_all_read_at = NOW ( )
WHERE id = $ 1
2021-07-23 19:00:37 +00:00
` ,
2021-07-30 04:48:30 +00:00
c . CurrentUser . ID ,
2021-07-23 19:00:37 +00:00
)
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to mark all posts as read" ) )
2021-07-23 19:00:37 +00:00
}
2021-07-30 04:48:30 +00:00
// Delete thread unread info
_ , err = tx . Exec ( c . Context ( ) ,
`
DELETE FROM handmade_threadlastreadinfo
WHERE user_id = $ 1 ;
` ,
c . CurrentUser . ID ,
)
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to delete thread unread info" ) )
2021-07-23 19:00:37 +00:00
}
2021-07-30 04:48:30 +00:00
// Delete subforum unread info
_ , err = tx . Exec ( c . Context ( ) ,
`
DELETE FROM handmade_subforumlastreadinfo
WHERE user_id = $ 1 ;
` ,
c . CurrentUser . ID ,
)
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to delete subforum unread info" ) )
2021-07-30 04:48:30 +00:00
}
} else {
c . Perf . StartBlock ( "SQL" , "Update SLRIs" )
_ , err = tx . Exec ( c . Context ( ) ,
`
INSERT INTO handmade_subforumlastreadinfo ( subforum_id , user_id , lastread )
2021-07-23 19:00:37 +00:00
SELECT id , $ 2 , $ 3
2021-07-30 04:48:30 +00:00
FROM handmade_subforum
2021-07-23 19:00:37 +00:00
WHERE id = ANY ( $ 1 )
2021-07-30 04:48:30 +00:00
ON CONFLICT ( subforum_id , user_id ) DO UPDATE
2021-07-23 19:00:37 +00:00
SET lastread = EXCLUDED . lastread
` ,
2021-07-30 04:48:30 +00:00
sfIds ,
c . CurrentUser . ID ,
time . Now ( ) ,
)
c . Perf . EndBlock ( )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to update forum slris" ) )
2021-07-30 04:48:30 +00:00
}
2021-07-23 19:00:37 +00:00
2021-07-30 04:48:30 +00:00
c . Perf . StartBlock ( "SQL" , "Delete TLRIs" )
_ , err = tx . Exec ( c . Context ( ) ,
`
2021-07-23 19:00:37 +00:00
DELETE FROM handmade_threadlastreadinfo
WHERE
user_id = $ 2
AND thread_id IN (
SELECT id
FROM handmade_thread
WHERE
2021-07-30 04:48:30 +00:00
subforum_id = ANY ( $ 1 )
2021-07-23 19:00:37 +00:00
)
` ,
2021-07-30 04:48:30 +00:00
sfIds ,
c . CurrentUser . ID ,
)
c . Perf . EndBlock ( )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to delete unnecessary tlris" ) )
2021-07-30 04:48:30 +00:00
}
2021-07-23 19:00:37 +00:00
}
err = tx . Commit ( c . Context ( ) )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to commit SLRI/TLRI updates" ) )
2021-07-23 19:00:37 +00:00
}
var redirUrl string
2021-07-30 03:40:47 +00:00
if sfId == 0 {
2021-07-23 19:00:37 +00:00
redirUrl = hmnurl . BuildFeed ( )
} else {
2021-07-30 03:40:47 +00:00
redirUrl = hmnurl . BuildForum ( c . CurrentProject . Slug , lineageBuilder . GetSubforumLineageSlugs ( sfId ) , 1 )
2021-07-23 19:00:37 +00:00
}
return c . Redirect ( redirUrl , http . StatusSeeOther )
}
2021-05-04 01:59:45 +00:00
type forumThreadData struct {
templates . BaseData
2021-05-06 05:57:14 +00:00
2021-05-04 01:59:45 +00:00
Thread templates . Thread
Posts [ ] templates . Post
2021-05-06 05:57:14 +00:00
2021-07-30 03:40:47 +00:00
SubforumUrl string
2021-05-06 05:57:14 +00:00
ReplyUrl string
Pagination templates . Pagination
2021-05-04 01:59:45 +00:00
}
2021-06-25 13:52:43 +00:00
var threadViewPostsPerPage = 15
2021-05-04 01:59:45 +00:00
2021-06-25 13:52:43 +00:00
func ForumThread ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-05-04 01:59:45 +00:00
return FourOhFour ( c )
}
2021-07-30 03:40:47 +00:00
currentSubforumSlugs := cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID )
2021-05-11 22:53:23 +00:00
2021-07-30 19:59:48 +00:00
thread := FetchThread ( c . Context ( ) , c . Conn , cd . ThreadID )
2021-05-04 01:59:45 +00:00
2021-05-06 05:57:14 +00:00
numPosts , err := db . QueryInt ( c . Context ( ) , c . Conn ,
`
SELECT COUNT ( * )
FROM handmade_post
WHERE
thread_id = $ 1
AND NOT deleted
` ,
thread . ID ,
)
if err != nil {
panic ( oops . New ( err , "failed to get count of posts for thread" ) )
}
2021-06-25 13:52:43 +00:00
page , numPages , ok := getPageInfo ( c . PathParams [ "page" ] , numPosts , threadViewPostsPerPage )
2021-05-04 01:59:45 +00:00
if ! ok {
2021-05-11 22:53:23 +00:00
urlNoPage := hmnurl . BuildForumThread ( c . CurrentProject . Slug , currentSubforumSlugs , thread . ID , thread . Title , 1 )
2021-05-04 01:59:45 +00:00
return c . Redirect ( urlNoPage , http . StatusSeeOther )
}
2021-05-06 05:57:14 +00:00
pagination := templates . Pagination {
Current : page ,
Total : numPages ,
2021-05-11 22:53:23 +00:00
FirstUrl : hmnurl . BuildForumThread ( c . CurrentProject . Slug , currentSubforumSlugs , thread . ID , thread . Title , 1 ) ,
LastUrl : hmnurl . BuildForumThread ( c . CurrentProject . Slug , currentSubforumSlugs , thread . ID , thread . Title , numPages ) ,
NextUrl : hmnurl . BuildForumThread ( c . CurrentProject . Slug , currentSubforumSlugs , thread . ID , thread . Title , utils . IntClamp ( 1 , page + 1 , numPages ) ) ,
PreviousUrl : hmnurl . BuildForumThread ( c . CurrentProject . Slug , currentSubforumSlugs , thread . ID , thread . Title , utils . IntClamp ( 1 , page - 1 , numPages ) ) ,
2021-05-06 05:57:14 +00:00
}
2021-05-04 01:59:45 +00:00
c . Perf . StartBlock ( "SQL" , "Fetch posts" )
2021-07-30 19:59:48 +00:00
_ , postsAndStuff := FetchThreadPostsAndStuff (
c . Context ( ) ,
c . Conn ,
cd . ThreadID ,
page , threadViewPostsPerPage ,
2021-05-04 01:59:45 +00:00
)
c . Perf . EndBlock ( )
2021-08-28 17:07:45 +00:00
c . Perf . StartBlock ( "TEMPLATE" , "Create template posts" )
2021-05-04 01:59:45 +00:00
var posts [ ] templates . Post
2021-07-30 19:59:48 +00:00
for _ , p := range postsAndStuff {
post := templates . PostToTemplate ( & p . Post , p . Author , c . Theme )
post . AddContentVersion ( p . CurrentVersion , p . Editor )
addForumUrlsToPost ( & post , c . CurrentProject . Slug , currentSubforumSlugs , thread . ID , post . ID )
if p . ReplyPost != nil {
reply := templates . PostToTemplate ( p . ReplyPost , p . ReplyAuthor , c . Theme )
addForumUrlsToPost ( & reply , c . CurrentProject . Slug , currentSubforumSlugs , thread . ID , post . ID )
2021-07-20 03:07:15 +00:00
post . ReplyPost = & reply
}
2021-08-28 17:07:45 +00:00
addAuthorCountsToPost ( c . Context ( ) , c . Conn , & post )
2021-05-06 05:57:14 +00:00
posts = append ( posts , post )
2021-05-04 01:59:45 +00:00
}
2021-08-28 17:07:45 +00:00
c . Perf . EndBlock ( )
2021-05-04 01:59:45 +00:00
2021-07-23 19:00:37 +00:00
// Update thread last read info
2021-07-23 20:35:18 +00:00
if c . CurrentUser != nil {
c . Perf . StartBlock ( "SQL" , "Update TLRI" )
_ , err = c . Conn . Exec ( c . Context ( ) ,
`
2021-07-23 19:00:37 +00:00
INSERT INTO handmade_threadlastreadinfo ( thread_id , user_id , lastread )
VALUES ( $ 1 , $ 2 , $ 3 )
ON CONFLICT ( thread_id , user_id ) DO UPDATE
SET lastread = EXCLUDED . lastread
` ,
2021-07-23 20:35:18 +00:00
cd . ThreadID ,
c . CurrentUser . ID ,
time . Now ( ) ,
)
c . Perf . EndBlock ( )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to update forum tlri" ) )
2021-07-23 20:35:18 +00:00
}
2021-07-23 19:00:37 +00:00
}
2021-09-01 18:25:09 +00:00
baseData := getBaseData ( c , thread . Title , SubforumBreadcrumbs ( cd . LineageBuilder , c . CurrentProject , cd . SubforumID ) )
2021-05-04 01:59:45 +00:00
var res ResponseData
2021-07-17 15:19:17 +00:00
res . MustWriteTemplate ( "forum_thread.html" , forumThreadData {
2021-05-06 05:57:14 +00:00
BaseData : baseData ,
2021-07-30 19:59:48 +00:00
Thread : templates . ThreadToTemplate ( & thread ) ,
2021-05-06 05:57:14 +00:00
Posts : posts ,
2021-07-30 03:40:47 +00:00
SubforumUrl : hmnurl . BuildForum ( c . CurrentProject . Slug , currentSubforumSlugs , 1 ) ,
2021-07-30 20:01:40 +00:00
ReplyUrl : hmnurl . BuildForumPostReply ( c . CurrentProject . Slug , currentSubforumSlugs , thread . ID , thread . FirstID ) ,
2021-05-06 05:57:14 +00:00
Pagination : pagination ,
2021-05-04 01:59:45 +00:00
} , c . Perf )
return res
}
2021-06-25 13:52:43 +00:00
func ForumPostRedirect ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-06-25 13:52:43 +00:00
return FourOhFour ( c )
}
c . Perf . StartBlock ( "SQL" , "Fetch post ids for thread" )
type postQuery struct {
PostID int ` db:"post.id" `
}
postQueryResult , err := db . Query ( c . Context ( ) , c . Conn , postQuery { } ,
`
SELECT $ columns
FROM
handmade_post AS post
WHERE
2021-07-30 03:40:47 +00:00
post . thread_id = $ 1
2021-06-25 13:52:43 +00:00
AND NOT post . deleted
ORDER BY postdate
` ,
2021-07-23 20:35:18 +00:00
cd . ThreadID ,
2021-06-25 13:52:43 +00:00
)
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to fetch post ids" ) )
2021-06-25 13:52:43 +00:00
}
postQuerySlice := postQueryResult . ToSlice ( )
c . Perf . EndBlock ( )
postIdx := - 1
for i , id := range postQuerySlice {
2021-07-23 20:35:18 +00:00
if id . ( * postQuery ) . PostID == cd . PostID {
2021-06-25 13:52:43 +00:00
postIdx = i
break
}
}
if postIdx == - 1 {
return FourOhFour ( c )
}
c . Perf . StartBlock ( "SQL" , "Fetch thread title" )
type threadTitleQuery struct {
ThreadTitle string ` db:"thread.title" `
}
threadTitleQueryResult , err := db . QueryOne ( c . Context ( ) , c . Conn , threadTitleQuery { } ,
`
SELECT $ columns
FROM handmade_thread AS thread
WHERE thread . id = $ 1
` ,
2021-07-23 20:35:18 +00:00
cd . ThreadID ,
2021-06-25 13:52:43 +00:00
)
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to fetch thread title" ) )
2021-06-25 13:52:43 +00:00
}
c . Perf . EndBlock ( )
threadTitle := threadTitleQueryResult . ( * threadTitleQuery ) . ThreadTitle
page := ( postIdx / threadViewPostsPerPage ) + 1
return c . Redirect ( hmnurl . BuildForumThreadWithPostHash (
c . CurrentProject . Slug ,
2021-07-30 03:40:47 +00:00
cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) ,
2021-07-23 20:35:18 +00:00
cd . ThreadID ,
2021-06-25 13:52:43 +00:00
threadTitle ,
page ,
2021-07-23 20:35:18 +00:00
cd . PostID ,
2021-06-25 13:52:43 +00:00
) , http . StatusSeeOther )
}
2021-06-12 03:51:07 +00:00
func ForumNewThread ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-07-02 05:11:58 +00:00
return FourOhFour ( c )
}
2021-09-01 18:25:09 +00:00
baseData := getBaseData ( c , "Create New Thread" , SubforumBreadcrumbs ( cd . LineageBuilder , c . CurrentProject , cd . SubforumID ) )
2021-07-30 22:32:19 +00:00
editData := getEditorDataForNew ( baseData , nil )
editData . SubmitUrl = hmnurl . BuildForumNewThread ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , true )
editData . SubmitLabel = "Post New Thread"
2021-06-12 03:51:07 +00:00
var res ResponseData
2021-07-30 22:32:19 +00:00
res . MustWriteTemplate ( "editor.html" , editData , c . Perf )
2021-06-12 03:51:07 +00:00
return res
}
func ForumNewThreadSubmit ( c * RequestContext ) ResponseData {
2021-07-04 20:25:28 +00:00
tx , err := c . Conn . Begin ( c . Context ( ) )
if err != nil {
panic ( err )
}
2021-07-04 22:48:08 +00:00
defer tx . Rollback ( c . Context ( ) )
2021-07-04 20:25:28 +00:00
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-07-04 20:25:28 +00:00
return FourOhFour ( c )
}
2021-07-30 23:08:42 +00:00
err = c . Req . ParseForm ( )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusBadRequest , oops . New ( err , "the form data was invalid" ) )
2021-07-30 23:08:42 +00:00
}
2021-07-04 20:25:28 +00:00
title := c . Req . Form . Get ( "title" )
unparsed := c . Req . Form . Get ( "body" )
sticky := false
if c . CurrentUser . IsStaff && c . Req . Form . Get ( "sticky" ) != "" {
sticky = true
}
2021-07-30 23:08:42 +00:00
if title == "" {
return RejectRequest ( c , "You must provide a title for your post." )
}
if unparsed == "" {
return RejectRequest ( c , "You must provide a body for your post." )
}
2021-07-23 20:35:18 +00:00
2021-07-04 20:25:28 +00:00
// Create thread
var threadId int
err = tx . QueryRow ( c . Context ( ) ,
`
2021-07-30 03:40:47 +00:00
INSERT INTO handmade_thread ( title , sticky , type , project_id , subforum_id , first_id , last_id )
VALUES ( $ 1 , $ 2 , $ 3 , $ 4 , $ 5 , $ 6 , $ 7 )
2021-07-04 20:25:28 +00:00
RETURNING id
` ,
title ,
sticky ,
2021-07-30 03:40:47 +00:00
models . ThreadTypeForumPost ,
c . CurrentProject . ID ,
cd . SubforumID ,
2021-07-04 22:48:08 +00:00
- 1 ,
- 1 ,
2021-07-04 20:25:28 +00:00
) . Scan ( & threadId )
if err != nil {
panic ( oops . New ( err , "failed to create thread" ) )
}
2021-07-30 22:32:19 +00:00
// Create everything else
CreateNewPost ( c . Context ( ) , tx , c . CurrentProject . ID , threadId , models . ThreadTypeForumPost , c . CurrentUser . ID , nil , unparsed , c . Req . Host )
2021-07-20 02:35:22 +00:00
err = tx . Commit ( c . Context ( ) )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to create new forum thread" ) )
2021-07-20 02:35:22 +00:00
}
2021-07-30 03:40:47 +00:00
newThreadUrl := hmnurl . BuildForumThread ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , threadId , title , 1 )
2021-07-20 02:35:22 +00:00
return c . Redirect ( newThreadUrl , http . StatusSeeOther )
}
func ForumPostReply ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-07-20 02:35:22 +00:00
return FourOhFour ( c )
}
2021-07-30 19:59:48 +00:00
postData := FetchPostAndStuff ( c . Context ( ) , c . Conn , cd . ThreadID , cd . PostID )
2021-07-20 02:35:22 +00:00
2021-09-01 18:25:09 +00:00
baseData := getBaseData (
c ,
fmt . Sprintf ( "Replying to post | %s" , cd . SubforumTree [ cd . SubforumID ] . Name ) ,
ForumThreadBreadcrumbs ( cd . LineageBuilder , c . CurrentProject , & postData . Thread ) ,
)
2021-07-20 02:35:22 +00:00
2021-07-30 23:08:42 +00:00
replyPost := templates . PostToTemplate ( & postData . Post , postData . Author , c . Theme )
replyPost . AddContentVersion ( postData . CurrentVersion , postData . Editor )
2021-07-20 02:35:22 +00:00
2021-07-30 23:08:42 +00:00
editData := getEditorDataForNew ( baseData , & replyPost )
2021-07-30 22:32:19 +00:00
editData . SubmitUrl = hmnurl . BuildForumPostReply ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , cd . ThreadID , cd . PostID )
editData . SubmitLabel = "Submit Reply"
2021-07-20 02:35:22 +00:00
2021-07-30 22:32:19 +00:00
var res ResponseData
res . MustWriteTemplate ( "editor.html" , editData , c . Perf )
2021-07-20 02:35:22 +00:00
return res
}
func ForumPostReplySubmit ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-07-20 02:35:22 +00:00
return FourOhFour ( c )
}
2021-07-23 20:35:18 +00:00
tx , err := c . Conn . Begin ( c . Context ( ) )
2021-07-20 03:07:15 +00:00
if err != nil {
2021-07-23 20:35:18 +00:00
panic ( err )
2021-07-20 03:07:15 +00:00
}
2021-07-23 20:35:18 +00:00
defer tx . Rollback ( c . Context ( ) )
2021-07-20 03:07:15 +00:00
2021-07-30 23:08:42 +00:00
err = c . Req . ParseForm ( )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusBadRequest , oops . New ( nil , "the form data was invalid" ) )
2021-07-30 23:08:42 +00:00
}
2021-07-20 02:35:22 +00:00
unparsed := c . Req . Form . Get ( "body" )
2021-07-30 23:08:42 +00:00
if unparsed == "" {
return RejectRequest ( c , "Your reply cannot be empty." )
}
2021-07-20 02:35:22 +00:00
2021-07-30 22:32:19 +00:00
newPostId , _ := CreateNewPost ( c . Context ( ) , tx , c . CurrentProject . ID , cd . ThreadID , models . ThreadTypeForumPost , c . CurrentUser . ID , & cd . PostID , unparsed , c . Req . Host )
2021-07-20 02:35:22 +00:00
err = tx . Commit ( c . Context ( ) )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to reply to forum post" ) )
2021-07-20 02:35:22 +00:00
}
2021-07-30 03:40:47 +00:00
newPostUrl := hmnurl . BuildForumPost ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , cd . ThreadID , newPostId )
2021-07-20 02:35:22 +00:00
return c . Redirect ( newPostUrl , http . StatusSeeOther )
}
2021-07-22 01:41:23 +00:00
func ForumPostEdit ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-07-22 01:41:23 +00:00
return FourOhFour ( c )
}
2021-07-30 22:32:19 +00:00
if ! UserCanEditPost ( c . Context ( ) , c . Conn , * c . CurrentUser , cd . PostID ) {
2021-07-22 01:41:23 +00:00
return FourOhFour ( c )
}
2021-07-30 19:59:48 +00:00
postData := FetchPostAndStuff ( c . Context ( ) , c . Conn , cd . ThreadID , cd . PostID )
2021-07-22 02:16:10 +00:00
2021-09-01 18:25:09 +00:00
title := ""
2021-07-30 22:32:19 +00:00
if postData . Thread . FirstID == postData . Post . ID {
2021-09-01 18:25:09 +00:00
title = fmt . Sprintf ( "Editing \"%s\" | %s" , postData . Thread . Title , cd . SubforumTree [ cd . SubforumID ] . Name )
2021-07-30 22:32:19 +00:00
} else {
2021-09-01 18:25:09 +00:00
title = fmt . Sprintf ( "Editing Post | %s" , cd . SubforumTree [ cd . SubforumID ] . Name )
2021-07-30 22:32:19 +00:00
}
2021-09-01 18:25:09 +00:00
baseData := getBaseData ( c , title , ForumThreadBreadcrumbs ( cd . LineageBuilder , c . CurrentProject , & postData . Thread ) )
2021-07-22 01:41:23 +00:00
2021-07-30 22:32:19 +00:00
editData := getEditorDataForEdit ( baseData , postData )
editData . SubmitUrl = hmnurl . BuildForumPostEdit ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , cd . ThreadID , cd . PostID )
editData . SubmitLabel = "Submit Edited Post"
2021-07-22 01:41:23 +00:00
var res ResponseData
2021-07-30 22:32:19 +00:00
res . MustWriteTemplate ( "editor.html" , editData , c . Perf )
2021-07-22 01:41:23 +00:00
return res
}
func ForumPostEditSubmit ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-07-22 01:41:23 +00:00
return FourOhFour ( c )
}
2021-07-30 22:32:19 +00:00
if ! UserCanEditPost ( c . Context ( ) , c . Conn , * c . CurrentUser , cd . PostID ) {
2021-07-22 01:41:23 +00:00
return FourOhFour ( c )
}
2021-07-23 20:35:18 +00:00
tx , err := c . Conn . Begin ( c . Context ( ) )
2021-07-22 04:42:34 +00:00
if err != nil {
2021-07-23 20:35:18 +00:00
panic ( err )
2021-07-22 04:42:34 +00:00
}
2021-07-23 20:35:18 +00:00
defer tx . Rollback ( c . Context ( ) )
2021-07-22 04:42:34 +00:00
c . Req . ParseForm ( )
unparsed := c . Req . Form . Get ( "body" )
editReason := c . Req . Form . Get ( "editreason" )
2021-08-28 17:07:45 +00:00
if unparsed == "" {
return RejectRequest ( c , "You must provide a body for your post." )
}
2021-07-22 04:42:34 +00:00
2021-07-30 22:32:19 +00:00
CreatePostVersion ( c . Context ( ) , tx , cd . PostID , unparsed , c . Req . Host , editReason , & c . CurrentUser . ID )
2021-07-22 04:42:34 +00:00
err = tx . Commit ( c . Context ( ) )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to edit forum post" ) )
2021-07-22 04:42:34 +00:00
}
2021-07-30 03:40:47 +00:00
postUrl := hmnurl . BuildForumPost ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , cd . ThreadID , cd . PostID )
2021-07-22 04:42:34 +00:00
return c . Redirect ( postUrl , http . StatusSeeOther )
}
func ForumPostDelete ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-07-22 04:42:34 +00:00
return FourOhFour ( c )
}
2021-07-30 22:32:19 +00:00
if ! UserCanEditPost ( c . Context ( ) , c . Conn , * c . CurrentUser , cd . PostID ) {
2021-07-22 04:42:34 +00:00
return FourOhFour ( c )
}
2021-07-30 19:59:48 +00:00
postData := FetchPostAndStuff ( c . Context ( ) , c . Conn , cd . ThreadID , cd . PostID )
2021-07-22 04:42:34 +00:00
2021-09-01 18:25:09 +00:00
baseData := getBaseData (
c ,
fmt . Sprintf ( "Deleting post in \"%s\" | %s" , postData . Thread . Title , cd . SubforumTree [ cd . SubforumID ] . Name ) ,
ForumThreadBreadcrumbs ( cd . LineageBuilder , c . CurrentProject , & postData . Thread ) ,
)
2021-07-22 04:42:34 +00:00
2021-07-23 20:35:18 +00:00
templatePost := templates . PostToTemplate ( & postData . Post , postData . Author , c . Theme )
templatePost . AddContentVersion ( postData . CurrentVersion , postData . Editor )
2021-07-22 04:42:34 +00:00
type forumPostDeleteData struct {
templates . BaseData
Post templates . Post
SubmitUrl string
}
var res ResponseData
res . MustWriteTemplate ( "forum_post_delete.html" , forumPostDeleteData {
BaseData : baseData ,
2021-07-30 03:40:47 +00:00
SubmitUrl : hmnurl . BuildForumPostDelete ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , cd . ThreadID , cd . PostID ) ,
2021-07-22 04:42:34 +00:00
Post : templatePost ,
} , c . Perf )
return res
}
func ForumPostDeleteSubmit ( c * RequestContext ) ResponseData {
2021-07-23 20:35:18 +00:00
cd , ok := getCommonForumData ( c )
if ! ok {
2021-07-22 04:42:34 +00:00
return FourOhFour ( c )
}
2021-07-30 22:32:19 +00:00
if ! UserCanEditPost ( c . Context ( ) , c . Conn , * c . CurrentUser , cd . PostID ) {
2021-07-22 04:42:34 +00:00
return FourOhFour ( c )
}
tx , err := c . Conn . Begin ( c . Context ( ) )
if err != nil {
panic ( err )
}
defer tx . Rollback ( c . Context ( ) )
2021-07-30 22:32:19 +00:00
threadDeleted := DeletePost ( c . Context ( ) , tx , cd . ThreadID , cd . PostID )
2021-07-22 01:41:23 +00:00
err = tx . Commit ( c . Context ( ) )
if err != nil {
2021-08-28 12:21:03 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to delete post" ) )
2021-07-22 01:41:23 +00:00
}
2021-07-30 22:32:19 +00:00
if threadDeleted {
forumUrl := hmnurl . BuildForum ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , 1 )
return c . Redirect ( forumUrl , http . StatusSeeOther )
} else {
threadUrl := hmnurl . BuildForumThread ( c . CurrentProject . Slug , cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , cd . ThreadID , "" , 1 ) // TODO: Go to the last page of the thread? Or the post before the post we just deleted?
return c . Redirect ( threadUrl , http . StatusSeeOther )
2021-07-22 04:42:34 +00:00
}
}
2021-07-23 20:35:18 +00:00
type commonForumData struct {
c * RequestContext
2021-07-30 03:40:47 +00:00
SubforumID int
ThreadID int
PostID int
2021-07-23 20:35:18 +00:00
2021-07-30 03:40:47 +00:00
SubforumTree models . SubforumTree
LineageBuilder * models . SubforumLineageBuilder
2021-07-23 20:35:18 +00:00
}
/ *
2021-07-30 03:40:47 +00:00
Gets data that is used on basically every forums - related route . Parses path params for subforum ,
2021-07-23 20:35:18 +00:00
thread , and post ids and validates that all those resources do in fact exist .
Returns false if any data is invalid and you should return a 404.
* /
func getCommonForumData ( c * RequestContext ) ( commonForumData , bool ) {
c . Perf . StartBlock ( "FORUMS" , "Fetch common forum data" )
defer c . Perf . EndBlock ( )
2021-07-30 03:40:47 +00:00
c . Perf . StartBlock ( "SQL" , "Fetch subforum tree" )
subforumTree := models . GetFullSubforumTree ( c . Context ( ) , c . Conn )
lineageBuilder := models . MakeSubforumLineageBuilder ( subforumTree )
2021-07-23 20:35:18 +00:00
c . Perf . EndBlock ( )
res := commonForumData {
c : c ,
2021-07-30 03:40:47 +00:00
SubforumTree : subforumTree ,
2021-07-23 20:35:18 +00:00
LineageBuilder : lineageBuilder ,
}
2021-07-30 03:40:47 +00:00
if subforums , hasSubforums := c . PathParams [ "subforums" ] ; hasSubforums {
sfId , valid := validateSubforums ( lineageBuilder , c . CurrentProject , subforums )
2021-07-23 20:35:18 +00:00
if ! valid {
return commonForumData { } , false
}
2021-07-30 03:40:47 +00:00
res . SubforumID = sfId
2021-07-23 20:35:18 +00:00
2021-07-30 03:40:47 +00:00
// No need to validate that subforum exists here; it's handled by validateSubforums.
2021-07-23 20:35:18 +00:00
}
if threadIdStr , hasThreadId := c . PathParams [ "threadid" ] ; hasThreadId {
threadId , err := strconv . Atoi ( threadIdStr )
if err != nil {
return commonForumData { } , false
}
res . ThreadID = threadId
c . Perf . StartBlock ( "SQL" , "Verify that the thread exists" )
threadExists , err := db . QueryBool ( c . Context ( ) , c . Conn ,
`
SELECT COUNT ( * ) > 0
FROM handmade_thread
WHERE
id = $ 1
2021-07-30 03:40:47 +00:00
AND subforum_id = $ 2
2021-07-30 19:59:48 +00:00
AND NOT deleted
2021-07-23 20:35:18 +00:00
` ,
res . ThreadID ,
2021-07-30 03:40:47 +00:00
res . SubforumID ,
2021-07-23 20:35:18 +00:00
)
c . Perf . EndBlock ( )
if err != nil {
panic ( err )
}
if ! threadExists {
return commonForumData { } , false
}
}
if postIdStr , hasPostId := c . PathParams [ "postid" ] ; hasPostId {
postId , err := strconv . Atoi ( postIdStr )
if err != nil {
return commonForumData { } , false
}
res . PostID = postId
c . Perf . StartBlock ( "SQL" , "Verify that the post exists" )
postExists , err := db . QueryBool ( c . Context ( ) , c . Conn ,
`
SELECT COUNT ( * ) > 0
FROM handmade_post
WHERE
id = $ 1
AND thread_id = $ 2
2021-07-30 19:59:48 +00:00
AND NOT deleted
2021-07-23 20:35:18 +00:00
` ,
res . PostID ,
res . ThreadID ,
)
c . Perf . EndBlock ( )
if err != nil {
panic ( err )
}
if ! postExists {
return commonForumData { } , false
}
}
return res , true
}
2021-07-30 03:40:47 +00:00
func validateSubforums ( lineageBuilder * models . SubforumLineageBuilder , project * models . Project , sfPath string ) ( int , bool ) {
2021-07-23 20:35:18 +00:00
if project . ForumID == nil {
return - 1 , false
}
2021-07-30 03:40:47 +00:00
subforumId := * project . ForumID
if len ( sfPath ) == 0 {
return subforumId , true
2021-07-23 20:35:18 +00:00
}
2021-07-30 03:40:47 +00:00
sfPath = strings . ToLower ( sfPath )
2021-07-23 20:35:18 +00:00
valid := false
2021-07-30 03:40:47 +00:00
sfSlugs := strings . Split ( sfPath , "/" )
lastSlug := sfSlugs [ len ( sfSlugs ) - 1 ]
2021-07-23 20:35:18 +00:00
if len ( lastSlug ) > 0 {
2021-07-30 03:40:47 +00:00
lastSlugSfId := lineageBuilder . FindIdBySlug ( project . ID , lastSlug )
if lastSlugSfId != - 1 {
subforumSlugs := lineageBuilder . GetSubforumLineageSlugs ( lastSlugSfId )
2021-07-23 20:35:18 +00:00
allMatch := true
for i , subforum := range subforumSlugs {
2021-07-30 03:40:47 +00:00
if subforum != sfSlugs [ i ] {
2021-07-23 20:35:18 +00:00
allMatch = false
break
}
}
valid = allMatch
}
if valid {
2021-07-30 03:40:47 +00:00
subforumId = lastSlugSfId
2021-07-23 20:35:18 +00:00
}
}
2021-07-30 03:40:47 +00:00
return subforumId , valid
2021-07-23 20:35:18 +00:00
}
2021-07-30 19:59:48 +00:00
func addForumUrlsToPost ( p * templates . Post , projectSlug string , subforums [ ] string , threadId int , postId int ) {
p . Url = hmnurl . BuildForumPost ( projectSlug , subforums , threadId , postId )
p . DeleteUrl = hmnurl . BuildForumPostDelete ( projectSlug , subforums , threadId , postId )
p . EditUrl = hmnurl . BuildForumPostEdit ( projectSlug , subforums , threadId , postId )
p . ReplyUrl = hmnurl . BuildForumPostReply ( projectSlug , subforums , threadId , postId )
}
2021-08-28 17:07:45 +00:00
func addAuthorCountsToPost ( ctx context . Context , conn db . ConnOrTx , p * templates . Post ) {
numPosts , err := db . QueryInt ( ctx , conn ,
`
SELECT COUNT ( * )
FROM
handmade_post AS post
JOIN handmade_project AS project ON post . project_id = project . id
WHERE
post . author_id = $ 1
AND NOT post . deleted
AND project . lifecycle = ANY ( $ 2 )
` ,
p . Author . ID ,
models . VisibleProjectLifecycles ,
)
if err != nil {
logging . ExtractLogger ( ctx ) . Warn ( ) . Err ( err ) . Msg ( "failed to get count of user posts" )
} else {
p . AuthorNumPosts = numPosts
}
numProjects , err := db . QueryInt ( ctx , conn ,
`
SELECT COUNT ( * )
FROM
handmade_project AS project
JOIN handmade_user_projects AS uproj ON uproj . project_id = project . id
WHERE
project . lifecycle = ANY ( $ 1 )
AND uproj . user_id = $ 2
` ,
models . VisibleProjectLifecycles ,
p . Author . ID ,
)
if err != nil {
logging . ExtractLogger ( ctx ) . Warn ( ) . Err ( err ) . Msg ( "failed to get count of user projects" )
} else {
p . AuthorNumProjects = numProjects
}
}