Developer documentation
Guides for people changing the code. The deep, always-current reference is
CLAUDE.md at the repository root: every route, table, setting key, admin
screen and gotcha is described there. These pages are the shorter, task-shaped entry points into
it.
Architecture in fifteen lines
Section titled “Architecture in fifteen lines”- One Node.js 20 process (
src/server.js→src/lib/createServer.js) running an Express 5 app, server-rendered with EJS. No frontend build step, no bundler; the plain CSS and JS files inpublic/are joined in memory at boot into/site.cssand/site.js. - SQLite (
better-sqlite3, synchronous) is the only store:DATA_DIR/site.dbplus uploads inDATA_DIR/uploads/. Migrations are plain.sqlfiles applied at startup. src/app.jsis the composition root:createApp({ content, mail, log, env, db, … })wires routers that are all factories receiving their dependencies — which is what makes every test an in-processsupertestcall against an in-memory database.- The public site reads through one
SqliteContentRepository(src/content/) and renders viares.renderPage(view, data)intosrc/views/layouts/base.ejs. - The admin panel (
/admin,src/admin/) is a separate router with its own session store (SQLite), CSRF, role checks (admin/editor), audit log and error pages. - Uploads go through a single choke point,
src/services/FileStorage.js(content sniffing, WebP re-encoding, delete guard). - Background work runs inside the app: the backup scheduler, the daily update check, the recycle-bin purge and the nightly upload maintenance. No cron.
- A strict CSP (no
'unsafe-inline'anywhere) shapes the front end: no inline styles or scripts. - Everything a school changes lives in the database (settings, menu, pages, theme) or in
.env(domain, mail, secrets). The code contains no installation-specific data (enforced bytest/neutralTemplate.test.js). - Production is Docker Compose: the app image from GHCR, nginx with Let’s Encrypt, certbot.
| Page | What it covers |
|---|---|
| architecture.md | Request flow, layers, where things live |
| data-model.md | Tables, relations, soft delete, settings, files, search index |
| adding-a-page-type.md | What to touch to add a new kind of page |
| writing-a-migration.md | Migration rules and the traps SQLite sets |
| testing.md | The test loop, node:test conventions, test helpers, accessibility check |
| release-process.md | Changelog, version bump, tag, workflows, GHCR, rollback |
| licensing.md | The chosen licence (AGPL-3.0-only), why, and dependency compatibility |
| publishing-checklist.md | What to do before the repository goes public |
| public-snapshot.md | How the public repository is produced from this tree, what it leaves out |
See also: CONTRIBUTING.md, SECURITY.md,
DEPLOYMENT.md (continuous deployment from an installation’s own
repository, and how to cut a release; in Ukrainian) and the Ukrainian install guide for other schools.