From e158af99f171ac892531092e4ab5f03eb00cdc36 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Wed, 4 Mar 2026 16:34:19 -0700 Subject: [PATCH] loading user agreement from a "resource" now --- TODO.md | 2 ++ config/config.go | 28 +++++++++++++++++-- config/default.yaml | 5 ++-- login.go | 8 ++++++ main.go | 11 ++++---- ui/resources/useragreement.html | 37 +++++++++++++++++++++++++ ui/setup.go | 1 + ui/static.go | 19 +++++++++++++ ui/templates.go | 49 ++++++++++++++------------------- ui/views/agreement.jet | 6 ++-- 10 files changed, 124 insertions(+), 42 deletions(-) create mode 100644 ui/resources/useragreement.html diff --git a/TODO.md b/TODO.md index 72c9ec3..1a71e75 100644 --- a/TODO.md +++ b/TODO.md @@ -7,6 +7,8 @@ After the point where it reaches feature parity with Venice circa 2006. * Policy page support. * User agreement in a separate file rather than directly in settings. * Support all customizations that were done with the EMinds instance of Venice. +* Gitea-like status page showing Go-specific internals. +* Build static Tailwind CSS file rather than using remote-loaded version. (Gate on debug/prod flag) ## Immediate Cleanups Required diff --git a/config/config.go b/config/config.go index ae13c89..5b62b2b 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,7 @@ import ( "errors" "fmt" "io" + "io/fs" "os" "path/filepath" "reflect" @@ -68,7 +69,6 @@ type AmConfig struct { Title string `yaml:"title"` Text string `yaml:"text"` } `yaml:"userAgreement"` - ExternalPath string `yaml:"externalPath"` } `yaml:"site"` Database struct { Driver string `yaml:"driver"` @@ -91,13 +91,16 @@ type AmConfig struct { Disclaimer string `yaml:"disclaimer"` } `yaml:"email"` Rendering struct { - TemplateDir string `yaml:"templatedir"` CookieKey string `yaml:"cookiekey"` CountryList struct { Prioritize string `yaml:"prioritize"` } `yaml:"countryList"` VeniceCompatibleImageURLs bool `yaml:"veniceCompatibleImageURLs"` } `yaml:"rendering"` + Resources struct { + ViewTemplateDir string `yaml:"viewTemplateDir"` + ExternalContentPath string `yaml:"externalContentPath"` + } `yaml:"resources"` Posting struct { ExternalDictionary string `yaml:"externalDictionary"` Uploads struct { @@ -256,6 +259,27 @@ func parseDataSize(s string) (int32, error) { return int32(rc), nil } +// AmOpenExternalContentPath opens the "external content path" specified in the configuration as a root filesystem. +func AmOpenExternalContentPath() (fs.FS, error) { + if GlobalConfig.Resources.ExternalContentPath == "" { + return nil, nil + } + finfo, err := os.Stat(GlobalConfig.Resources.ExternalContentPath) + if err != nil { + log.Errorf("external content path \"%s\" is not valid, ignored (%v)", GlobalConfig.Resources.ExternalContentPath, err) + return nil, nil + } + if !finfo.IsDir() { + log.Errorf("external content path \"%s\" is not a directory, ignored", GlobalConfig.Resources.ExternalContentPath) + return nil, nil + } + root, err := os.OpenRoot(GlobalConfig.Resources.ExternalContentPath) + if err != nil { + return nil, err + } + return root.FS(), nil +} + // SetupConfig loads the command line arguments, loads the config file, and prepares GlobalConfig. func SetupConfig() { argparse.MustParse(&CommandLine) diff --git a/config/default.yaml b/config/default.yaml index 45dd3ee..d27ab46 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -17,7 +17,6 @@ site: title: "Amsterdam User Agreement" text: > Text of this agreement is TBD. - externalPath: "" database: driver: "mysql" dsn: "amsdb:x00yes2k@tcp(localhost)/amsterdam?parseTime=true&loc=UTC" @@ -40,11 +39,13 @@ email: The Amsterdam Project is not responsible for the contents of this message Report abuses to: rendering: - templatedir: custom_templates cookiekey: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz countryList: prioritize: US veniceCompatibleImageURLs: false +resources: + viewTemplateDir: "" + externalContentPath: "" posting: externalDictionary: "" uploads: diff --git a/login.go b/login.go index 99b6eff..19c7972 100644 --- a/login.go +++ b/login.go @@ -315,8 +315,16 @@ func NewAccountUserAgreement(ctxt ui.AmContext) (string, any) { return "error", ENOACCOUNT } + // Load the user agreement from the resources. + agreementTitle, agreementBody, err := ui.AmLoadHTMLResource("useragreement.html") + if err != nil { + return "error", err + } + ctxt.SetLeftMenu("top") ctxt.VarMap().Set("target", target) + ctxt.VarMap().Set("agreementTitle", agreementTitle) + ctxt.VarMap().Set("agreementBody", agreementBody) ctxt.SetScratch("frame_suppressLogin", true) ctxt.SetFrameTitle("New Account User Agreement") return "framed", "agreement.jet" diff --git a/main.go b/main.go index 81e9d63..10541e2 100644 --- a/main.go +++ b/main.go @@ -53,12 +53,11 @@ func setupEcho() *echo.Echo { uiset := []echo.MiddlewareFunc{ui.SessionStoreInjector, ui.ContextCreator, ui.IPBanTest, ui.CookieLoginTest} e.RouteNotFound("/*", ui.AmWrap(AmNotFoundHandler), uiset...) - if config.GlobalConfig.Site.ExternalPath != "" { - root, err := os.OpenRoot(config.GlobalConfig.Site.ExternalPath) - if err != nil { - panic(err) - } - fs := root.FS() + fs, err := config.AmOpenExternalContentPath() + if err != nil { + panic(err) + } + if fs != nil { e.StaticFS("/ext", fs) e.GET("/fext/*", ui.AmWrap(ui.AmStaticFramePage(fs, "/fext/")), uiset...) } diff --git a/ui/resources/useragreement.html b/ui/resources/useragreement.html new file mode 100644 index 0000000..bc8ab91 --- /dev/null +++ b/ui/resources/useragreement.html @@ -0,0 +1,37 @@ + + + Amsterdam User Agreement + + +

This is a "sample" user agreement, intended as a default. Amsterdam sites are encouraged to provide their + own version of the user agreement.

+

By creating a user account, you agree to abide by the following rules regarding your conduct on + our system. These rules may also be found as part of our Policy page.

+

Your messages are your words, and you are responsible for them, as well as your overall behavior as a + member of the community. As a member of the community, you agree not to post messages that harass, solicit, + threaten, offend, embarrass or impersonate any other person or that disrupt the dialogue in the community, + or use this site to do any of the above. In addition, you agree not to post messages that violate other persons' + intellectual property, privacy or other rights.

+

When you post a message to this site, or to any community hosted on this server, you grant the site + the right to display your message on the page on which it was posted by you. It may also be displayed on + other pages on the site, or be reachable through searches or other means, but it will always and only be here, + on this server, and we have no intention of ever reusing, reprinting, or recreating your message anywhere else. + You lose no copyright to your words, and you are not beholden to us in any way, shape, or form.

+

By posting here, you also grant us (the moderator(s) of the conference to which you post, the host and + cohost(s) of the community in which that conference is located, or the administrators of the site) the right to + remove your message if we choose not to want it here. We do not edit messages, except in extreme cases of + messages which include HTML or other characters that damage the usability of the site. We do remove + messages that are inappropriate or offensive to the admins. However, in the case of messages or message + attachments that contain copyrighted information or data that has been distributed in violation of + copyright, we are required by law to delete these messages if and when the legitimate copyright holder + contacts us, gives us the name and/or type of information that was posted and the location it was posted + to on this site, and requests that it be removed. This policy is the direct result of the United + States Digital Millennium Copyright Act, and neither this site nor the Amsterdam team is responsible for + this law or its effects.

+

The administrators of this site are not responsible for the accuracy or integrity of the information + contained in any messages posted to the site, and will not be held liable for any damages of any kind + incurred as the result of any message posted on this site. Some messages and site content may contain links + to sites on the Internet and World Wide Web other than this site itself. The administrators are + not responsible for the content of these other sites, and will not be held liable for any of their content.

+ + diff --git a/ui/setup.go b/ui/setup.go index 42e9b0c..242a279 100644 --- a/ui/setup.go +++ b/ui/setup.go @@ -17,6 +17,7 @@ func SetupUILayer() func() { exitfuncs := make([]func(), 0, 2) setupTemplates() setupMenuCache() + setupResources() exitfuncs = append(exitfuncs, setupSessionManager()) exitfuncs = append(exitfuncs, setupContext()) return func() { diff --git a/ui/static.go b/ui/static.go index f64cdcb..44f53bd 100644 --- a/ui/static.go +++ b/ui/static.go @@ -16,6 +16,7 @@ import ( "io" "io/fs" "net/http" + "path/filepath" "strings" "github.com/labstack/echo/v4" @@ -25,6 +26,13 @@ import ( //go:embed static/* var static_data embed.FS +//go:embed resources/* +var static_resources embed.FS + +func setupResources() { + // do nothing yet +} + // AmStaticFileHandler returns a handler for the files in the static embedded filesystem. func AmStaticFileHandler() echo.HandlerFunc { fsys, err := fs.Sub(static_data, "static") @@ -85,6 +93,17 @@ func breakUpHTML(r io.Reader) (string, string, error) { return title, body, nil } +// AmLoadHTMLResource loads an HTML resource and splits it into title and body. +func AmLoadHTMLResource(resourceName string) (string, string, error) { + f, err := static_resources.Open(filepath.Join("resources", resourceName)) + if err != nil { + return "", "", err + } + title, body, err := breakUpHTML(f) + f.Close() + return title, body, err +} + /* AmStaticFramePage generates a handler that will serve up data from an external filesystem "framed" inside * the frame with the template engine. * Parameters: diff --git a/ui/templates.go b/ui/templates.go index d618ad7..64a70fe 100644 --- a/ui/templates.go +++ b/ui/templates.go @@ -15,6 +15,7 @@ import ( "errors" "fmt" "io" + "os" "reflect" "regexp" "strconv" @@ -202,28 +203,7 @@ func displayMemberCount(a jet.Arguments) reflect.Value { // displayFullName extracts a full name from a contact record. func displayFullName(a jet.Arguments) reflect.Value { ci := a.Get(0).Convert(reflect.TypeFor[*database.ContactInfo]()).Interface().(*database.ContactInfo) - var rc strings.Builder - if ci.Prefix != nil && *ci.Prefix != "" { - rc.WriteString(*ci.Prefix) - rc.WriteString(" ") - } - if ci.GivenName != nil && *ci.GivenName != "" { - rc.WriteString(*ci.GivenName) - } - if ci.MiddleInit != nil && *ci.MiddleInit != "" { - rc.WriteString(" ") - rc.WriteString(*ci.MiddleInit) - rc.WriteString(".") - } - if ci.FamilyName != nil && *ci.FamilyName != "" { - rc.WriteString(" ") - rc.WriteString(*ci.FamilyName) - } - if ci.Suffix != nil && *ci.Suffix != "" { - rc.WriteString(" ") - rc.WriteString(*ci.Suffix) - } - return reflect.ValueOf(rc.String()) + return reflect.ValueOf(ci.FullName(true)) } // displayExpandCat displays a category expanded into a hierarchy. @@ -305,13 +285,24 @@ func postRewrite(a jet.Arguments) reflect.Value { // setupTemplates is called to set up the template renderer after the configuration is loaded. func setupTemplates() { - views = jet.NewSet( - multi.NewLoader( - jet.NewOSFileSystemLoader(config.GlobalConfig.Rendering.TemplateDir), - embedfs.NewLoader("views/", static_views), - ), - jet.DevelopmentMode(true), - ) + // Set up the template loaders: the optional filesystem loader, then the embedded loader. + templateLoaders := make([]jet.Loader, 0, 2) + if config.GlobalConfig.Resources.ViewTemplateDir != "" { + finfo, err := os.Stat(config.GlobalConfig.Resources.ViewTemplateDir) + if err == nil { + if finfo.IsDir() { + templateLoaders = append(templateLoaders, jet.NewOSFileSystemLoader(config.GlobalConfig.Resources.ViewTemplateDir)) + } else { + log.Errorf("view template directory %s is not a directory, ignored", config.GlobalConfig.Resources.ViewTemplateDir) + } + } else { + log.Errorf("view template directory %s is not valid, ignored (%v)", config.GlobalConfig.Resources.ViewTemplateDir, err) + } + } + templateLoaders = append(templateLoaders, embedfs.NewLoader("views/", static_views)) + + // Create the template renderer and add our globals to it. + views = jet.NewSet(multi.NewLoader(templateLoaders...), jet.DevelopmentMode(true)) views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION) views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT) views.AddGlobal("GlobalConfig", config.GlobalConfig) diff --git a/ui/views/agreement.jet b/ui/views/agreement.jet index 77d9515..7b7b1b4 100644 --- a/ui/views/agreement.jet +++ b/ui/views/agreement.jet @@ -1,6 +1,6 @@ {* * Amsterdam Web Communities System - * Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved + * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -9,10 +9,10 @@
-

{{ GlobalConfig.Site.UserAgreement.Title }}

+

{{ agreementTitle }}


- {{ GlobalConfig.Site.UserAgreement.Text | raw }} + {{ agreementBody | raw }}