Initial scaffolding for education

This commit is contained in:
Ben Visness 2022-06-22 23:09:20 -05:00
parent 42e1ed95fb
commit 33352e13b7
9 changed files with 209 additions and 0 deletions

View File

@ -185,6 +185,19 @@ func TestFishbowl(t *testing.T) {
AssertRegexNoMatch(t, BuildFishbowl("oop")+"/otherfiles/whatever", RegexFishbowl)
}
func TestEducationIndex(t *testing.T) {
AssertRegexMatch(t, BuildEducationIndex(), RegexEducationIndex, nil)
}
func TestEducationGlossary(t *testing.T) {
AssertRegexMatch(t, BuildEducationGlossary(""), RegexEducationGlossary, map[string]string{"slug": ""})
AssertRegexMatch(t, BuildEducationGlossary("foo"), RegexEducationGlossary, map[string]string{"slug": "foo"})
}
func TestEducationArticle(t *testing.T) {
AssertRegexMatch(t, BuildEducationArticle("foo"), RegexEducationArticle, map[string]string{"slug": "foo"})
}
func TestForum(t *testing.T) {
AssertRegexMatch(t, hmn.BuildForum(nil, 1), RegexForum, nil)
AssertRegexMatch(t, hmn.BuildForum([]string{"wip"}, 2), RegexForum, map[string]string{"subforums": "wip", "page": "2"})

View File

@ -434,6 +434,35 @@ func BuildFishbowl(slug string) string {
var RegexFishbowlFiles = regexp.MustCompile(`^/fishbowl/(?P<slug>[^/]+)(?P<path>/.+)$`)
/*
* Education
*/
var RegexEducationIndex = regexp.MustCompile(`^/education$`)
func BuildEducationIndex() string {
defer CatchPanic()
return Url("/education", nil)
}
var RegexEducationGlossary = regexp.MustCompile(`^/education/glossary(/(?P<slug>[^/]+))?$`)
func BuildEducationGlossary(termSlug string) string {
defer CatchPanic()
if termSlug == "" {
return Url("/education/glossary", nil)
} else {
return Url(fmt.Sprintf("/education/glossary/%s", termSlug), nil)
}
}
var RegexEducationArticle = regexp.MustCompile(`^/education/(?P<slug>[^/]+)$`)
func BuildEducationArticle(slug string) string {
return Url(fmt.Sprintf("/education/%s", slug), nil)
}
/*
* Forums
*/

View File

@ -0,0 +1,71 @@
package migrations
import (
"context"
"time"
"git.handmade.network/hmn/hmn/src/migration/types"
"git.handmade.network/hmn/hmn/src/oops"
"github.com/jackc/pgx/v4"
)
func init() {
registerMigration(AddEducationResources{})
}
type AddEducationResources struct{}
func (m AddEducationResources) Version() types.MigrationVersion {
return types.MigrationVersion(time.Date(2022, 6, 23, 3, 27, 52, 0, time.UTC))
}
func (m AddEducationResources) Name() string {
return "AddEducationResources"
}
func (m AddEducationResources) Description() string {
return "Adds the tables needed for the 2022 education initiative"
}
func (m AddEducationResources) Up(ctx context.Context, tx pgx.Tx) error {
_, err := tx.Exec(ctx,
`
CREATE TABLE education_article_version (
id SERIAL NOT NULL PRIMARY KEY
);
CREATE TABLE education_article (
id SERIAL NOT NULL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
type INT NOT NULL,
current_version INT NOT NULL REFERENCES education_article_version (id)
);
ALTER TABLE education_article_version
ADD article_id INT NOT NULL REFERENCES education_article (id) ON DELETE CASCADE,
ADD date TIMESTAMP WITH TIME ZONE NOT NULL,
ADD content_raw TEXT NOT NULL,
ADD content_html TEXT NOT NULL,
ADD editor_id INT REFERENCES hmn_user (id) ON DELETE SET NULL;
`,
)
if err != nil {
return oops.New(err, "failed to create education tables")
}
return nil
}
func (m AddEducationResources) Down(ctx context.Context, tx pgx.Tx) error {
_, err := tx.Exec(ctx, `
DROP TABLE education_article CASCADE;
DROP TABLE education_article_version CASCADE;
`)
if err != nil {
return oops.New(err, "failed to delete education tables")
}
return nil
}

32
src/models/education.go Normal file
View File

@ -0,0 +1,32 @@
package models
import "time"
type EducationArticle struct {
ID int `db:"id"`
Title string `db:"title"`
Slug string `db:"slug"`
Description string `db:"description"`
Type EducationArticleType `db:"type"`
CurrentVersion int `db:"current_version"`
}
type EducationArticleType int
const (
EducationArticleTypeArticle EducationArticleType = iota + 1
EducationArticleTypeGlossary
)
type EducationArticleVersion struct {
ID int `db:"id"`
ArticleID int `db:"article_id"`
Date time.Time `db:"date"`
EditorID *int `db:"editor_id"`
ContentRaw string `db:"content_raw"`
ContentHTML string `db:"content_html"`
}

View File

@ -0,0 +1,5 @@
{{ template "base.html" . }}
{{ define "content" }}
O BOY
{{ end }}

View File

@ -0,0 +1,5 @@
{{ template "base.html" . }}
{{ define "content" }}
O YES
{{ end }}

View File

@ -0,0 +1,5 @@
{{ template "base.html" . }}
{{ define "content" }}
O NO
{{ end }}

45
src/website/education.go Normal file
View File

@ -0,0 +1,45 @@
package website
import "git.handmade.network/hmn/hmn/src/templates"
func EducationIndex(c *RequestContext) ResponseData {
type indexData struct {
templates.BaseData
}
tmpl := indexData{
BaseData: getBaseData(c, "Handmade Education", nil),
}
var res ResponseData
res.MustWriteTemplate("education_index.html", tmpl, c.Perf)
return res
}
func EducationGlossary(c *RequestContext) ResponseData {
type glossaryData struct {
templates.BaseData
}
tmpl := glossaryData{
BaseData: getBaseData(c, "Handmade Education", nil),
}
var res ResponseData
res.MustWriteTemplate("education_glossary.html", tmpl, c.Perf)
return res
}
func EducationArticle(c *RequestContext) ResponseData {
type articleData struct {
templates.BaseData
}
tmpl := articleData{
BaseData: getBaseData(c, "Handmade Education", nil),
}
var res ResponseData
res.MustWriteTemplate("education_article.html", tmpl, c.Perf)
return res
}

View File

@ -117,6 +117,10 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
hmnOnly.GET(hmnurl.RegexFishbowlIndex, FishbowlIndex)
hmnOnly.GET(hmnurl.RegexFishbowl, Fishbowl)
hmnOnly.GET(hmnurl.RegexEducationIndex, EducationIndex)
hmnOnly.GET(hmnurl.RegexEducationGlossary, EducationGlossary) // Must be above article so `/glossary` does not match as an article slug
hmnOnly.GET(hmnurl.RegexEducationArticle, EducationArticle)
hmnOnly.POST(hmnurl.RegexAPICheckUsername, csrfMiddleware(APICheckUsername))
hmnOnly.GET(hmnurl.RegexLibraryAny, LibraryNotPortedYet)