Initial scaffolding for education
This commit is contained in:
parent
42e1ed95fb
commit
33352e13b7
|
@ -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"})
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{{ template "base.html" . }}
|
||||
|
||||
{{ define "content" }}
|
||||
O BOY
|
||||
{{ end }}
|
|
@ -0,0 +1,5 @@
|
|||
{{ template "base.html" . }}
|
||||
|
||||
{{ define "content" }}
|
||||
O YES
|
||||
{{ end }}
|
|
@ -0,0 +1,5 @@
|
|||
{{ template "base.html" . }}
|
||||
|
||||
{{ define "content" }}
|
||||
O NO
|
||||
{{ end }}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue