cerca

lean forum software (pmc local branch)
Log | Files | Refs | README | LICENSE

commit 43ae236447a3ef196a1f2bc7131ccdc3e0776963
parent d8637554f6d690e19922b5920999c89071535624
Author: cblgh <cblgh@cblgh.org>
Date:   Mon, 24 Oct 2022 10:05:24 +0200

typo

Diffstat:
MREADME.md | 2+-
Mi18n/i18n.go | 24++++++++++++------------
Mrun.go | 12++++++------
Mserver/server.go | 118++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mtypes/types.go | 28++++++++++++++--------------
Mutil/util.go | 55+++++++++++++++++++++++++++----------------------------
6 files changed, 119 insertions(+), 120 deletions(-)

diff --git a/README.md b/README.md @@ -11,7 +11,7 @@ The reason it exists are many. To harbor longer form discussions, and for crawli threads and topics. For habitually visiting the site to see if anything new happened, as opposed to being obtrusively notified when in the middle of something else. For that sweet tinge of nostalgia that comes with the terrain, from having grown up in pace with the sprawling -phpBB forum communities of the mid naughties. +phpBB forum communities of the mid noughties. It was written for the purpose of powering the nascent [Merveilles community forums](https://forum.merveilles.town). diff --git a/i18n/i18n.go b/i18n/i18n.go @@ -87,9 +87,9 @@ var English = map[string]string{ "RegisterVerificationCode": "Your verification code is", "RegisterVerificationInstructionsTitle": "Verification instructions", - "RegisterVerificationLink": "Verification link", - "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="{{ .Data.Link }}">{{ .Data.Name }} Code of Conduct</a>`, - "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="{{ .Data.Link }}">read it</a>`, + "RegisterVerificationLink": "Verification link", + "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="{{ .Data.Link }}">{{ .Data.Name }} Code of Conduct</a>`, + "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="{{ .Data.Link }}">read it</a>`, "PasswordResetDescription": "On this page we'll go through a few steps to securely reset your password—without resorting to any emails!", "PasswordResetUsernameQuestion": "First up: what was your username?", @@ -188,12 +188,12 @@ var Swedish = map[string]string{ "RegisterVerificationCode": "Din verifikationskod är", "RegisterVerificationInstructionsTitle": "Verification instructions", - "RegisterVerificationLink": "Verificationsnyckel", - "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="{{ .Data.Link }}">{{ .Data.Name }} Code of Conduct</a>`, - "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="{{ .Data.Link }}">read it</a>`, + "RegisterVerificationLink": "Verificationsnyckel", + "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="{{ .Data.Link }}">{{ .Data.Name }} Code of Conduct</a>`, + "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="{{ .Data.Link }}">read it</a>`, "PasswordResetDescription": "På denna sida går vi igenom ett par steg för att säkert nollställa ditt lösenord—utan att behöva ta till mejl!", - "PasswordResetUsernameQuestion": "För de första: hur löd användarnamnet?", + "PasswordResetUsernameQuestion": "För de första: hur löd användarnamnet?", "PasswordResetCopyPayload": `Kopiera nu textsnutten nedan (aka <i>beviset</i>)`, "PasswordResetFollowToolInstructions": `Följ <b>verktygsinstruktionerna</b> för att finalisera nollställningen.`, "ToolInstructions": `verktygsinstruktionerna`, @@ -288,9 +288,9 @@ var EspanolMexicano = map[string]string{ "RegisterVerificationCode": "Your verification code is", "RegisterVerificationInstructionsTitle": "Verification instructions", - "RegisterVerificationLink": "Verification link", - "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="{{ .Data.Link }}">{{ .Data.Name }} Code of Conduct</a>`, - "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="{{ .Data.Link }}">read it</a>`, + "RegisterVerificationLink": "Verification link", + "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="{{ .Data.Link }}">{{ .Data.Name }} Code of Conduct</a>`, + "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="{{ .Data.Link }}">read it</a>`, "PasswordResetDescription": "On this page we'll go through a few steps to securely reset your password—without resorting to any emails!", "PasswordResetUsernameQuestion": "First up: what was your username?", @@ -315,11 +315,11 @@ var EspanolMexicano = map[string]string{ var translations = map[string]map[string]string{ "English": English, "EspañolMexicano": EspanolMexicano, - "Swedish": Swedish, + "Swedish": Swedish, } type TranslationData struct { - Data interface{} + Data interface{} } func (tr *Translator) TranslateWithData(key string, data TranslationData) string { diff --git a/run.go b/run.go @@ -4,8 +4,8 @@ import ( "flag" "fmt" "net/url" - "path/filepath" "os" + "path/filepath" "strings" "cerca/server" @@ -38,7 +38,7 @@ func main() { var allowlistLocation string var sessionKey string var configPath string - var dataDir string + var dataDir string var dev bool flag.BoolVar(&dev, "dev", false, "trigger development mode") flag.StringVar(&allowlistLocation, "allowlist", "", "domains which can be used to read verification codes from during registration") @@ -52,10 +52,10 @@ func main() { complain("please pass a file containing the verification code domain allowlist") } - err := os.MkdirAll(filepath.Dir(dataDir), 0750) - if err != nil { - complain(fmt.Sprintf("couldn't create dir '%s'", dataDir)) - } + err := os.MkdirAll(filepath.Dir(dataDir), 0750) + if err != nil { + complain(fmt.Sprintf("couldn't create dir '%s'", dataDir)) + } allowlist := readAllowlist(allowlistLocation) allowlist = append(allowlist, "merveilles.town") config := util.ReadConfig(configPath) diff --git a/server/server.go b/server/server.go @@ -17,12 +17,12 @@ import ( "cerca/crypto" "cerca/database" + "cerca/defaults" cercaHTML "cerca/html" "cerca/i18n" "cerca/server/session" "cerca/types" "cerca/util" - "cerca/defaults" "github.com/carlmjohnson/requests" ) @@ -57,11 +57,11 @@ type GenericMessageData struct { } type RegisterData struct { - VerificationCode string - ErrorMessage string - Rules template.HTML - VerificationInstructions template.HTML - ConductLink string + VerificationCode string + ErrorMessage string + Rules template.HTML + VerificationInstructions template.HTML + ConductLink string } type RegisterSuccessData struct { @@ -79,13 +79,13 @@ type ThreadData struct { } type RequestHandler struct { - db *database.DB - session *session.Session - allowlist []string // allowlist of domains valid for forum registration - files map[string][]byte - config types.Config - translator i18n.Translator - templates *template.Template + db *database.DB + session *session.Session + allowlist []string // allowlist of domains valid for forum registration + files map[string][]byte + config types.Config + translator i18n.Translator + templates *template.Template } var developing bool @@ -120,13 +120,13 @@ func (h RequestHandler) IsLoggedIn(req *http.Request) (bool, int) { // establish closure over config + translator so that it's present in templates during render func generateTemplates(config types.Config, translator i18n.Translator) (*template.Template, error) { - // only read logo contents once when generating - logo, err := os.ReadFile(config.Documents.LogoPath) - util.Check(err, "generate-template: dump logo") - templateFuncs := template.FuncMap{ - "dumpLogo": func() template.HTML { - return template.HTML(logo) - }, + // only read logo contents once when generating + logo, err := os.ReadFile(config.Documents.LogoPath) + util.Check(err, "generate-template: dump logo") + templateFuncs := template.FuncMap{ + "dumpLogo": func() template.HTML { + return template.HTML(logo) + }, "formatDateTime": func(t time.Time) string { return t.Format("2006-01-02 15:04:05") }, @@ -149,13 +149,13 @@ func generateTemplates(config types.Config, translator i18n.Translator) (*templa return translator.Translate(key) }, "translateWithData": func(key string) string { - data := struct{ - Name string - Link string - }{ - Name: config.Community.Name, - Link: config.Community.ConductLink, - } + data := struct { + Name string + Link string + }{ + Name: config.Community.Name, + Link: config.Community.ConductLink, + } return translator.TranslateWithData(key, i18n.TranslationData{data}) }, "capitalize": util.Capitalize, @@ -202,9 +202,9 @@ func (h RequestHandler) renderView(res http.ResponseWriter, viewName string, dat data.Title = strings.ReplaceAll(viewName, "-", " ") } - if h.config.Community.Name != "" { - data.ForumName = h.config.Community.Name - } + if h.config.Community.Name != "" { + data.ForumName = h.config.Community.Name + } if data.ForumName == "" { data.ForumName = "Forum" } @@ -495,9 +495,9 @@ func (h RequestHandler) RegisterRoute(res http.ResponseWriter, req *http.Request return } - rules := util.Markup(template.HTML(h.files["rules"])) - verification := util.Markup(template.HTML(h.files["verification-instructions"])) - conduct := h.config.Community.ConductLink + rules := util.Markup(template.HTML(h.files["rules"])) + verification := util.Markup(template.HTML(h.files["verification-instructions"])) + conduct := h.config.Community.ConductLink var verificationCode string renderErr := func(errFmt string, args ...interface{}) { errMessage := fmt.Sprintf(errFmt, args...) @@ -512,7 +512,7 @@ func (h RequestHandler) RegisterRoute(res http.ResponseWriter, req *http.Request verificationCode, err = h.session.GetVerificationCode(req) // we had an error getting the verification code, generate a code and set it on the session if err != nil { - prefix := util.VerificationPrefix(h.config.Community.Name) + prefix := util.VerificationPrefix(h.config.Community.Name) verificationCode = fmt.Sprintf("%s%06d\n", prefix, crypto.GenerateVerificationCode()) err = h.session.SaveVerificationCode(req, res, verificationCode) if err != nil { @@ -773,31 +773,31 @@ func NewServer(allowlist []string, sessionKey, dir string, config types.Config) dbpath := filepath.Join(s.directory(), "forum.db") db := database.InitDB(dbpath) - config.EnsureDefaultPaths() - // load the documents specified in the config - // iff document doesn't exist, dump a default document where it should be and read that - type triple struct { key, docpath, content string } - triples := []triple{ - {"about", config.Documents.AboutPath, defaults.DEFAULT_ABOUT}, - {"rules", config.Documents.RegisterRulesPath, defaults.DEFAULT_RULES}, - {"verification-instructions", config.Documents.VerificationExplanationPath, defaults.DEFAULT_VERIFICATION}, - {"logo", config.Documents.LogoPath, defaults.DEFAULT_LOGO}, - } - - files := make(map[string][]byte) - for _, t := range triples { - data, err := util.LoadFile(t.key, t.docpath, t.content) - if err != nil { - return s, err - } - files[t.key] = data - } - - // TODO (2022-10-20): when receiving user request, inspect user-agent language and change language from server default - // for currently translated languages, see i18n/i18n.go - translator := i18n.Init(config.Community.Language) - templates := template.Must(generateTemplates(config, translator)) - handler := RequestHandler{&db, session.New(sessionKey, developing), allowlist, files, config, translator, templates} + config.EnsureDefaultPaths() + // load the documents specified in the config + // iff document doesn't exist, dump a default document where it should be and read that + type triple struct{ key, docpath, content string } + triples := []triple{ + {"about", config.Documents.AboutPath, defaults.DEFAULT_ABOUT}, + {"rules", config.Documents.RegisterRulesPath, defaults.DEFAULT_RULES}, + {"verification-instructions", config.Documents.VerificationExplanationPath, defaults.DEFAULT_VERIFICATION}, + {"logo", config.Documents.LogoPath, defaults.DEFAULT_LOGO}, + } + + files := make(map[string][]byte) + for _, t := range triples { + data, err := util.LoadFile(t.key, t.docpath, t.content) + if err != nil { + return s, err + } + files[t.key] = data + } + + // TODO (2022-10-20): when receiving user request, inspect user-agent language and change language from server default + // for currently translated languages, see i18n/i18n.go + translator := i18n.Init(config.Community.Language) + templates := template.Must(generateTemplates(config, translator)) + handler := RequestHandler{&db, session.New(sessionKey, developing), allowlist, files, config, translator, templates} /* note: be careful with trailing slashes; go's default handler is a bit sensitive */ // TODO (2022-01-10): introduce middleware to make sure there is never an issue with trailing slashes diff --git a/types/types.go b/types/types.go @@ -1,14 +1,14 @@ package types import ( - "path/filepath" + "path/filepath" ) type Config struct { Community struct { Name string `json:"name"` ConductLink string `json:"conduct_url"` // optional - Language string `json:"language"` + Language string `json:"language"` } `json:"general"` Documents struct { @@ -21,18 +21,18 @@ type Config struct { // Ensure that, at the very least, default paths exist for each expected document path. Does not overwrite previously set values. func (c *Config) EnsureDefaultPaths() { - if c.Documents.AboutPath == "" { - c.Documents.AboutPath = filepath.Join("content", "about.md") - } - if c.Documents.RegisterRulesPath == "" { - c.Documents.RegisterRulesPath = filepath.Join("content", "rules.md") - } - if c.Documents.VerificationExplanationPath == "" { - c.Documents.VerificationExplanationPath = filepath.Join("content", "verification-instructions.md") - } - if c.Documents.LogoPath == "" { - c.Documents.LogoPath = filepath.Join("content", "logo.html") - } + if c.Documents.AboutPath == "" { + c.Documents.AboutPath = filepath.Join("content", "about.md") + } + if c.Documents.RegisterRulesPath == "" { + c.Documents.RegisterRulesPath = filepath.Join("content", "rules.md") + } + if c.Documents.VerificationExplanationPath == "" { + c.Documents.VerificationExplanationPath = filepath.Join("content", "verification-instructions.md") + } + if c.Documents.LogoPath == "" { + c.Documents.LogoPath = filepath.Join("content", "logo.html") + } } /* diff --git a/util/util.go b/util/util.go @@ -3,18 +3,18 @@ package util import ( "bytes" "encoding/base64" - "regexp" "encoding/hex" "encoding/json" "errors" - "io/fs" - "path/filepath" "fmt" "html/template" + "io/fs" "log" "net/http" "net/url" "os" + "path/filepath" + "regexp" "strconv" "strings" @@ -105,14 +105,14 @@ func SanitizeStringStrict(s string) string { return strictContentGuardian.Sanitize(s) } -func VerificationPrefix (name string) string { - pattern := regexp.MustCompile("A|E|O|U|I|Y") - upper := strings.ToUpper(name) - replaced := string(pattern.ReplaceAll([]byte(upper), []byte(""))) - if len(replaced) < 3 { - replaced += "XYZ" - } - return replaced[0:3] +func VerificationPrefix(name string) string { + pattern := regexp.MustCompile("A|E|O|U|I|Y") + upper := strings.ToUpper(name) + replaced := string(pattern.ReplaceAll([]byte(upper), []byte(""))) + if len(replaced) < 3 { + replaced += "XYZ" + } + return replaced[0:3] } func GetThreadSlug(threadid int, title string, threadLen int) string { @@ -156,10 +156,10 @@ func Capitalize(s string) string { } func CreateIfNotExist(docpath, content string) (bool, error) { - err := os.MkdirAll(filepath.Dir(docpath), 0750) - if err != nil { - return false, err - } + err := os.MkdirAll(filepath.Dir(docpath), 0750) + if err != nil { + return false, err + } _, err = os.Stat(docpath) if err != nil { // if the file doesn't exist, create it @@ -195,17 +195,16 @@ func ReadConfig(confpath string) types.Config { } func LoadFile(key, docpath, defaultContent string) ([]byte, error) { - ed := Describe("load file") - _, err := CreateIfNotExist(docpath, defaultContent) - err = ed.Eout(err, "create if not exist (%s) %s", key, docpath) - if err != nil { - return nil, err - } - data, err := os.ReadFile(docpath) - err = ed.Eout(err, "read %s", docpath) - if err != nil { - return nil, err - } - return data, nil + ed := Describe("load file") + _, err := CreateIfNotExist(docpath, defaultContent) + err = ed.Eout(err, "create if not exist (%s) %s", key, docpath) + if err != nil { + return nil, err + } + data, err := os.ReadFile(docpath) + err = ed.Eout(err, "read %s", docpath) + if err != nil { + return nil, err + } + return data, nil } -