Documentation
¶
Overview ¶
Package webthings runs a website's backend: database, accounts, pages, media, site settings, the admin and the selected modules. The website itself only supplies templates (or its own Renderer) and assets.
app := webthings.New(webthings.Config{Name: "My site", Templates: os.DirFS("templates")})
app.Use(publishing.Module("news", "News"))
if err := app.ListenAndServe(ctx, ":8080"); err != nil {
log.Fatal(err)
}
ListenAndServe returns nil once ctx is cancelled and the app has shut down, so a clean stop, for example on SIGTERM, exits without an error.
Routing ¶
Once started, every request passes one go-router, the app's root router (App.ServeHTTP):
- Around it, with UseAll, the request-wide middleware: the visitor's address (Config.TrustedProxies), the request ID (RequestID), the security headers X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin, which every response gets, also go-router's own redirects to clean paths, telemetry, the access log and metrics for every request but those to the endpoints for operations, and recovery from panics, which they see as a 500.
- The endpoints for operations: /healthz, /readyz and /metrics.
- Behind compression, the core routes: the admin, whose router is mounted at Config.AdminPath, so its routes and their names are the root router's, the media library's routes at /media, named media.asset and media.variant, sitemap.xml, robots.txt, /assets, /theme and /_/webthings.css.
- The modules' visitor actions, each module's action router (Env.Actions) mounted at /_/<key>/ behind the protection against cross-site posts, the framing headers of the site setting (site.Settings.Framing) and the body limit (Parts.ActionBodyLimit); its routes become part of the root router.
- With Config.API, the content API at /api/v1/ (package api), with each module's endpoints (Parts.API) mounted at /api/v1/<key>/, behind its rate limit and CORS, the output cache, compression and an ETag.
- The site, as the catch-all "/", behind the framing headers of the site setting. Pages live in the database and can move, so the site resolves the page itself and serves the module on it there: each module's Parts.Public, a go-router of its own from frontend.Router, whose routes are relative to the page, served at the page's address for that request (go-router's ServeAt), with the routes of its Atom and RSS feeds (Parts.Feeds). The site's requests pass the editors' recognition and the modules' middleware (Parts.Middleware) first, then the output cache, which keeps pages for visitors in memory, compressed (see Config.OutputCacheTTL), then compression.
Before Start a small router answers instead: the endpoints for operations, with request IDs and recovery, and 503 for everything else. Every registration of the root router but the site's is labelled with its route class, such as admin, media or asset: they are CorePaths, known from New on, and the metrics count requests by them.
Index ¶
- Constants
- Variables
- func LogHandler(handler slog.Handler) slog.Handler
- func RequestID(ctx context.Context) string
- type App
- func (a *App) AdminPath() string
- func (a *App) AdminURL(name string, args ...any) string
- func (a *App) Changed()
- func (a *App) Close() error
- func (a *App) CorePaths() []string
- func (a *App) InviteAdministrator(ctx context.Context, email string) (string, error)
- func (a *App) IsCorePath(path string) bool
- func (a *App) ListenAndServe(ctx context.Context, addr string) error
- func (a *App) Metrics() http.Handler
- func (a *App) PurgeTrash(ctx context.Context) (int, error)
- func (a *App) Ready(ctx context.Context) error
- func (a *App) RunSchedules(ctx context.Context) (int, error)
- func (a *App) Serve(ctx context.Context, listener net.Listener) error
- func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (a *App) Service(key string) any
- func (a *App) SetupToken() string
- func (a *App) Start(ctx context.Context) error
- func (a *App) TemplateKeys() []string
- func (a *App) TranslationCatalog() (*extract.Catalog, error)
- func (a *App) Translations(dir string, args []string) error
- func (a *App) Use(modules ...Module) error
- func (a *App) WriteTemplates(dir, key string) ([]string, error)
- type Config
- type Env
- func (e Env) AbsoluteURL(r *http.Request, path string) string
- func (e Env) Actions() *router.Router
- func (e Env) AdminURL(name string, args ...any) string
- func (e Env) Changed()
- func (e Env) IsCorePath(path string) bool
- func (e Env) Links() *links.Check
- func (e Env) PersonalCookie(name string)
- func (e Env) Secret(ctx context.Context, name string) ([]byte, error)
- func (e Env) Service(key string) any
- func (e Env) VisitorID(ctx context.Context, r *http.Request, parts ...string) (string, error)
- type Module
- type Parts
- type Telemetry
Constants ¶
const DefaultActionBodyLimit = 1 << 20
DefaultActionBodyLimit is the largest request body modules' visitor actions (Env.Actions) accept unless Parts.ActionBodyLimit says otherwise: 1 MiB, ample for a form.
Variables ¶
var ErrNotStarted = errors.New("the app has not started")
ErrNotStarted is returned by what needs a started app (see Start), such as InviteAdministrator.
var ErrTemplates = errors.New("templates have problems")
ErrTemplates is the error Start returns for templates that fail their renders: in development for every problem of every theme, otherwise for a template of the active theme's layout, regions or partials, which every page parses, that does not parse or calls a function the site does not have; see Config.Development.
Functions ¶
func LogHandler ¶
LogHandler wraps a log handler so every record logged with a request's context, as slog.InfoContext(r.Context(), …) does, carries the request's ID as request_id. Start installs it itself with Config.LogFormat; a site with a logger of its own wraps that one:
slog.SetDefault(slog.New(webthings.LogHandler(handler)))
It is go-router's middleware.LogHandler.
func RequestID ¶
RequestID returns the ID of the request ctx belongs to: the X-Request-ID header a proxy sent, when it is a sensible one (at most 64 letters, digits and -_.:), or one WebThings made. Every response names it in X-Request-ID; empty outside a request. It is go-router's middleware.GetRequestID.
Types ¶
type App ¶
type App struct {
// DB is the database, for a site's own tables and queries.
DB *db.DB
// Auth holds the accounts, sessions and groups of editors.
Auth *auth.Service
// Media is the media library of uploaded files.
Media *media.Service
// Site holds the Site settings, such as the title, languages and theme.
Site *site.Service
// Pages is the page tree with its revisions.
Pages *pages.Service
// Frontend renders the public site with the active theme.
Frontend *frontend.Frontend
// Links knows which published content links to which address on the
// site, the pages' and the modules' (Parts.Links).
Links *links.Check
// contains filtered or unexported fields
}
App is a running site: the core services, the modules chosen with Use, the admin and the public site, served as one http.Handler. Create it with New, choose modules with Use, then Start it, or let ListenAndServe do both. The services in its exported fields are ready once Start has returned.
func New ¶
New returns an App for config, with the defaults filled in: the name "WebThings", state in "var", SQLite in var/site.sqlite, English as the only language and the admin at "/admin". It opens nothing yet; Start does, so New cannot fail.
func (*App) AdminPath ¶
AdminPath is where the admin is served, Config.AdminPath, such as "/admin". Code links into the admin by route name (Env.AdminURL), not from this path; tests and tools use it to reach the admin.
func (*App) AdminURL ¶
AdminURL is the address of a named admin route, as Env.AdminURL builds it, for the site's own code, tests and tools: AdminURL("admin.login"), AdminURL("admin.pages.edit", "id", id). It works once the app has started; before, it returns "".
func (*App) Changed ¶
func (a *App) Changed()
Changed tells the app that what visitors see changed outside a request that tells it already, such as content the site's own code wrote to a module's store or a module's service from Go, or data the site's pages show from elsewhere: the output cache, which keeps public pages in memory, renders them again. The app tells it itself after every successful post to the admin and to the site and its actions that does not say it changed nothing (frontend.NothingChanged), after scheduled publication (see RunSchedules) and after the writes of the bundled modules' stores and services, also those made from Go. A change it misses shows when the output cache's time has passed (site.Settings.OutputCache).
func (*App) Close ¶
Close stops the background work (scheduler, cleanup and the variants of uploaded images, see media.Service.Prepare) and the modules, last added first, then closes storage and the database.
func (*App) CorePaths ¶
CorePaths are the addresses the app serves itself, next to the site's pages: the admin (Config.AdminPath), visitor actions (/_/), media, the site's assets, theme files, sitemap.xml, robots.txt, the endpoints for operations (/healthz, /readyz and /metrics) and, while Config.API turns it on, the content API (/api/v1). Requests below most of them never reach module middleware, so modules use Env.IsCorePath to keep away from them, for example a redirect that could never fire or a statistic of a file. They are the root router's own routes, labelled with the class the metrics count their requests under, so they and the routes cannot drift apart; they are known from New on. The admin comes first.
func (*App) InviteAdministrator ¶
InviteAdministrator creates an administrator account for email and returns a link with which its owner chooses a password: the admin's password reset page with a new reset token, which works for an hour (auth.ResetLifetime). The account gets no password anyone knows, so it cannot be signed in to before then. The link is absolute on Config.BaseURL, or the path below the site's address without one, such as /admin/reset-password?token=….
It is for whoever runs the site, such as a command line that adds the first administrator without the setup link (see SetupToken), and works once the app has started, also on a site with accounts. It returns auth.ErrEmail for an address that is no address, auth.ErrDuplicate for one that has an account, and ErrNotStarted before Start. Give the link only to the owner of the address: it opens the account.
func (*App) IsCorePath ¶
IsCorePath reports whether path is one of CorePaths or below one of them: "/theme" and "/theme/site.css" are, "/themes" is not. It asks the root router which registration receives path.
func (*App) ListenAndServe ¶
ListenAndServe starts the app (unless Start was already called, for example to import content first) and serves on addr until ctx is cancelled; see Serve.
func (*App) Metrics ¶
Metrics serves the app's metrics in the Prometheus text format: requests by route class, method and status with their durations, renders by kind with theirs, the database's connection pool, the output cache (hits, misses, skips, evictions, entries and bytes) and a few of the process. The site serves it at /metrics with Config.MetricsToken; without a token a site can serve it itself, for example on an address only its monitoring reaches.
func (*App) PurgeTrash ¶
PurgeTrash deletes for good the items that have been in the admin's trash longer than Site settings › Trash allows (site.Settings.TrashDays), in every section, as the background cleanup does every hour, and returns how many it deleted. Every item goes the way Delete selected for good takes it, with its revisions and what else belongs to it (see admin.Admin.PurgeTrash). While the setting is off, the default, it deletes nothing. Several processes may run it at once: each item is deleted by one of them.
func (*App) Ready ¶
Ready reports whether the app can serve: it has started, the database answers, every schema it applied is still applied (another version of the site may have moved the database on, or it was restored from an old backup) and the media storage can be reached, when it can tell (see media.Pinger). /readyz answers 200 when it returns nil, 503 otherwise.
func (*App) RunSchedules ¶
RunSchedules publishes and unpublishes the pages and module content whose scheduled time has come, as the background scheduler does every ScheduleInterval, and returns the number of changes. Every change has the same effects as publishing by hand. Several processes may run it at once: each change is made by one of them.
func (*App) Serve ¶
Serve is ListenAndServe on a listener the caller opened: for example one handed over by systemd socket activation, or one on port 0, whose address a test knows before the app serves. It starts the app unless Start was already called, serves until ctx is cancelled, lets running requests finish, and then closes the listener and the app.
func (*App) ServeHTTP ¶
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP serves the site through the root router once the app has started; before that only the endpoints for operations answer, and every other address with 503 (see idleRoutes).
func (*App) Service ¶
Service returns the API of a started module instance, for the website's own code. Module packages offer typed helpers, for example publishing.Of(app, "news").
func (*App) SetupToken ¶
SetupToken is the secret of this start's first-run setup link, <AdminPath>/setup?token=<SetupToken>, which Start makes when the site has no accounts and logs; empty when accounts existed. A new one is made at every start, and the setup answers 404 once any account exists. Tests and tools that create the first account through the admin use it.
func (*App) Start ¶
Start opens the database, applies schemas and builds the site and admin. Without accounts it logs the link to the first-run setup (see SetupToken).
func (*App) TemplateKeys ¶
TemplateKeys lists the keys WriteTemplates accepts: "site", then the keys of the modules with templates, in the order they were added. A site's command line can show them when no key is given.
func (*App) TranslationCatalog ¶
TranslationCatalog is every text the site translates, found with go-translator's extract package. First come the texts of every template in Config.Templates. Then, in the order of their references, come those of the default templates of the modules (Module.Templates, Module.Components and Module.Mail) and of the core components that not every theme replaces, referenced as module:<type>/<name>.gohtml, module:<type>/mail/<name>.gohtml and core:site/<name>.gohtml, and the texts Go code translates: core's, such as the 404 page's title "Not found" and the networks' names of share links and profiles (frontend.CoreTexts), as core:texts, the names of months and days and the date patterns, with their contexts (frontend.CalendarTexts), as core:calendar, which a catalogue may translate to replace the built-in ones and must for a language without built-in ones, and the texts of the modules' visitor forms (Module.Forms, go-form's Texts) as module:<type>/forms. It needs no Start.
func (*App) Translations ¶
Translations is the "translations" command of a site's main, with dir its translations folder (where the .po files are):
go run . translations writes dir/translations.pot go run . translations check lists the texts each .po file misses go run . translations check --csv the same as CSV
The check returns translations.ErrMissing when a language misses texts, so the command fails. It reads TranslationCatalog and needs no Start.
func (*App) WriteTemplates ¶
WriteTemplates writes starting points for templates into dir: for a module instance key the module's templates and components in dir/<key>/ and its mail templates in dir/mail/<key>/, for "site" the layout with its head region (regions/head.gohtml, core's tags and room for the theme's own; see frontend.HeadScaffold), the stylesheet the layout links (assets/site.css), the page, 404 and search templates and core's components in dir/site/. Existing files are never overwritten. It returns the files it wrote and needs no Start.
type Config ¶
type Config struct {
// Name is the initial site title and the admin title.
Name string
// StateDir holds the SQLite database and uploaded media, and may hold
// translations/<locale>.po files that override the admin's own texts.
StateDir string
// Driver is sqlite (default; "sqlite3" is accepted too), pgx or mysql.
// SQLite is pure Go, so no build needs cgo. An empty DSN with SQLite uses
// site.sqlite in StateDir; db.Open adds foreign keys, a busy timeout and
// the other settings WebThings needs to any SQLite DSN that lacks them.
Driver string
// DSN is the data source name the driver connects with, such as
// "postgres://user:pass@host/site"; see Driver for SQLite's default.
DSN string
// Locales are the initial content languages, used on first start only.
// Later changes happen in the admin's site settings.
Locales locales.Config
// AdminLanguage is the admin UI language, for example "en" or "nl";
// "en" when empty.
AdminLanguage string
// AdminPath is where the admin is served, such as "/admin" (the default,
// when empty) or "/toolbox": a leading slash, no trailing slash,
// letters, digits and - . _ ~ only, and no "." or ".." segment. The
// admin, the core sections and the modules link to its pages by route
// name (see Env.AdminURL), so
// nothing depends on it, and robots.txt does not name it: admin pages
// answer with X-Robots-Tag: noindex instead. It cannot be one of the
// other core paths (see App.CorePaths), such as /media.
AdminPath string
// AdminEmail and AdminPassword create the first admin account on an empty
// database. They are ignored once any account exists. Without them, a
// start without accounts logs a one-time link to the first-run setup,
// <AdminPath>/setup?token=… on BaseURL or the address ListenAndServe listens
// on, where the first administrator account is created (see
// App.SetupToken). The setup answers 404 without that link's token and
// once any account exists.
AdminEmail string
AdminPassword string
// Insecure allows session cookies over plain HTTP, for local development.
Insecure bool
// Authorize decides admin permissions. Nil applies the built-in rules:
// administrators may do everything, editors what their groups' matrix
// allows (see auth.Group). When set, it decides every permission check,
// for administrators too. The Accounts and Groups screens always need the
// administrator role as well, and Authorize is asked about them too, so
// it can keep them from administrators but never open them to editors.
// It must be safe for concurrent use: requests and background work ask
// it at the same time.
Authorize func(ctx context.Context, user auth.User, permission admin.Permission) bool
// Templates are the public site's templates: one folder per theme, such as
// templates/default/, or a single theme at the root (see frontend.Themes).
Templates fs.FS
// Translations are the site's own translations of the texts of its
// templates and of the modules' default templates and visitor forms,
// usually frontend.Translations("translations"): templates get its tl,
// tn, ctl and ctn, and core translates with it its own texts (such as
// the 404 page's title), go-form's labels and messages and the names of
// months and days and the date patterns. Those replace the built-in ones
// in every language, English and Dutch included; what it leaves out is
// built in, or English for a language without built-in ones (see
// frontend.BuiltInCalendar). Nil shows every text as written.
// App.Translations extracts the texts to translate.
Translations *translator.Translator
// Funcs are added to the templates.
Funcs []template.FuncMap
// Renderer replaces Templates with another template engine; see
// frontend.Renderer for what it renders and frontend.TemplateFuncs for
// the template functions. Funcs and the modules' functions still serve
// components, mail and the site's translations.
Renderer frontend.Renderer
// ReloadTemplates reads Templates again for every request, so edits to
// template files show without a restart; for development. Otherwise
// every template is parsed once.
ReloadTemplates bool
// Assets are served at /assets/ (the route "assets") like the files of
// an ImportMap of their own: templates get {{importmap_url "file"}}
// for a file's versioned URL, which may be cached for a year, and a
// plain /assets/<file> revalidates with a strong ETag. They are
// fingerprinted at Start, and again after a change while templates
// reload. Set Assets or ImportMap, not both.
Assets fs.FS
// ImportMap is the site's import map of go-importmap, such as one of
// packages vendored with its importmap command and loaded with
// importmap.FromFS, which may hold the site's own scripts and
// stylesheets too. Its files are served at /assets/ (the route
// "assets") with versioned URLs and strong ETags, and templates get
// {{importmap}} for the head and {{importmap_url "file"}} for the
// versioned URL of one of its files.
//
// Core adds go-partial's browser clients to either import map, under
// the names scripts import them by: partial-flash, partial-sse and
// partial-interactions, unless the site maps such a name itself. The
// site needs no copy of them.
ImportMap *importmap.ImportMap
// FormTheme is the go-form theme the visitor forms of templates render
// with ({{form_field .Form "name" nil}} and the other form functions),
// such as one the site registers with go-form's
// templates.RegisterClassTheme with the classes of its CSS. Empty uses
// frontend.FormTheme, the classes of webthings.css.
FormTheme string
// BaseURL is the public address, for example "https://example.com", used in
// sitemap.xml, share links, {{absURL}} and links sent by e-mail. Empty
// uses the address of each request: its Host header, which the client
// chooses, and X-Forwarded-Proto and X-Forwarded-Host (or Forwarded)
// from TrustedProxies only. Set it in production; Start warns without it
// unless Insecure.
BaseURL string
// TrustedProxies are the reverse proxies (IP addresses or CIDR ranges,
// for example "127.0.0.1" or "10.0.0.0/8") whose X-Forwarded-For,
// X-Real-IP and RFC 7239 Forwarded (for=) headers name the visitor (see
// frontend.ClientIP) and whose X-Forwarded-Proto and X-Forwarded-Host
// headers, with Forwarded's proto= and host= as a fallback, name the
// address visitors used (see frontend.AbsoluteURL). Empty trusts none,
// so visitors are known by the address they connect from.
TrustedProxies []string
// Mail sends modules' e-mail; nil logs messages instead (mail.Log).
Mail mail.Sender
// Storage keeps uploaded media; nil stores files in StateDir/media.
Storage media.Storage
// CacheTTL is how long site settings and published routes are kept in
// memory: changes in this process apply at once, changes by other
// processes (several servers on one database) within this time. Zero
// means 30 seconds; negative disables the cache.
CacheTTL time.Duration
// OutputCacheTTL overrides the site setting of the output cache, which
// keeps the public pages visitors get in memory and serves them again
// without rendering them (see site.Settings.OutputCache): a positive
// time keeps them that long, also in Development, where responses say
// in X-Cache whether the cache answered (hit, miss or skip); negative
// turns the cache off. Zero follows the site setting, except that
// Development and ReloadTemplates turn the cache off. Site settings ›
// Performance then shows the time that applies, which editors cannot
// change there.
OutputCacheTTL time.Duration
// ScheduleInterval is how often scheduled publication changes are carried
// out. Zero means 30 seconds; negative stops the background scheduler, for
// example in tests that call RunSchedules themselves.
ScheduleInterval time.Duration
// Development shows failing templates in detail, with the template and
// line, on the site and in the admin, and makes {{debug .}} show a
// value; for local development only. Otherwise a failing template is
// logged and answered with the theme's 500.gohtml (or go-partial's
// plain error page when the theme has none), or in the admin with its own
// error page, and {{debug .}} shows nothing.
//
// Start checks every template of every theme (see ErrTemplates): in
// development it fails with every problem the check finds, and a
// template call that would show nothing, such as {{posts "nieuws" 3}}
// for an instance key the site does not have, an asset file the theme
// does not have or an unknown theme setting, fails the page. Otherwise
// the problems and such calls are logged at WARN, and Start fails only
// when every page of the active theme would fail.
Development bool
// LogFormat, "text" or "json", makes Start log to stderr in that format,
// at LogLevel and up, with the request ID (see RequestID) on every line
// logged with a request's context; Start makes it slog's default. Empty
// leaves logging to the site (see LogHandler).
LogFormat string
// LogLevel is the lowest level LogFormat logs; the zero value is info.
LogLevel slog.Level
// AccessLog logs a line for every request: method, path without the
// query, status, size, duration and request ID.
AccessLog bool
// MetricsToken serves the metrics (see App.Metrics) at /metrics to
// requests that carry it as "Authorization: Bearer <token>", such as
// the output cache's counters:
//
// curl -H "Authorization: Bearer $TOKEN" https://example.com/metrics | grep output_cache
//
// Empty serves no metrics at /metrics: it answers 404. /healthz and
// /readyz are always served (see App.Ready).
MetricsToken string
// Telemetry plugs in tracing and other instrumentation.
Telemetry Telemetry
// ContentSecurityPolicy overrides the site setting of the
// Content-Security-Policy (site.Settings.ContentSecurity), for tests
// and deployments that decide it in code: "off", "report-only" or
// "enforce" (site.CSPOff, site.CSPReportOnly, site.CSPEnforce). Empty
// follows the site setting, which is off until an administrator
// changes it; otherwise Site settings › Security shows this value,
// which cannot be changed there. The policy lets pages run only the
// scripts and styles the site serves, by go-router's
// middleware.StrictCSP and the hashes of the import maps; see the
// package documentation.
ContentSecurityPolicy string
// ContentSecuritySources are sources the policy allows by directive,
// besides the site's own, such as {"font-src":
// {"https://fonts.example.com"}}, added to those of the site setting
// and the theme. Start refuses what site.CheckCSPSources does not
// allow, such as 'unsafe-inline' or *.
ContentSecuritySources map[string][]string
// API serves the published content as JSON at /api/v1/ (package api),
// with the modules' endpoints (Parts.API), for apps and decoupled
// frontends: its rate limit and the sites that may read it in the
// browser (CORS). Off by default.
API api.Config
}
Config is everything a site decides in code when it creates its App: its name, where it keeps its state, which database it uses, its templates and languages, and the limits it runs with. Everything an editor may change later, such as the site title or the theme, lives in the Site settings instead. The zero value of every field is a working default.
type Env ¶
type Env struct {
// Key and Title are those of the Module being set up.
Key string
Title string
// DB is the site's database.
DB *db.DB
// Locales gives the content languages, as the Site settings have them
// at the time of asking.
Locales locales.Provider
// Site holds the Site settings.
Site *site.Service
// Auth holds the editors' accounts, for names in the admin and for
// notifying editors.
Auth *auth.Service
// Media is the media library.
Media *media.Service
// Pages is the page tree, for links to the pages a module is placed on.
Pages *pages.Service
// Frontend renders templates, components and e-mail with the theme.
Frontend *frontend.Frontend
// Mail sends e-mail; it logs messages when the site has no Config.Mail.
Mail mail.Sender
// BaseURL is Config.BaseURL, for absolute links in e-mail.
BaseURL string
// AdminLanguage is Config.AdminLanguage, the language of the admin, for
// text staff read, such as notification e-mail; "en" when not set.
AdminLanguage string
// contains filtered or unexported fields
}
Env gives a module the shared services.
func (Env) AbsoluteURL ¶
AbsoluteURL makes path absolute with BaseURL, or with the address of r when BaseURL is empty; for links in e-mail.
func (Env) Actions ¶
Actions is the go-router of the module's visitor actions: the forms visitors post to and links they follow, wherever the site shows them. It is mounted at /_/<key>/ behind the protection against cross-site posts and the body limit (Parts.ActionBodyLimit), so its routes are relative to that and become part of the app's root router, their names with the key in front, such as <key>.post for a route named post. A module that runs more than once names its routes alike.
actions := env.Actions()
actions.Use(frontend.Limits(env.Key, frontend.ActionLimits{Body: 64 << 10, Posts: 5, Window: time.Minute}))
actions.Post("/post", post).As("post")
Modules keep the router and build the addresses of their actions from it by route name, also without a request, such as for links in e-mail: actions.RoutePath("post") is "/_/<key>/post", and actions.RoutePathArgs("download", "id", id) fills in a wildcard. Handlers see the whole request path, and PathValue gives the route's wildcards; a request without a route gets a plain 404, or 405 for a path that has routes for other methods.
On an Env the app made, every call returns the same router; the app mounts it on the first. An Env made without an app, as in tests and tools, gets a new router on every call, mounted on a private root router with the app's own routes, so the addresses are the same.
func (Env) AdminURL ¶
AdminURL is the address of a named admin route, as admin.Admin.URL builds it, for links into the admin from outside it, such as an edit link on the site or a link in an e-mail to staff:
env.AdminURL(admin.RouteName(env.Key, "edit"), "id", id)
Modules never build admin addresses from paths: the admin may be anywhere (Config.AdminPath). Section routes are named "admin.<section ID>.<name>" (see admin.RouteName and admin.Route.As); sections built with admin/editor name theirs, such as index, new and edit, and a module names the routes it adds with Route.As. Inside the admin, handlers use admin.Routes.URL or admin.Admin.URL and templates the route function. It works once the app has started; before, it returns "". Like admin.Admin.URL, it panics on an unknown route or a missing or unknown parameter.
func (Env) Changed ¶
func (e Env) Changed()
Changed tells the app that what visitors see of the module changed outside a request that tells it already (see App.Changed). Modules call it after the writes their stores and services make, so a write made from Go, such as an import or a background job, shows at once; a store made with a content.Spec whose Changed is set to it tells it by itself. Call it once the write is committed (see db.AfterCommit).
func (Env) IsCorePath ¶
IsCorePath reports whether path is one of the app's CorePaths or below one of them, such as the admin's pages wherever it is (Config.AdminPath).
func (Env) Links ¶
Links returns the site's link check (App.Links), which knows the published content that links to an address, for the editor of a module whose items have an address of their own (see editor.Linked). It is nil on an Env made without an app.
func (Env) PersonalCookie ¶
PersonalCookie names a cookie the module reads to show public pages differently to the visitor who has it, such as the polls' visitor ID, which marks the polls they voted on. The output cache, which keeps public pages in memory, keeps a page apart for every value of such cookies, also when a response varies on the Cookie header, where other cookies, such as a site's analytics, count for nothing. Call it in Setup. Pages that are a visitor's own altogether, such as a registration looked up by its code, say so instead with Cache-Control: private or no-store, which the output cache never keeps.
func (Env) Secret ¶
Secret returns a random 32-byte key of this module instance, the same in every process of the site, for example to hash visitors' addresses with HMAC. name tells a module's keys apart: lowercase letters, digits, - and _, at most 35.
func (Env) Service ¶
Service returns the API of another module instance, for example env.Service("agenda").(*events.Service). It works once the app has started, so modules resolve it when they need it (in a handler) rather than in Setup; before, and for unknown keys, it returns nil.
func (Env) VisitorID ¶
VisitorID identifies the visitor of r without storing their address: an HMAC-SHA256 of the address (frontend.ClientIP), with the instance's "visitors" Secret as key, as 64 hexadecimal digits. Nobody can trace it back to the address by trying all addresses, and it differs between module instances.
parts are hashed before the address, joined with "|", to narrow the identifier: with the date (time.DateOnly) it changes every day, so messages of one day can be grouped by sender without following anyone for longer. Modules that keep the identifier, such as a poll counting one vote per visitor, may store a prefix of it.
type Module ¶
type Module struct {
// Type is the kind of module, such as "publishing"; it is the same for
// every instance.
Type string
// Key names this instance in addresses, template folders and the
// admin, such as "news": lowercase letters, digits and dashes.
Key string
// Title is the instance's name in the admin, such as "News".
Title string
// Schema holds the module's schema files, applied at Start once for
// all instances of the Type (see db.ApplySchema); nil for modules
// without tables.
Schema fs.FS
// Setup builds the instance once the core services are ready, and
// returns what it adds to the site and the admin.
Setup func(Env) (Parts, error)
// Templates are the public templates the module renders, looked up as
// <key>/<name>.gohtml, then <type>/<name>.gohtml.
Templates []frontend.Template
// Funcs are template functions the module offers to every template, such
// as {{range posts "news" 3}}. A first context.Context parameter receives
// the request; frontend.Service gives the instance for a key.
Funcs template.FuncMap
// Mail are the e-mail templates the module writes with Frontend.Mail, with
// the default text as Example; themes replace them with
// mail/<key>/<name>.gohtml or mail/<type>/<name>.gohtml.
Mail []frontend.Template
// Components are parts of a page the module offers to every template,
// such as a comment thread or a signup form, with the default markup as
// Example. The module's template functions render them with
// frontend.Component; themes replace them with <key>/<name>.gohtml or
// <type>/<name>.gohtml, so their names differ from those of Templates.
Components []frontend.Template
// Forms are the visitor forms the module offers to templates, values of
// structs with go-form's tags such as comments.PostInput{}, which
// themes render with form_open and form_field. Their texts (labels,
// descriptions, placeholders, option labels, submit texts and messages,
// go-form's Texts) are translated with the site's translations, so
// extraction finds them (see TranslationCatalog).
Forms []any
// contains filtered or unexported fields
}
Module is one capability instance. Type names the module's schema and its template folder; Key identifies this instance, for example "news" and "blog" for two Publishing instances.
func (Module) Group ¶
Group lists the module's admin sections under a heading of the navigation, for example:
app.Use(banners.Module("notice", "Notices").Group("Website"))
All sections of the module get it, also the ones it adds next to its main section, such as categories. The name is translated by the admin when its catalogue has it: Agenda, Content, Visitors, Website, Communication, Shop and the admin's own groups are; other names are shown as written. Modules without a group are listed under "Modules" (admin.GroupModules), unless they set Section.Group themselves.
type Parts ¶
type Parts struct {
// Admin sections, usually one with the module's key as ID, at
// <AdminPath>/<key> (see admin.Section.Path).
Admin []admin.Section
// Public serves the module at and below the page it is mounted on. Nil
// means the module cannot be mounted. A go-router of frontend.Router is
// served at the page's address (see frontend.Router); any other handler
// sees the request's whole path, its page in frontend.MountFrom.
Public http.Handler
// Service is the module's API for the website's own code; see App.Service.
Service any
// Sitemap lists the module's public paths below its page, for sitemap.xml.
Sitemap frontend.SitemapFunc
// Feeds are the module's Atom and RSS feeds, served below its page on
// Public, which is then a router of frontend.Router, as routes of
// their own, such as <page>/feed.atom named feed.atom (see
// frontend.Feed). The layout links to those of the page shown with
// {{feedLinks}}.
Feeds []frontend.Feed
// ActionBodyLimit is the largest request body the module's visitor
// actions (Env.Actions) accept in bytes, for example more for an upload;
// zero means DefaultActionBodyLimit.
ActionBodyLimit int64
// Middleware wraps the public site, for example to redirect old URLs.
// It sees every request for the site's pages, also those the output
// cache answers, outside compression: a response it reads may be
// gzip-compressed (Content-Encoding).
Middleware func(http.Handler) http.Handler
// Close runs when the app closes, before the database does, for example
// to write buffered data.
Close func(ctx context.Context) error
// Search finds the module's published content for the site search (see
// frontend.Search); results need a URL.
Search frontend.SearchFunc
// Scheduled is content whose publication editors can plan, carried out by
// the app's scheduler; see content.Revisions.Scheduler.
Scheduled []content.Scheduler
// Links lists the module's published content in a language with the
// links in it, such as Markdown texts and link fields (see
// links.Source), so the admin knows which content links to a page: it
// warns editors before the page leaves its address and lists the
// links that lead nowhere (App.Links).
Links links.Func
// Redirects is the module's service when it redirects old addresses,
// such as the redirects module's: the admin then offers to redirect
// the addresses links lead to when a page leaves them. The first
// module that sets it is used.
Redirects links.Redirector
// API serves the module's published content as JSON below
// /api/v1/<key>/ while the site's content API is on (Config.API): a
// router of api.NewRouter, whose routes are relative to that and named
// api.<key>.<name> for the app's other routers. Its handlers answer in
// the request's language (api.Locale) with the helpers of package api,
// and never show drafts or personal data, such as entries of visitors.
// It is not mounted while the API is off.
API *router.Router
// StructuredData adds to the structured data of the site's pages, or
// changes it, such as the offers of an event visitors register for.
// A module's own pages give theirs with frontend.Content's
// StructuredData instead.
StructuredData frontend.StructuredDataFunc
}
Parts is what a module adds to the site.
type Telemetry ¶
type Telemetry struct {
// Middleware wraps every request below the request ID, outside the
// access log, compression and the routes, for example to start a trace
// span per request; the health and metrics endpoints are left out.
Middleware func(http.Handler) http.Handler
// RenderStages run in every render of the site's templates and the
// admin's, for example go-partial's exp/metrics with a sink of its own.
RenderStages []partial.RenderStage
// RenderEvents receive go-partial's diagnostic events, for example
// go-partial's exp/otel Sink, which adds them to the request's span.
RenderEvents []partial.EventSink
}
Telemetry plugs instrumentation into the app, such as tracing with OpenTelemetry. Core brings no tracing library of its own: integrations such as go-webthings-contrib's provide these parts.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package admin is the shared back office: one shell (layout, navigation, notices), sign-in with optional two-step sign-in, password reset and first-run setup, translations and a permission check on every route.
|
Package admin is the shared back office: one shell (layout, navigation, notices), sign-in with optional two-step sign-in, password reset and first-run setup, translations and a permission check on every route. |
|
editor
Package editor is the ready-made admin for content stores (see the content package): an overview with language tabs, a go-form editor per language, publication and, for revisioned content, history, restore and preview.
|
Package editor is the ready-made admin for content stores (see the content package): an overview with language tabs, a go-form editor per language, publication and, for revisioned content, history, restore and preview. |
|
presence
Package presence keeps track of which admin page every signed-in user is looking at, so the admin can show "Ann is also on this page".
|
Package presence keeps track of which admin page every signed-in user is looking at, so the admin can show "Ann is also on this page". |
|
Package api is the site's content API: its published content as JSON, for apps and decoupled frontends, at /api/v1/ (Path).
|
Package api is the site's content API: its published content as JSON, for apps and decoupled frontends, at /api/v1/ (Path). |
|
Package auth keeps the accounts of the people who edit the site, their sessions and their groups of rights.
|
Package auth keeps the accounts of the people who edit the site, their sessions and their groups of rights. |
|
Package content stores multilingual content for modules.
|
Package content stores multilingual content for modules. |
|
Package db is WebThings' small layer over SQL.
|
Package db is WebThings' small layer over SQL. |
|
dbtest
Package dbtest opens a fresh database per test for every configured dialect.
|
Package dbtest opens a fresh database per test for every configured dialect. |
|
Package feed writes web feeds, Atom 1.0 (RFC 4287) and RSS 2.0, from one model: a Feed with its Entries.
|
Package feed writes web feeds, Atom 1.0 (RFC 4287) and RSS 2.0, from one model: a Feed with its Entries. |
|
Package frontend serves the public website.
|
Package frontend serves the public website. |
|
internal
|
|
|
bundled
Package bundled knows the template functions of the modules bundled with WebThings, also of those a site does not use, so the template check at Start can say where an unknown function comes from.
|
Package bundled knows the template functions of the modules bundled with WebThings, also of those a site does not use, so the template check at Start can say where an unknown function comes from. |
|
cache
Package cache keeps one value read from the database in memory for a short time.
|
Package cache keeps one value read from the database in memory for a short time. |
|
ops
Package ops holds the operational parts of serving a site that are WebThings' own: its metrics in the Prometheus text format, with the access log, and the caching policy of public pages.
|
Package ops holds the operational parts of serving a site that are WebThings' own: its metrics in the Prometheus text format, with the access log, and the caching policy of public pages. |
|
testhooks
Package testhooks holds switches that only test helpers flip, to make expensive work cheap in tests.
|
Package testhooks holds switches that only test helpers flip, to make expensive work cheap in tests. |
|
Package links knows which published content of a site links to which address on it: the Markdown of the published pages, and what modules report of their published content (Func), such as the text of a post or the address of a banner's button.
|
Package links knows which published content of a site links to which address on it: the Markdown of the published pages, and what modules report of their published content (Func), such as the text of a post or the address of a banner's button. |
|
Package locales checks the content languages a site offers, such as "en" and "nl-BE", and writes their codes the same way everywhere.
|
Package locales checks the content languages a site offers, such as "en" and "nl-BE", and writes their codes the same way everywhere. |
|
Package mail sends e-mail for modules: confirmations, notifications and newsletters.
|
Package mail sends e-mail for modules: confirmations, notifications and newsletters. |
|
Package markdown renders editor content.
|
Package markdown renders editor content. |
|
Package media stores shared assets (images and documents) with translated titles and alt texts.
|
Package media stores shared assets (images and documents) with translated titles and alt texts. |
|
mediaadmin
Package mediaadmin is the Media section of the admin: the media library, where editors upload files, write their titles and alternative texts per language and choose the focal point of images.
|
Package mediaadmin is the Media section of the admin: the media library, where editors upload files, write their titles and alternative texts per language and choose the focal point of images. |
|
modules
|
|
|
banners
Package banners shows editor-managed banners anywhere on the site: hero slides on the home page, a carousel, or a scheduled announcement bar at the top of every page.
|
Package banners shows editor-managed banners anywhere on the site: hero slides on the home page, a carousel, or a scheduled announcement bar at the top of every page. |
|
comments
Package comments lets visitors leave messages on the website: comments below news posts or pages, and guestbooks.
|
Package comments lets visitors leave messages on the website: comments below news posts or pages, and guestbooks. |
|
directory
Package directory is a categorised directory: organisations, shops, venues or a neighbourhood guide.
|
Package directory is a categorised directory: organisations, shops, venues or a neighbourhood guide. |
|
documents
Package documents offers files for download: minutes, reports, forms and policies.
|
Package documents offers files for download: minutes, reports, forms and policies. |
|
events
Package events is an agenda module: dated events with revisions, a timezone, an optional end, all-day events, a location, an image and Markdown content.
|
Package events is an agenda module: dated events with revisions, a timezone, an optional end, all-day events, a location, an image and Markdown content. |
|
forms
Package forms adds a public form, such as a contact form or a quote request, with an inbox for its submissions.
|
Package forms adds a public form, such as a contact form or a quote request, with an inbox for its submissions. |
|
gallery
Package gallery shows photo albums.
|
Package gallery shows photo albums. |
|
lists
Package lists keeps short ordered lists such as FAQs, services, features or steps.
|
Package lists keeps short ordered lists such as FAQs, services, features or steps. |
|
newsletter
Package newsletter keeps a subscriber list with double opt-in and sends mailings to it.
|
Package newsletter keeps a subscriber list with double opt-in and sends mailings to it. |
|
people
Package people presents the people behind an organisation: staff, a team, a board or volunteers.
|
Package people presents the people behind an organisation: staff, a team, a board or volunteers. |
|
polls
Package polls asks visitors a question with a few options and shows the results.
|
Package polls asks visitors a question with a few options and shows the results. |
|
publishing
Package publishing is a news or blog module: dated posts with revisions, categories, a featured image and Markdown content.
|
Package publishing is a news or blog module: dated posts with revisions, categories, a featured image and Markdown content. |
|
redirects
Package redirects sends visitors from old URLs to new ones, for example after moving a site to WebThings.
|
Package redirects sends visitors from old URLs to new ones, for example after moving a site to WebThings. |
|
registrations
Package registrations lets visitors sign up for events, or for any other subject: places with an optional capacity, adults and children with prices, a deadline, a confirmation e-mail with a personal code, and a page where visitors look up and cancel their registration.
|
Package registrations lets visitors sign up for events, or for any other subject: places with an optional capacity, adults and children with prices, a deadline, a confirmation e-mail with a personal code, and a page where visitors look up and cancel their registration. |
|
stats
Package stats counts the public site's page views without cookies and without storing anything about visitors.
|
Package stats counts the public site's page views without cookies and without storing anything about visitors. |
|
submissions
Package submissions lets visitors propose a news item: a title, a text and an optional image, with their name and e-mail address.
|
Package submissions lets visitors propose a news item: a title, a text and an optional image, with their name and e-mail address. |
|
testimonials
Package testimonials shows quotes from customers or partners: a quote, the author's name, role and organisation, and an optional photo.
|
Package testimonials shows quotes from customers or partners: a quote, the author's name, role and organisation, and an optional photo. |
|
Package pages owns the public page tree.
|
Package pages owns the public page tree. |
|
pageadmin
Package pageadmin is the Pages section of the admin: the page tree, where editors add, move and publish pages and place modules on them, and the editor of each page, built with package editor.
|
Package pageadmin is the Pages section of the admin: the page tree, where editors add, move and publish pages and place modules on them, and the editor of each page, built with package editor. |
|
Package render wraps go-partial: a layout with a content slot and optional out-of-band regions, served as a full page or as an HTMX fragment.
|
Package render wraps go-partial: a layout with a content slot and optional out-of-band regions, served as a full page or as an HTMX fragment. |
|
Package site holds the website's identity and settings: languages, timezone, branding, contact details, social media profiles and per-language title, tagline, SEO and footer.
|
Package site holds the website's identity and settings: languages, timezone, branding, contact details, social media profiles and per-language title, tagline, SEO and footer. |
|
siteadmin
Package siteadmin is the Site settings section of the admin: the site's languages, time zone, texts, social media profiles, performance, security, review and trash settings, the Theme screen where editors choose the theme and set its values, and the Backup screen where administrators download the site as a zip file.
|
Package siteadmin is the Site settings section of the admin: the site's languages, time zone, texts, social media profiles, performance, security, review and trash settings, the Theme screen where editors choose the theme and set its values, and the Backup screen where administrators download the site as a zip file. |
|
Package sitetest starts a complete WebThings app for end-to-end tests and drives its admin like a browser: sign in, submit forms, publish.
|
Package sitetest starts a complete WebThings app for end-to-end tests and drives its admin like a browser: sign in, submit forms, publish. |
|
catalogtest
Package catalogtest checks in tests that the admin's catalogues translate every text of a section's forms in each language the admin comes with, so a label or description added to a form cannot go untranslated:
|
Package catalogtest checks in tests that the admin's catalogues translate every text of a section's forms in each language the admin comes with, so a label or description added to a form cannot go untranslated: |
|
Package translations is the "translations" command of a site: it writes the texts a site translates with tl, tn, ctl and ctn, found with go-translator's extract package, to a gettext template (translations.pot) and checks which texts each language's .po file still misses.
|
Package translations is the "translations" command of a site: it writes the texts a site translates with tl, tn, ctl and ctn, found with go-translator's extract package, to a gettext template (translations.pot) and checks which texts each language's .po file still misses. |