Open education up to all

This commit is contained in:
Ben Visness 2022-11-05 16:18:39 -05:00
parent bd1edb2077
commit f0597f3eb8
2 changed files with 36 additions and 29 deletions

View File

@ -40,7 +40,10 @@ func EducationIndex(c *RequestContext) ResponseData {
// } // }
article := func(slug string) templates.EduArticle { article := func(slug string) templates.EduArticle {
if article, err := fetchEduArticle(c, c.Conn, slug, models.EduArticleTypeArticle, c.CurrentUser); err == nil { if article, err := fetchEduArticle(c, c.Conn, slug, c.CurrentUser, EduArticleQuery{
Types: []models.EduArticleType{models.EduArticleTypeArticle},
IncludeUnpublished: true,
}); err == nil {
return templates.EducationArticleToTemplate(article) return templates.EducationArticleToTemplate(article)
} else if errors.Is(err, db.NotFound) { } else if errors.Is(err, db.NotFound) {
return templates.EduArticle{ return templates.EduArticle{
@ -119,7 +122,9 @@ func EducationArticle(c *RequestContext) ResponseData {
DeleteUrl string DeleteUrl string
} }
article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], models.EduArticleTypeArticle, c.CurrentUser) article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], c.CurrentUser, EduArticleQuery{
Types: []models.EduArticleType{models.EduArticleTypeArticle},
})
if errors.Is(err, db.NotFound) { if errors.Is(err, db.NotFound) {
return FourOhFour(c) return FourOhFour(c)
} else if err != nil { } else if err != nil {
@ -215,7 +220,7 @@ func EducationArticleEdit(c *RequestContext) ResponseData {
Article templates.EduArticle Article templates.EduArticle
} }
article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0, c.CurrentUser) article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], c.CurrentUser, EduArticleQuery{})
if errors.Is(err, db.NotFound) { if errors.Is(err, db.NotFound) {
return FourOhFour(c) return FourOhFour(c)
} else if err != nil { } else if err != nil {
@ -239,7 +244,7 @@ func EducationArticleEditSubmit(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusBadRequest, err) return c.ErrorResponse(http.StatusBadRequest, err)
} }
_, err = fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0, c.CurrentUser) _, err = fetchEduArticle(c, c.Conn, c.PathParams["slug"], c.CurrentUser, EduArticleQuery{})
if errors.Is(err, db.NotFound) { if errors.Is(err, db.NotFound) {
return FourOhFour(c) return FourOhFour(c)
} else if err != nil { } else if err != nil {
@ -255,7 +260,7 @@ func EducationArticleEditSubmit(c *RequestContext) ResponseData {
} }
func EducationArticleDelete(c *RequestContext) ResponseData { func EducationArticleDelete(c *RequestContext) ResponseData {
article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], 0, c.CurrentUser) article, err := fetchEduArticle(c, c.Conn, c.PathParams["slug"], c.CurrentUser, EduArticleQuery{})
if errors.Is(err, db.NotFound) { if errors.Is(err, db.NotFound) {
return FourOhFour(c) return FourOhFour(c)
} else if err != nil { } else if err != nil {
@ -291,7 +296,7 @@ func EducationArticleDeleteSubmit(c *RequestContext) ResponseData {
} }
func EducationRerender(c *RequestContext) ResponseData { func EducationRerender(c *RequestContext) ResponseData {
everything := utils.Must1(fetchEduArticles(c, c.Conn, 0, c.CurrentUser)) everything := utils.Must1(fetchEduArticles(c, c.Conn, c.CurrentUser, EduArticleQuery{}))
for _, thing := range everything { for _, thing := range everything {
newHTML := parsing.ParseMarkdown(thing.CurrentVersion.ContentRaw, parsing.EducationRealMarkdown) newHTML := parsing.ParseMarkdown(thing.CurrentVersion.ContentRaw, parsing.EducationRealMarkdown)
utils.Must1(c.Conn.Exec(c, utils.Must1(c.Conn.Exec(c,
@ -310,11 +315,16 @@ func EducationRerender(c *RequestContext) ResponseData {
return res return res
} }
type EduArticleQuery struct {
Types []models.EduArticleType
IncludeUnpublished bool // If true, unpublished articles will be fetched even if they would not otherwise be visible
}
func fetchEduArticles( func fetchEduArticles(
ctx context.Context, ctx context.Context,
dbConn db.ConnOrTx, dbConn db.ConnOrTx,
t models.EduArticleType,
currentUser *models.User, currentUser *models.User,
q EduArticleQuery,
) ([]models.EduArticle, error) { ) ([]models.EduArticle, error) {
type eduArticleResult struct { type eduArticleResult struct {
Article models.EduArticle `db:"a"` Article models.EduArticle `db:"a"`
@ -330,11 +340,11 @@ func fetchEduArticles(
WHERE WHERE
TRUE TRUE
`) `)
if t != 0 { if len(q.Types) > 0 {
qb.Add(`AND a.type = $?`, t) qb.Add(`AND a.type = ANY($?)`, q.Types)
} }
if currentUser == nil || !currentUser.CanSeeUnpublishedEducationContent() { if (currentUser == nil || !currentUser.CanSeeUnpublishedEducationContent()) && !q.IncludeUnpublished {
qb.Add(`AND NOT a.published`) qb.Add(`AND a.published`)
} }
articles, err := db.Query[eduArticleResult](ctx, dbConn, qb.String(), qb.Args()...) articles, err := db.Query[eduArticleResult](ctx, dbConn, qb.String(), qb.Args()...)
@ -356,8 +366,8 @@ func fetchEduArticle(
ctx context.Context, ctx context.Context,
dbConn db.ConnOrTx, dbConn db.ConnOrTx,
slug string, slug string,
t models.EduArticleType,
currentUser *models.User, currentUser *models.User,
q EduArticleQuery,
) (*models.EduArticle, error) { ) (*models.EduArticle, error) {
type eduArticleResult struct { type eduArticleResult struct {
Article models.EduArticle `db:"a"` Article models.EduArticle `db:"a"`
@ -376,11 +386,11 @@ func fetchEduArticle(
`, `,
slug, slug,
) )
if t != 0 { if len(q.Types) > 0 {
qb.Add(`AND a.type = $?`, t) qb.Add(`AND a.type = ANY($?)`, q.Types)
} }
if currentUser == nil || !currentUser.CanSeeUnpublishedEducationContent() { if (currentUser == nil || !currentUser.CanSeeUnpublishedEducationContent()) && !q.IncludeUnpublished {
qb.Add(`AND NOT a.published`) qb.Add(`AND a.published`)
} }
res, err := db.QueryOne[eduArticleResult](ctx, dbConn, qb.String(), qb.Args()...) res, err := db.QueryOne[eduArticleResult](ctx, dbConn, qb.String(), qb.Args()...)

View File

@ -117,19 +117,16 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
hmnOnly.GET(hmnurl.RegexFishbowlIndex, FishbowlIndex) hmnOnly.GET(hmnurl.RegexFishbowlIndex, FishbowlIndex)
hmnOnly.GET(hmnurl.RegexFishbowl, Fishbowl) hmnOnly.GET(hmnurl.RegexFishbowl, Fishbowl)
educationPrerelease := hmnOnly.WithMiddleware(educationBetaTestersOnly) hmnOnly.GET(hmnurl.RegexEducationIndex, EducationIndex)
{ hmnOnly.GET(hmnurl.RegexEducationGlossary, EducationGlossary)
educationPrerelease.GET(hmnurl.RegexEducationIndex, EducationIndex) hmnOnly.GET(hmnurl.RegexEducationArticleNew, educationAuthorsOnly(EducationArticleNew))
educationPrerelease.GET(hmnurl.RegexEducationGlossary, EducationGlossary) hmnOnly.POST(hmnurl.RegexEducationArticleNew, educationAuthorsOnly(EducationArticleNewSubmit))
educationPrerelease.GET(hmnurl.RegexEducationArticleNew, educationAuthorsOnly(EducationArticleNew)) hmnOnly.GET(hmnurl.RegexEducationRerender, educationAuthorsOnly(EducationRerender))
educationPrerelease.POST(hmnurl.RegexEducationArticleNew, educationAuthorsOnly(EducationArticleNewSubmit)) hmnOnly.GET(hmnurl.RegexEducationArticle, EducationArticle) // Article stuff must be last so `/glossary` and others do not match as an article slug
educationPrerelease.GET(hmnurl.RegexEducationRerender, educationAuthorsOnly(EducationRerender)) hmnOnly.GET(hmnurl.RegexEducationArticleEdit, educationAuthorsOnly(EducationArticleEdit))
educationPrerelease.GET(hmnurl.RegexEducationArticle, EducationArticle) // Article stuff must be last so `/glossary` and others do not match as an article slug hmnOnly.POST(hmnurl.RegexEducationArticleEdit, educationAuthorsOnly(EducationArticleEditSubmit))
educationPrerelease.GET(hmnurl.RegexEducationArticleEdit, educationAuthorsOnly(EducationArticleEdit)) hmnOnly.GET(hmnurl.RegexEducationArticleDelete, educationAuthorsOnly(EducationArticleDelete))
educationPrerelease.POST(hmnurl.RegexEducationArticleEdit, educationAuthorsOnly(EducationArticleEditSubmit)) hmnOnly.POST(hmnurl.RegexEducationArticleDelete, educationAuthorsOnly(csrfMiddleware(EducationArticleDeleteSubmit)))
educationPrerelease.GET(hmnurl.RegexEducationArticleDelete, educationAuthorsOnly(EducationArticleDelete))
educationPrerelease.POST(hmnurl.RegexEducationArticleDelete, educationAuthorsOnly(csrfMiddleware(EducationArticleDeleteSubmit)))
}
hmnOnly.POST(hmnurl.RegexAPICheckUsername, csrfMiddleware(APICheckUsername)) hmnOnly.POST(hmnurl.RegexAPICheckUsername, csrfMiddleware(APICheckUsername))