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.
This commit is contained in:
Ben Visness 2021-11-10 09:13:56 -08:00
parent ab84332b23
commit a84ec79ee2
8 changed files with 82 additions and 40 deletions

View File

@ -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(`-+`)

View File

@ -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,
}

View File

@ -20,7 +20,7 @@
</div>
</div>
<div>
{{ if $.User }}
{{ if and $.User $.Project.HasBlog }}
<div class="flex">
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">&#10006;</a>&nbsp;
@ -81,7 +81,7 @@
</div>
</div>
<div>
{{ if $.User }}
{{ if and $.User $.Project.HasBlog }}
<div class="flex">
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">&#10006;</a>&nbsp;
@ -111,10 +111,12 @@
<div class="optionbar bottom">
<div class="options">
{{ if $.User }}
<a class="button" href="{{ .ReplyLink }}"><span class="big pr1">+</span> Add Comment</a>
{{ else }}
<a class="button" href="{{ .LoginLink }}">Log in to comment</a>
{{ if .Project.HasBlog }}
{{ if $.User }}
<a class="button" href="{{ .ReplyLink }}"><span class="big pr1">+</span> Add Comment</a>
{{ else }}
<a class="button" href="{{ .LoginLink }}">Log in to comment</a>
{{ end }}
{{ end }}
</div>
</div>

View File

@ -34,14 +34,18 @@
{{ define "subforum_options" }}
<div class="options">
{{ if .Project.HasForum }}
{{ if .User }}
<a class="button new-thread" href="{{ .NewThreadUrl }}"><span class="big pr1">+</span> New Thread</a>
{{ else }}
<a class="button" href="{{ .LoginPageUrl }}">Log in to post a new thread</a>
{{ end }}
{{ end }}
{{ if .User }}
<a class="button new-thread" href="{{ .NewThreadUrl }}"><span class="big pr1">+</span> New Thread</a>
<form method="POST" action="{{ .MarkReadUrl }}">
{{ csrftoken .Session }}
<button type="submit"><span class="big pr1">&#x2713;</span> Mark threads here as read</button>
</form>
{{ else }}
<a class="button" href="{{ .LoginPageUrl }}">Log in to post a new thread</a>
{{ end }}
</div>
<div class="options">

View File

@ -61,7 +61,7 @@
<div class="postid">
<a name="{{ .ID }}" href="{{ .Url }}">#{{ .ID }}</a>
</div>
{{ if $.User }}
{{ if and $.User $.Project.HasForum }}
<div class="flex pr3">
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">&#10006;</a>&nbsp;
@ -120,10 +120,12 @@
<a class="button" href="{{ .SubforumUrl }}">&larr; Back to index</a>
{{ if .Thread.Locked }}
<span>Thread is locked.</span>
{{ else if .User }}
<a class="button" href="{{ .ReplyUrl }}">&#10551; Reply to Thread</a>
{{ else }}
<span class="pa2"><a href="{{ .LoginPageUrl }}">Log in</a> to reply</span>
{{ else if .Project.HasForum }}
{{ if .User }}
<a class="button" href="{{ .ReplyUrl }}">&#10551; Reply to Thread</a>
{{ else }}
<span class="pa2"><a href="{{ .LoginPageUrl }}">Log in</a> to reply</span>
{{ end }}
{{ end }}
</div>
<div class="options order-0 order-last-ns">

View File

@ -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),

View File

@ -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 {

View File

@ -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,