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:
parent
ab84332b23
commit
a84ec79ee2
|
@ -67,7 +67,7 @@ type Project struct {
|
||||||
|
|
||||||
ForumEnabled bool `db:"forum_enabled"`
|
ForumEnabled bool `db:"forum_enabled"`
|
||||||
BlogEnabled bool `db:"blog_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 {
|
func (p *Project) IsHMN() bool {
|
||||||
|
@ -82,6 +82,19 @@ func (p *Project) Subdomain() string {
|
||||||
return p.Slug
|
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 slugUnsafeChars = regexp.MustCompile(`[^a-zA-Z0-9-]`)
|
||||||
var slugHyphenRun = regexp.MustCompile(`-+`)
|
var slugHyphenRun = regexp.MustCompile(`-+`)
|
||||||
|
|
||||||
|
|
|
@ -80,8 +80,8 @@ func ProjectToTemplate(p *models.Project, url string, theme string) Project {
|
||||||
|
|
||||||
IsHMN: p.IsHMN(),
|
IsHMN: p.IsHMN(),
|
||||||
|
|
||||||
HasBlog: p.BlogEnabled,
|
HasBlog: p.HasBlog(),
|
||||||
HasForum: p.ForumEnabled,
|
HasForum: p.HasForums(),
|
||||||
|
|
||||||
DateApproved: p.DateApproved,
|
DateApproved: p.DateApproved,
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ if $.User }}
|
{{ if and $.User $.Project.HasBlog }}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
|
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
|
||||||
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">✖</a>
|
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">✖</a>
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ if $.User }}
|
{{ if and $.User $.Project.HasBlog }}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
|
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
|
||||||
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">✖</a>
|
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">✖</a>
|
||||||
|
@ -111,10 +111,12 @@
|
||||||
|
|
||||||
<div class="optionbar bottom">
|
<div class="optionbar bottom">
|
||||||
<div class="options">
|
<div class="options">
|
||||||
{{ if $.User }}
|
{{ if .Project.HasBlog }}
|
||||||
<a class="button" href="{{ .ReplyLink }}"><span class="big pr1">+</span> Add Comment</a>
|
{{ if $.User }}
|
||||||
{{ else }}
|
<a class="button" href="{{ .ReplyLink }}"><span class="big pr1">+</span> Add Comment</a>
|
||||||
<a class="button" href="{{ .LoginLink }}">Log in to comment</a>
|
{{ else }}
|
||||||
|
<a class="button" href="{{ .LoginLink }}">Log in to comment</a>
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,14 +34,18 @@
|
||||||
|
|
||||||
{{ define "subforum_options" }}
|
{{ define "subforum_options" }}
|
||||||
<div class="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 }}
|
{{ if .User }}
|
||||||
<a class="button new-thread" href="{{ .NewThreadUrl }}"><span class="big pr1">+</span> New Thread</a>
|
|
||||||
<form method="POST" action="{{ .MarkReadUrl }}">
|
<form method="POST" action="{{ .MarkReadUrl }}">
|
||||||
{{ csrftoken .Session }}
|
{{ csrftoken .Session }}
|
||||||
<button type="submit"><span class="big pr1">✓</span> Mark threads here as read</button>
|
<button type="submit"><span class="big pr1">✓</span> Mark threads here as read</button>
|
||||||
</form>
|
</form>
|
||||||
{{ else }}
|
|
||||||
<a class="button" href="{{ .LoginPageUrl }}">Log in to post a new thread</a>
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="options">
|
<div class="options">
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<div class="postid">
|
<div class="postid">
|
||||||
<a name="{{ .ID }}" href="{{ .Url }}">#{{ .ID }}</a>
|
<a name="{{ .ID }}" href="{{ .Url }}">#{{ .ID }}</a>
|
||||||
</div>
|
</div>
|
||||||
{{ if $.User }}
|
{{ if and $.User $.Project.HasForum }}
|
||||||
<div class="flex pr3">
|
<div class="flex pr3">
|
||||||
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
|
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
|
||||||
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">✖</a>
|
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">✖</a>
|
||||||
|
@ -120,10 +120,12 @@
|
||||||
<a class="button" href="{{ .SubforumUrl }}">← Back to index</a>
|
<a class="button" href="{{ .SubforumUrl }}">← Back to index</a>
|
||||||
{{ if .Thread.Locked }}
|
{{ if .Thread.Locked }}
|
||||||
<span>Thread is locked.</span>
|
<span>Thread is locked.</span>
|
||||||
{{ else if .User }}
|
{{ else if .Project.HasForum }}
|
||||||
<a class="button" href="{{ .ReplyUrl }}">⤷ Reply to Thread</a>
|
{{ if .User }}
|
||||||
{{ else }}
|
<a class="button" href="{{ .ReplyUrl }}">⤷ Reply to Thread</a>
|
||||||
<span class="pa2"><a href="{{ .LoginPageUrl }}">Log in</a> to reply</span>
|
{{ else }}
|
||||||
|
<span class="pa2"><a href="{{ .LoginPageUrl }}">Log in</a> to reply</span>
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="options order-0 order-last-ns">
|
<div class="options order-0 order-last-ns">
|
||||||
|
|
|
@ -94,8 +94,8 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
|
||||||
}
|
}
|
||||||
|
|
||||||
baseData.Header.Project = &templates.ProjectHeader{
|
baseData.Header.Project = &templates.ProjectHeader{
|
||||||
HasForums: c.CurrentProject.ForumEnabled,
|
HasForums: c.CurrentProject.HasForums(),
|
||||||
HasBlog: c.CurrentProject.BlogEnabled,
|
HasBlog: c.CurrentProject.HasBlog(),
|
||||||
HasEpisodeGuide: hasAnnotations,
|
HasEpisodeGuide: hasAnnotations,
|
||||||
ForumsUrl: c.UrlContext.BuildForum(nil, 1),
|
ForumsUrl: c.UrlContext.BuildForum(nil, 1),
|
||||||
BlogUrl: c.UrlContext.BuildBlog(1),
|
BlogUrl: c.UrlContext.BuildBlog(1),
|
||||||
|
|
|
@ -35,7 +35,7 @@ func BlogIndex(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
const postsPerPage = 5
|
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},
|
ProjectIDs: []int{c.CurrentProject.ID},
|
||||||
ThreadTypes: []models.ThreadType{models.ThreadTypeProjectBlogPost},
|
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"))
|
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)
|
page, ok := ParsePageNumber(c, "page", numPages)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.Redirect(c.UrlContext.BuildBlog(page), http.StatusSeeOther)
|
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)})
|
baseData := getBaseData(c, fmt.Sprintf("%s Blog", c.CurrentProject.Name), []templates.Breadcrumb{BlogBreadcrumb(c.UrlContext)})
|
||||||
|
|
||||||
canCreate := false
|
canCreate := false
|
||||||
if c.CurrentUser != nil {
|
if c.CurrentProject.HasBlog() && c.CurrentUser != nil {
|
||||||
isProjectOwner := false
|
isProjectOwner := false
|
||||||
owners, err := FetchProjectOwners(c.Context(), c.Conn, c.CurrentProject.ID)
|
owners, err := FetchProjectOwners(c.Context(), c.Conn, c.CurrentProject.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -216,7 +216,6 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
|
||||||
|
|
||||||
hmnOnly.GET(hmnurl.RegexLibraryAny, LibraryNotPortedYet)
|
hmnOnly.GET(hmnurl.RegexLibraryAny, LibraryNotPortedYet)
|
||||||
|
|
||||||
// NOTE(asaf): Any-project routes:
|
|
||||||
attachProjectRoutes := func(rb *RouteBuilder) {
|
attachProjectRoutes := func(rb *RouteBuilder) {
|
||||||
rb.GET(hmnurl.RegexHomepage, func(c *RequestContext) ResponseData {
|
rb.GET(hmnurl.RegexHomepage, func(c *RequestContext) ResponseData {
|
||||||
if c.CurrentProject.IsHMN() {
|
if c.CurrentProject.IsHMN() {
|
||||||
|
@ -226,31 +225,53 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
rb.POST(hmnurl.RegexForumNewThreadSubmit, authMiddleware(csrfMiddleware(ForumNewThreadSubmit)))
|
// Middleware used for forum action routes - anything related to actually creating or editing forum content
|
||||||
rb.GET(hmnurl.RegexForumNewThread, authMiddleware(ForumNewThread))
|
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.RegexForumThread, ForumThread)
|
||||||
rb.GET(hmnurl.RegexForum, Forum)
|
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.RegexForumPost, ForumPostRedirect)
|
||||||
rb.GET(hmnurl.RegexForumPostReply, authMiddleware(ForumPostReply))
|
rb.GET(hmnurl.RegexForumPostReply, needsForums(ForumPostReply))
|
||||||
rb.POST(hmnurl.RegexForumPostReply, authMiddleware(csrfMiddleware(ForumPostReplySubmit)))
|
rb.POST(hmnurl.RegexForumPostReply, needsForums(csrfMiddleware(ForumPostReplySubmit)))
|
||||||
rb.GET(hmnurl.RegexForumPostEdit, authMiddleware(ForumPostEdit))
|
rb.GET(hmnurl.RegexForumPostEdit, needsForums(ForumPostEdit))
|
||||||
rb.POST(hmnurl.RegexForumPostEdit, authMiddleware(csrfMiddleware(ForumPostEditSubmit)))
|
rb.POST(hmnurl.RegexForumPostEdit, needsForums(csrfMiddleware(ForumPostEditSubmit)))
|
||||||
rb.GET(hmnurl.RegexForumPostDelete, authMiddleware(ForumPostDelete))
|
rb.GET(hmnurl.RegexForumPostDelete, needsForums(ForumPostDelete))
|
||||||
rb.POST(hmnurl.RegexForumPostDelete, authMiddleware(csrfMiddleware(ForumPostDeleteSubmit)))
|
rb.POST(hmnurl.RegexForumPostDelete, needsForums(csrfMiddleware(ForumPostDeleteSubmit)))
|
||||||
rb.GET(hmnurl.RegexWikiArticle, WikiArticleRedirect)
|
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.RegexBlog, BlogIndex)
|
||||||
rb.GET(hmnurl.RegexBlogNewThread, authMiddleware(BlogNewThread))
|
rb.GET(hmnurl.RegexBlogNewThread, needsBlogs(BlogNewThread))
|
||||||
rb.POST(hmnurl.RegexBlogNewThread, authMiddleware(csrfMiddleware(BlogNewThreadSubmit)))
|
rb.POST(hmnurl.RegexBlogNewThread, needsBlogs(csrfMiddleware(BlogNewThreadSubmit)))
|
||||||
rb.GET(hmnurl.RegexBlogThread, BlogThread)
|
rb.GET(hmnurl.RegexBlogThread, BlogThread)
|
||||||
rb.GET(hmnurl.RegexBlogPost, BlogPostRedirectToThread)
|
rb.GET(hmnurl.RegexBlogPost, BlogPostRedirectToThread)
|
||||||
rb.GET(hmnurl.RegexBlogPostReply, authMiddleware(BlogPostReply))
|
rb.GET(hmnurl.RegexBlogPostReply, needsBlogs(BlogPostReply))
|
||||||
rb.POST(hmnurl.RegexBlogPostReply, authMiddleware(csrfMiddleware(BlogPostReplySubmit)))
|
rb.POST(hmnurl.RegexBlogPostReply, needsBlogs(csrfMiddleware(BlogPostReplySubmit)))
|
||||||
rb.GET(hmnurl.RegexBlogPostEdit, authMiddleware(BlogPostEdit))
|
rb.GET(hmnurl.RegexBlogPostEdit, needsBlogs(BlogPostEdit))
|
||||||
rb.POST(hmnurl.RegexBlogPostEdit, authMiddleware(csrfMiddleware(BlogPostEditSubmit)))
|
rb.POST(hmnurl.RegexBlogPostEdit, needsBlogs(csrfMiddleware(BlogPostEditSubmit)))
|
||||||
rb.GET(hmnurl.RegexBlogPostDelete, authMiddleware(BlogPostDelete))
|
rb.GET(hmnurl.RegexBlogPostDelete, needsBlogs(BlogPostDelete))
|
||||||
rb.POST(hmnurl.RegexBlogPostDelete, authMiddleware(csrfMiddleware(BlogPostDeleteSubmit)))
|
rb.POST(hmnurl.RegexBlogPostDelete, needsBlogs(csrfMiddleware(BlogPostDeleteSubmit)))
|
||||||
rb.GET(hmnurl.RegexBlogsRedirect, func(c *RequestContext) ResponseData {
|
rb.GET(hmnurl.RegexBlogsRedirect, func(c *RequestContext) ResponseData {
|
||||||
return c.Redirect(c.UrlContext.Url(
|
return c.Redirect(c.UrlContext.Url(
|
||||||
fmt.Sprintf("blog%s", c.PathParams["remainder"]), nil,
|
fmt.Sprintf("blog%s", c.PathParams["remainder"]), nil,
|
||||||
|
|
Loading…
Reference in New Issue