2021-04-29 04:52:27 +00:00
package website
import (
2021-08-28 17:07:45 +00:00
"context"
2021-09-09 00:43:24 +00:00
"errors"
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-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-09-21 23:13:11 +00:00
MaxFileSize int
UploadUrl string
2021-07-30 22:32:19 +00:00
}
2021-07-22 01:41:23 +00:00
2021-11-10 04:11:39 +00:00
func getEditorDataForNew ( urlContext * hmnurl . UrlContext , currentUser * models . User , baseData templates . BaseData , replyPost * templates . Post ) editorData {
2021-07-30 22:32:19 +00:00
result := editorData {
BaseData : baseData ,
CanEditTitle : replyPost == nil ,
PostReplyingTo : replyPost ,
2021-09-21 23:13:11 +00:00
MaxFileSize : AssetMaxSize ( currentUser ) ,
2021-11-10 04:11:39 +00:00
UploadUrl : urlContext . BuildAssetUpload ( ) ,
2021-07-30 22:32:19 +00:00
}
if replyPost != nil {
result . Title = "Replying to post"
}
return result
}
2021-11-10 04:11:39 +00:00
func getEditorDataForEdit ( urlContext * hmnurl . UrlContext , currentUser * models . User , baseData templates . BaseData , p PostAndStuff ) editorData {
2021-07-30 22:32:19 +00:00
return editorData {
BaseData : baseData ,
Title : p . Thread . Title ,
CanEditTitle : p . Thread . FirstID == p . Post . ID ,
IsEditing : true ,
EditInitialContents : p . CurrentVersion . TextRaw ,
2021-09-21 23:13:11 +00:00
MaxFileSize : AssetMaxSize ( currentUser ) ,
2021-11-10 04:11:39 +00:00
UploadUrl : urlContext . BuildAssetUpload ( ) ,
2021-07-30 22:32:19 +00:00
}
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-09-14 04:13:58 +00:00
numThreads , err := CountThreads ( c . Context ( ) , c . Conn , c . CurrentUser , ThreadsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
SubforumIDs : [ ] int { cd . SubforumID } ,
} )
2021-04-29 04:52:27 +00:00
if err != nil {
panic ( oops . New ( err , "failed to get count of threads" ) )
}
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 {
2021-11-10 04:11:39 +00:00
c . Redirect ( c . UrlContext . BuildForum ( currentSubforumSlugs , page ) , http . StatusSeeOther )
2021-04-29 04:52:27 +00:00
}
howManyThreadsToSkip := ( page - 1 ) * threadsPerPage
2021-09-14 04:13:58 +00:00
mainThreads , err := FetchThreads ( c . Context ( ) , c . Conn , c . CurrentUser , ThreadsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
SubforumIDs : [ ] int { cd . SubforumID } ,
Limit : threadsPerPage ,
Offset : howManyThreadsToSkip ,
} )
2021-05-03 22:53:28 +00:00
2021-09-14 04:13:58 +00:00
makeThreadListItem := func ( row ThreadAndStuff ) templates . ThreadListItem {
2021-05-03 23:59:43 +00:00
return templates . ThreadListItem {
2021-05-11 22:53:23 +00:00
Title : row . Thread . Title ,
2021-11-10 04:11:39 +00:00
Url : c . UrlContext . BuildForumThread ( cd . LineageBuilder . GetSubforumLineageSlugs ( * row . Thread . SubforumID ) , row . Thread . ID , row . Thread . Title , 1 ) ,
2021-09-14 04:13:58 +00:00
FirstUser : templates . UserToTemplate ( row . FirstPostAuthor , c . Theme ) ,
2021-05-03 14:51:07 +00:00
FirstDate : row . FirstPost . PostDate ,
2021-09-14 04:13:58 +00:00
LastUser : templates . UserToTemplate ( row . LastPostAuthor , c . Theme ) ,
2021-05-03 14:51:07 +00:00
LastDate : row . LastPost . PostDate ,
2021-09-14 04:13:58 +00:00
Unread : row . Unread ,
2021-05-03 23:59:43 +00:00
}
}
var threads [ ] templates . ThreadListItem
2021-09-14 04:13:58 +00:00
for _ , row := range mainThreads {
2021-05-03 23:59:43 +00:00
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 {
2021-09-14 04:13:58 +00:00
numThreads , err := CountThreads ( c . Context ( ) , c . Conn , c . CurrentUser , ThreadsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
SubforumIDs : [ ] int { sfNode . ID } ,
} )
2021-05-03 23:59:43 +00:00
if err != nil {
panic ( oops . New ( err , "failed to get count of threads" ) )
}
2021-09-14 04:13:58 +00:00
subforumThreads , err := FetchThreads ( c . Context ( ) , c . Conn , c . CurrentUser , ThreadsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
SubforumIDs : [ ] int { sfNode . ID } ,
Limit : 3 ,
} )
2021-05-03 23:59:43 +00:00
var threads [ ] templates . ThreadListItem
2021-09-14 04:13:58 +00:00
for _ , row := range subforumThreads {
threads = append ( threads , makeThreadListItem ( row ) )
2021-05-03 23:59:43 +00:00
}
2021-07-30 03:40:47 +00:00
subforums = append ( subforums , forumSubforumData {
Name : sfNode . Name ,
2021-11-10 04:11:39 +00:00
Url : c . UrlContext . BuildForum ( 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 ) ,
2021-11-10 04:11:39 +00:00
SubforumBreadcrumbs ( c . UrlContext , cd . LineageBuilder , cd . SubforumID ) ,
2021-09-01 18:25:09 +00:00
)
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-11-10 04:11:39 +00:00
NewThreadUrl : c . UrlContext . BuildForumNewThread ( currentSubforumSlugs , false ) ,
MarkReadUrl : c . UrlContext . BuildForumMarkRead ( 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-11-10 04:11:39 +00:00
FirstUrl : c . UrlContext . BuildForum ( currentSubforumSlugs , 1 ) ,
LastUrl : c . UrlContext . BuildForum ( currentSubforumSlugs , numPages ) ,
NextUrl : c . UrlContext . BuildForum ( currentSubforumSlugs , utils . IntClamp ( 1 , page + 1 , numPages ) ) ,
PreviousUrl : c . UrlContext . BuildForum ( 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-11-10 04:11:39 +00:00
redirUrl = c . UrlContext . BuildForum ( 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-09-14 04:13:58 +00:00
// How many posts to display on a single page of a forum thread.
var threadPostsPerPage = 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-09-14 04:13:58 +00:00
threads , err := FetchThreads ( c . Context ( ) , c . Conn , c . CurrentUser , ThreadsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadIDs : [ ] int { cd . ThreadID } ,
} )
if err != nil {
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to get thread" ) )
}
if len ( threads ) == 0 {
return FourOhFour ( c )
}
threadResult := threads [ 0 ]
thread := threadResult . Thread
2021-05-04 01:59:45 +00:00
2021-09-14 04:13:58 +00:00
numPosts , err := CountPosts ( c . Context ( ) , c . Conn , c . CurrentUser , PostsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
ThreadIDs : [ ] int { cd . ThreadID } ,
} )
2021-05-06 05:57:14 +00:00
if err != nil {
panic ( oops . New ( err , "failed to get count of posts for thread" ) )
}
2021-09-14 04:13:58 +00:00
page , numPages , ok := getPageInfo ( c . PathParams [ "page" ] , numPosts , threadPostsPerPage )
2021-05-04 01:59:45 +00:00
if ! ok {
2021-11-10 04:11:39 +00:00
urlNoPage := c . UrlContext . BuildForumThread ( 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-11-10 04:11:39 +00:00
FirstUrl : c . UrlContext . BuildForumThread ( currentSubforumSlugs , thread . ID , thread . Title , 1 ) ,
LastUrl : c . UrlContext . BuildForumThread ( currentSubforumSlugs , thread . ID , thread . Title , numPages ) ,
NextUrl : c . UrlContext . BuildForumThread ( currentSubforumSlugs , thread . ID , thread . Title , utils . IntClamp ( 1 , page + 1 , numPages ) ) ,
PreviousUrl : c . UrlContext . BuildForumThread ( 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
2021-09-14 04:13:58 +00:00
postsAndStuff , err := FetchPosts ( c . Context ( ) , c . Conn , c . CurrentUser , PostsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadIDs : [ ] int { thread . ID } ,
Limit : threadPostsPerPage ,
Offset : ( page - 1 ) * threadPostsPerPage ,
} )
if err != nil {
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to fetch thread 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 )
2021-11-10 04:11:39 +00:00
addForumUrlsToPost ( c . UrlContext , & post , currentSubforumSlugs , thread . ID , post . ID )
2021-07-30 19:59:48 +00:00
if p . ReplyPost != nil {
reply := templates . PostToTemplate ( p . ReplyPost , p . ReplyAuthor , c . Theme )
2021-11-10 04:11:39 +00:00
addForumUrlsToPost ( c . UrlContext , & reply , currentSubforumSlugs , thread . ID , reply . 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-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-09-14 04:13:58 +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-11-10 04:11:39 +00:00
baseData := getBaseData ( c , thread . Title , SubforumBreadcrumbs ( c . UrlContext , cd . LineageBuilder , cd . SubforumID ) )
2021-09-09 02:51:43 +00:00
baseData . OpenGraphItems = append ( baseData . OpenGraphItems , templates . OpenGraphItem {
Property : "og:description" ,
2021-09-14 04:13:58 +00:00
Value : threadResult . FirstPost . Preview ,
2021-09-09 02:51:43 +00:00
} )
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-11-10 04:11:39 +00:00
SubforumUrl : c . UrlContext . BuildForum ( currentSubforumSlugs , 1 ) ,
ReplyUrl : c . UrlContext . BuildForumPostReply ( 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 )
}
2021-09-14 04:13:58 +00:00
posts , err := FetchPosts ( c . Context ( ) , c . Conn , c . CurrentUser , PostsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
ThreadIDs : [ ] int { cd . ThreadID } ,
} )
2021-06-25 13:52:43 +00:00
if err != nil {
2021-09-14 04:13:58 +00:00
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to fetch posts for redirect" ) )
2021-06-25 13:52:43 +00:00
}
2021-09-14 04:13:58 +00:00
var post PostAndStuff
2021-06-25 13:52:43 +00:00
postIdx := - 1
2021-09-14 04:13:58 +00:00
for i , p := range posts {
if p . Post . ID == cd . PostID {
post = p
2021-06-25 13:52:43 +00:00
postIdx = i
break
}
}
if postIdx == - 1 {
return FourOhFour ( c )
}
2021-09-14 04:13:58 +00:00
page := ( postIdx / threadPostsPerPage ) + 1
2021-06-25 13:52:43 +00:00
2021-11-10 04:11:39 +00:00
return c . Redirect ( c . UrlContext . BuildForumThreadWithPostHash (
2021-07-30 03:40:47 +00:00
cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) ,
2021-07-23 20:35:18 +00:00
cd . ThreadID ,
2021-09-14 04:13:58 +00:00
post . Thread . Title ,
2021-06-25 13:52:43 +00:00
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-11-10 04:11:39 +00:00
baseData := getBaseData ( c , "Create New Thread" , SubforumBreadcrumbs ( c . UrlContext , cd . LineageBuilder , cd . SubforumID ) )
editData := getEditorDataForNew ( c . UrlContext , c . CurrentUser , baseData , nil )
editData . SubmitUrl = c . UrlContext . BuildForumNewThread ( cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , true )
2021-07-30 22:32:19 +00:00
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-11-10 04:11:39 +00:00
newThreadUrl := c . UrlContext . BuildForumThread ( 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-09-14 04:13:58 +00:00
post , err := FetchThreadPost ( c . Context ( ) , c . Conn , c . CurrentUser , cd . ThreadID , cd . PostID , PostsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
} )
if errors . Is ( err , db . NotFound ) {
return FourOhFour ( c )
} else if err != nil {
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to fetch post for reply" ) )
}
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 ) ,
2021-11-10 04:11:39 +00:00
ForumThreadBreadcrumbs ( c . UrlContext , cd . LineageBuilder , & post . Thread ) ,
2021-09-01 18:25:09 +00:00
)
2021-07-20 02:35:22 +00:00
2021-09-14 04:13:58 +00:00
replyPost := templates . PostToTemplate ( & post . Post , post . Author , c . Theme )
replyPost . AddContentVersion ( post . CurrentVersion , post . Editor )
2021-07-20 02:35:22 +00:00
2021-11-10 04:11:39 +00:00
editData := getEditorDataForNew ( c . UrlContext , c . CurrentUser , baseData , & replyPost )
editData . SubmitUrl = c . UrlContext . BuildForumPostReply ( cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , cd . ThreadID , cd . PostID )
2021-07-30 22:32:19 +00:00
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-09-14 04:13:58 +00:00
post , err := FetchThreadPost ( c . Context ( ) , c . Conn , c . CurrentUser , cd . ThreadID , cd . PostID , PostsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
} )
if errors . Is ( err , db . NotFound ) {
return FourOhFour ( c )
}
2021-09-06 21:39:28 +00:00
// Replies to the OP should not be considered replies
var replyPostId * int
2021-09-14 04:13:58 +00:00
if cd . PostID != post . Thread . FirstID {
2021-09-06 21:39:28 +00:00
replyPostId = & cd . PostID
}
newPostId , _ := CreateNewPost ( c . Context ( ) , tx , c . CurrentProject . ID , cd . ThreadID , models . ThreadTypeForumPost , c . CurrentUser . ID , replyPostId , 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-11-10 04:11:39 +00:00
newPostUrl := c . UrlContext . BuildForumPost ( 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-09-14 04:13:58 +00:00
post , err := FetchThreadPost ( c . Context ( ) , c . Conn , c . CurrentUser , cd . ThreadID , cd . PostID , PostsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
} )
if errors . Is ( err , db . NotFound ) {
return FourOhFour ( c )
} else if err != nil {
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to fetch post for editing" ) )
}
2021-07-22 02:16:10 +00:00
2021-09-01 18:25:09 +00:00
title := ""
2021-09-14 04:13:58 +00:00
if post . Thread . FirstID == post . Post . ID {
title = fmt . Sprintf ( "Editing \"%s\" | %s" , post . 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-11-10 04:11:39 +00:00
baseData := getBaseData ( c , title , ForumThreadBreadcrumbs ( c . UrlContext , cd . LineageBuilder , & post . Thread ) )
2021-07-22 01:41:23 +00:00
2021-11-10 04:11:39 +00:00
editData := getEditorDataForEdit ( c . UrlContext , c . CurrentUser , baseData , post )
editData . SubmitUrl = c . UrlContext . BuildForumPostEdit ( cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , cd . ThreadID , cd . PostID )
2021-07-30 22:32:19 +00:00
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
2021-09-23 06:09:18 +00:00
post , err := FetchThreadPost ( c . Context ( ) , tx , c . CurrentUser , cd . ThreadID , cd . PostID , PostsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
} )
if errors . Is ( err , db . NotFound ) {
return FourOhFour ( c )
} else if err != nil {
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to get forum post to submit edits" ) )
}
2021-07-22 04:42:34 +00:00
c . Req . ParseForm ( )
2021-09-23 06:09:18 +00:00
title := c . Req . Form . Get ( "title" )
2021-07-22 04:42:34 +00:00
unparsed := c . Req . Form . Get ( "body" )
editReason := c . Req . Form . Get ( "editreason" )
2021-09-23 06:09:18 +00:00
if title != "" && post . Thread . FirstID != post . Post . ID {
return RejectRequest ( c , "You can only edit the title by editing the first post." )
}
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
2021-09-23 06:09:18 +00:00
if title != "" {
_ , err := tx . Exec ( c . Context ( ) ,
`
UPDATE handmade_thread SET title = $ 1 WHERE id = $ 2
` ,
title ,
post . Thread . ID ,
)
if err != nil {
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to update thread title" ) )
}
}
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-11-10 04:11:39 +00:00
postUrl := c . UrlContext . BuildForumPost ( 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-09-14 04:13:58 +00:00
post , err := FetchThreadPost ( c . Context ( ) , c . Conn , c . CurrentUser , cd . ThreadID , cd . PostID , PostsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
ThreadTypes : [ ] models . ThreadType { models . ThreadTypeForumPost } ,
} )
if errors . Is ( err , db . NotFound ) {
return FourOhFour ( c )
} else if err != nil {
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to fetch post for delete" ) )
}
2021-07-22 04:42:34 +00:00
2021-09-01 18:25:09 +00:00
baseData := getBaseData (
c ,
2021-09-14 04:13:58 +00:00
fmt . Sprintf ( "Deleting post in \"%s\" | %s" , post . Thread . Title , cd . SubforumTree [ cd . SubforumID ] . Name ) ,
2021-11-10 04:11:39 +00:00
ForumThreadBreadcrumbs ( c . UrlContext , cd . LineageBuilder , & post . Thread ) ,
2021-09-01 18:25:09 +00:00
)
2021-07-22 04:42:34 +00:00
2021-09-14 04:13:58 +00:00
templatePost := templates . PostToTemplate ( & post . Post , post . Author , c . Theme )
templatePost . AddContentVersion ( post . CurrentVersion , post . 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-11-10 04:11:39 +00:00
SubmitUrl : c . UrlContext . BuildForumPostDelete ( 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 {
2021-11-10 04:11:39 +00:00
forumUrl := c . UrlContext . BuildForum ( cd . LineageBuilder . GetSubforumLineageSlugs ( cd . SubforumID ) , 1 )
2021-07-30 22:32:19 +00:00
return c . Redirect ( forumUrl , http . StatusSeeOther )
} else {
2021-11-10 04:11:39 +00:00
threadUrl := c . UrlContext . BuildForumThread ( 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?
2021-07-30 22:32:19 +00:00
return c . Redirect ( threadUrl , http . StatusSeeOther )
2021-07-22 04:42:34 +00:00
}
}
2021-07-23 20:35:18 +00:00
2021-09-09 00:43:24 +00:00
func WikiArticleRedirect ( c * RequestContext ) ResponseData {
threadIdStr := c . PathParams [ "threadid" ]
threadId , err := strconv . Atoi ( threadIdStr )
if err != nil {
panic ( err )
}
2021-09-14 04:13:58 +00:00
thread , err := FetchThread ( c . Context ( ) , c . Conn , c . CurrentUser , threadId , ThreadsQuery {
ProjectIDs : [ ] int { c . CurrentProject . ID } ,
// This is the rare query where we want all thread types!
} )
if errors . Is ( err , db . NotFound ) {
2021-09-09 00:43:24 +00:00
return FourOhFour ( c )
} else if err != nil {
return c . ErrorResponse ( http . StatusInternalServerError , oops . New ( err , "failed to look up wiki thread" ) )
}
c . Perf . StartBlock ( "SQL" , "Fetch subforum tree" )
subforumTree := models . GetFullSubforumTree ( c . Context ( ) , c . Conn )
lineageBuilder := models . MakeSubforumLineageBuilder ( subforumTree )
c . Perf . EndBlock ( )
2021-11-10 04:11:39 +00:00
dest := UrlForGenericThread ( c . UrlContext , & thread . Thread , lineageBuilder )
2021-09-09 00:43:24 +00:00
return c . Redirect ( dest , http . StatusFound )
}
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-09-14 04:13:58 +00:00
thread , and post ids .
Does NOT validate that the requested thread and post ID are valid .
2021-07-23 20:35:18 +00:00
2021-09-14 04:13:58 +00:00
If this returns false , then something was malformed and you should 404.
2021-07-23 20:35:18 +00:00
* /
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
}
if threadIdStr , hasThreadId := c . PathParams [ "threadid" ] ; hasThreadId {
threadId , err := strconv . Atoi ( threadIdStr )
if err != nil {
return commonForumData { } , false
}
res . ThreadID = threadId
}
if postIdStr , hasPostId := c . PathParams [ "postid" ] ; hasPostId {
postId , err := strconv . Atoi ( postIdStr )
if err != nil {
return commonForumData { } , false
}
res . PostID = postId
}
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
2021-11-10 04:11:39 +00:00
func addForumUrlsToPost ( urlContext * hmnurl . UrlContext , p * templates . Post , subforums [ ] string , threadId int , postId int ) {
p . Url = urlContext . BuildForumPost ( subforums , threadId , postId )
p . DeleteUrl = urlContext . BuildForumPostDelete ( subforums , threadId , postId )
p . EditUrl = urlContext . BuildForumPostEdit ( subforums , threadId , postId )
p . ReplyUrl = urlContext . BuildForumPostReply ( subforums , threadId , postId )
2021-07-30 19:59:48 +00:00
}
2021-08-28 17:07:45 +00:00
2021-09-14 04:13:58 +00:00
// Takes a template post and adds information about how many posts the user has made
// on the site.
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
}
}