From a84ec79ee2edc0cca6eea58b5f267847806000af Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Wed, 10 Nov 2021 09:13:56 -0800 Subject: [PATCH] Disable forum/blog actions for projects that don't have them enabled I implemented this for personal projects, but I think it was actually affecting official projects too that didn't have forums or blogs enabled. --- src/models/project.go | 15 +++++++- src/templates/mapping.go | 4 +- src/templates/src/blog_post.html | 14 ++++--- src/templates/src/forum.html | 10 +++-- src/templates/src/forum_thread.html | 12 +++--- src/website/base_data.go | 4 +- src/website/blogs.go | 6 +-- src/website/routes.go | 57 ++++++++++++++++++++--------- 8 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/models/project.go b/src/models/project.go index 43f6263..24dc591 100644 --- a/src/models/project.go +++ b/src/models/project.go @@ -67,7 +67,7 @@ type Project struct { ForumEnabled bool `db:"forum_enabled"` BlogEnabled bool `db:"blog_enabled"` - LibraryEnabled bool `db:"library_enabled"` + LibraryEnabled bool `db:"library_enabled"` // TODO: Delete this field from the db } func (p *Project) IsHMN() bool { @@ -82,6 +82,19 @@ func (p *Project) Subdomain() string { return p.Slug } +// Checks whether the project has forums enabled. This should restrict the creation of new forum +// content, but it should NOT prevent the viewing of existing forum content. (Projects may at one +// point have forums enabled, write some stuff, and then later disable forums, and we want that +// content to stay accessible.) Hiding the navigation is ok. +func (p *Project) HasForums() bool { + return !p.Personal && p.ForumEnabled +} + +// Same as HasForums, but for blogs. +func (p *Project) HasBlog() bool { + return !p.Personal && p.BlogEnabled +} + var slugUnsafeChars = regexp.MustCompile(`[^a-zA-Z0-9-]`) var slugHyphenRun = regexp.MustCompile(`-+`) diff --git a/src/templates/mapping.go b/src/templates/mapping.go index 364f4ab..5bf86ac 100644 --- a/src/templates/mapping.go +++ b/src/templates/mapping.go @@ -80,8 +80,8 @@ func ProjectToTemplate(p *models.Project, url string, theme string) Project { IsHMN: p.IsHMN(), - HasBlog: p.BlogEnabled, - HasForum: p.ForumEnabled, + HasBlog: p.HasBlog(), + HasForum: p.HasForums(), DateApproved: p.DateApproved, } diff --git a/src/templates/src/blog_post.html b/src/templates/src/blog_post.html index b984dfe..fd7444d 100644 --- a/src/templates/src/blog_post.html +++ b/src/templates/src/blog_post.html @@ -20,7 +20,7 @@
- {{ if $.User }} + {{ if and $.User $.Project.HasBlog }}
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}   @@ -81,7 +81,7 @@
- {{ if $.User }} + {{ if and $.User $.Project.HasBlog }}
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}   @@ -111,10 +111,12 @@
- {{ if $.User }} - + Add Comment - {{ else }} - Log in to comment + {{ if .Project.HasBlog }} + {{ if $.User }} + + Add Comment + {{ else }} + Log in to comment + {{ end }} {{ end }}
diff --git a/src/templates/src/forum.html b/src/templates/src/forum.html index 99f3563..39b36b4 100644 --- a/src/templates/src/forum.html +++ b/src/templates/src/forum.html @@ -34,14 +34,18 @@ {{ define "subforum_options" }}
+ {{ if .Project.HasForum }} + {{ if .User }} + + New Thread + {{ else }} + Log in to post a new thread + {{ end }} + {{ end }} {{ if .User }} - + New Thread
{{ csrftoken .Session }}
- {{ else }} - Log in to post a new thread {{ end }}
diff --git a/src/templates/src/forum_thread.html b/src/templates/src/forum_thread.html index 74aa4c8..1af99fb 100644 --- a/src/templates/src/forum_thread.html +++ b/src/templates/src/forum_thread.html @@ -61,7 +61,7 @@ - {{ if $.User }} + {{ if and $.User $.Project.HasForum }}
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}   @@ -120,10 +120,12 @@ ← Back to index {{ if .Thread.Locked }} Thread is locked. - {{ else if .User }} - ⤷ Reply to Thread - {{ else }} - Log in to reply + {{ else if .Project.HasForum }} + {{ if .User }} + ⤷ Reply to Thread + {{ else }} + Log in to reply + {{ end }} {{ end }}
diff --git a/src/website/base_data.go b/src/website/base_data.go index eb767ed..350b66a 100644 --- a/src/website/base_data.go +++ b/src/website/base_data.go @@ -94,8 +94,8 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc } baseData.Header.Project = &templates.ProjectHeader{ - HasForums: c.CurrentProject.ForumEnabled, - HasBlog: c.CurrentProject.BlogEnabled, + HasForums: c.CurrentProject.HasForums(), + HasBlog: c.CurrentProject.HasBlog(), HasEpisodeGuide: hasAnnotations, ForumsUrl: c.UrlContext.BuildForum(nil, 1), BlogUrl: c.UrlContext.BuildBlog(1), diff --git a/src/website/blogs.go b/src/website/blogs.go index 9b8a666..15e7168 100644 --- a/src/website/blogs.go +++ b/src/website/blogs.go @@ -35,7 +35,7 @@ func BlogIndex(c *RequestContext) ResponseData { const postsPerPage = 5 - numPosts, err := CountPosts(c.Context(), c.Conn, c.CurrentUser, PostsQuery{ + numThreads, err := CountThreads(c.Context(), c.Conn, c.CurrentUser, ThreadsQuery{ ProjectIDs: []int{c.CurrentProject.ID}, ThreadTypes: []models.ThreadType{models.ThreadTypeProjectBlogPost}, }) @@ -43,7 +43,7 @@ func BlogIndex(c *RequestContext) ResponseData { return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch total number of blog posts")) } - numPages := utils.NumPages(numPosts, postsPerPage) + numPages := utils.NumPages(numThreads, postsPerPage) page, ok := ParsePageNumber(c, "page", numPages) if !ok { c.Redirect(c.UrlContext.BuildBlog(page), http.StatusSeeOther) @@ -73,7 +73,7 @@ func BlogIndex(c *RequestContext) ResponseData { baseData := getBaseData(c, fmt.Sprintf("%s Blog", c.CurrentProject.Name), []templates.Breadcrumb{BlogBreadcrumb(c.UrlContext)}) canCreate := false - if c.CurrentUser != nil { + if c.CurrentProject.HasBlog() && c.CurrentUser != nil { isProjectOwner := false owners, err := FetchProjectOwners(c.Context(), c.Conn, c.CurrentProject.ID) if err != nil { diff --git a/src/website/routes.go b/src/website/routes.go index a16864f..7976935 100644 --- a/src/website/routes.go +++ b/src/website/routes.go @@ -216,7 +216,6 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe hmnOnly.GET(hmnurl.RegexLibraryAny, LibraryNotPortedYet) - // NOTE(asaf): Any-project routes: attachProjectRoutes := func(rb *RouteBuilder) { rb.GET(hmnurl.RegexHomepage, func(c *RequestContext) ResponseData { if c.CurrentProject.IsHMN() { @@ -226,31 +225,53 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe } }) - rb.POST(hmnurl.RegexForumNewThreadSubmit, authMiddleware(csrfMiddleware(ForumNewThreadSubmit))) - rb.GET(hmnurl.RegexForumNewThread, authMiddleware(ForumNewThread)) + // Middleware used for forum action routes - anything related to actually creating or editing forum content + needsForums := func(h Handler) Handler { + return func(c *RequestContext) ResponseData { + // 404 if the project has forums disabled + if !c.CurrentProject.HasForums() { + return FourOhFour(c) + } + // Require auth if forums are enabled + return authMiddleware(h)(c) + } + } + rb.POST(hmnurl.RegexForumNewThreadSubmit, needsForums(csrfMiddleware(ForumNewThreadSubmit))) + rb.GET(hmnurl.RegexForumNewThread, needsForums(ForumNewThread)) rb.GET(hmnurl.RegexForumThread, ForumThread) rb.GET(hmnurl.RegexForum, Forum) - rb.POST(hmnurl.RegexForumMarkRead, authMiddleware(csrfMiddleware(ForumMarkRead))) + rb.POST(hmnurl.RegexForumMarkRead, authMiddleware(csrfMiddleware(ForumMarkRead))) // needs auth but doesn't need forums enabled rb.GET(hmnurl.RegexForumPost, ForumPostRedirect) - rb.GET(hmnurl.RegexForumPostReply, authMiddleware(ForumPostReply)) - rb.POST(hmnurl.RegexForumPostReply, authMiddleware(csrfMiddleware(ForumPostReplySubmit))) - rb.GET(hmnurl.RegexForumPostEdit, authMiddleware(ForumPostEdit)) - rb.POST(hmnurl.RegexForumPostEdit, authMiddleware(csrfMiddleware(ForumPostEditSubmit))) - rb.GET(hmnurl.RegexForumPostDelete, authMiddleware(ForumPostDelete)) - rb.POST(hmnurl.RegexForumPostDelete, authMiddleware(csrfMiddleware(ForumPostDeleteSubmit))) + rb.GET(hmnurl.RegexForumPostReply, needsForums(ForumPostReply)) + rb.POST(hmnurl.RegexForumPostReply, needsForums(csrfMiddleware(ForumPostReplySubmit))) + rb.GET(hmnurl.RegexForumPostEdit, needsForums(ForumPostEdit)) + rb.POST(hmnurl.RegexForumPostEdit, needsForums(csrfMiddleware(ForumPostEditSubmit))) + rb.GET(hmnurl.RegexForumPostDelete, needsForums(ForumPostDelete)) + rb.POST(hmnurl.RegexForumPostDelete, needsForums(csrfMiddleware(ForumPostDeleteSubmit))) rb.GET(hmnurl.RegexWikiArticle, WikiArticleRedirect) + // Middleware used for blog action routes - anything related to actually creating or editing blog content + needsBlogs := func(h Handler) Handler { + return func(c *RequestContext) ResponseData { + // 404 if the project has blogs disabled + if !c.CurrentProject.HasBlog() { + return FourOhFour(c) + } + // Require auth if blogs are enabled + return authMiddleware(h)(c) + } + } rb.GET(hmnurl.RegexBlog, BlogIndex) - rb.GET(hmnurl.RegexBlogNewThread, authMiddleware(BlogNewThread)) - rb.POST(hmnurl.RegexBlogNewThread, authMiddleware(csrfMiddleware(BlogNewThreadSubmit))) + rb.GET(hmnurl.RegexBlogNewThread, needsBlogs(BlogNewThread)) + rb.POST(hmnurl.RegexBlogNewThread, needsBlogs(csrfMiddleware(BlogNewThreadSubmit))) rb.GET(hmnurl.RegexBlogThread, BlogThread) rb.GET(hmnurl.RegexBlogPost, BlogPostRedirectToThread) - rb.GET(hmnurl.RegexBlogPostReply, authMiddleware(BlogPostReply)) - rb.POST(hmnurl.RegexBlogPostReply, authMiddleware(csrfMiddleware(BlogPostReplySubmit))) - rb.GET(hmnurl.RegexBlogPostEdit, authMiddleware(BlogPostEdit)) - rb.POST(hmnurl.RegexBlogPostEdit, authMiddleware(csrfMiddleware(BlogPostEditSubmit))) - rb.GET(hmnurl.RegexBlogPostDelete, authMiddleware(BlogPostDelete)) - rb.POST(hmnurl.RegexBlogPostDelete, authMiddleware(csrfMiddleware(BlogPostDeleteSubmit))) + rb.GET(hmnurl.RegexBlogPostReply, needsBlogs(BlogPostReply)) + rb.POST(hmnurl.RegexBlogPostReply, needsBlogs(csrfMiddleware(BlogPostReplySubmit))) + rb.GET(hmnurl.RegexBlogPostEdit, needsBlogs(BlogPostEdit)) + rb.POST(hmnurl.RegexBlogPostEdit, needsBlogs(csrfMiddleware(BlogPostEditSubmit))) + rb.GET(hmnurl.RegexBlogPostDelete, needsBlogs(BlogPostDelete)) + rb.POST(hmnurl.RegexBlogPostDelete, needsBlogs(csrfMiddleware(BlogPostDeleteSubmit))) rb.GET(hmnurl.RegexBlogsRedirect, func(c *RequestContext) ResponseData { return c.Redirect(c.UrlContext.Url( fmt.Sprintf("blog%s", c.PathParams["remainder"]), nil,