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)
|
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) {
|
func TestForum(t *testing.T) {
|
||||||
AssertRegexMatch(t, hmn.BuildForum(nil, 1), RegexForum, nil)
|
AssertRegexMatch(t, hmn.BuildForum(nil, 1), RegexForum, nil)
|
||||||
AssertRegexMatch(t, hmn.BuildForum([]string{"wip"}, 2), RegexForum, map[string]string{"subforums": "wip", "page": "2"})
|
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>/.+)$`)
|
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
|
* 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.RegexFishbowlIndex, FishbowlIndex)
|
||||||
hmnOnly.GET(hmnurl.RegexFishbowl, Fishbowl)
|
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.POST(hmnurl.RegexAPICheckUsername, csrfMiddleware(APICheckUsername))
|
||||||
|
|
||||||
hmnOnly.GET(hmnurl.RegexLibraryAny, LibraryNotPortedYet)
|
hmnOnly.GET(hmnurl.RegexLibraryAny, LibraryNotPortedYet)
|
||||||
|
|
Loading…
Reference in New Issue