Architecture
Ce contenu n’est pas encore disponible dans votre langue.
The production shape is deliberately small: three containers and one Go binary.
Production topology
Section titled “Production topology”flowchart LR
subgraph host[Single host]
nginx[nginx<br/>TLS + static]
app[Go binary<br/>API + embedded SPA]
pg[(Postgres)]
storage[(Filesystem<br/>or S3)]
nginx -->|reverse proxy| app
app --> pg
app --> storage
end
user[Artist / Reviewer / Operator] -->|HTTPS| nginx
- Go binary serves the JSON API and embeds the SvelteKit SPA via
go:embed. One artifact. One process. Same binary supports theembed_webbuild tag for production (frontend baked in) and a dev mode that proxies the Vite dev server. - Postgres holds every record Artist Alley owns — assets,
metadata fields + values, posts, members, collections, comments,
likes, sessions, audit, system_config. Migrations live in
app/internal/db/migrations/and run on boot. - Storage is content-addressed: every byte blob is keyed by its
SHA-256, so dedupe across the install is free. Two backends:
fs— directory on the host (mounted into the container)s3— any S3-compatible API (AWS, R2, B2, MinIO)
- nginx in front for TLS termination and static serving. Optional: the binary serves HTTP fine on its own behind another proxy if you prefer.
Contract-first API
Section titled “Contract-first API”The JSON API is defined in
app/api/openapi.yaml.
That file is the contract: server types and handler interfaces
are generated from it by oapi-codegen. Frontend code (SvelteKit)
generates its TypeScript types from the same source via
openapi-typescript.
Edits to the spec are the only way to change the public API surface
— handlers won’t compile if the Go signatures drift from the spec,
and npm run check fails on the frontend if its generated types
diverge.
The rendered reference lives at API reference and is re-rendered on every site build.
Schema
Section titled “Schema”The Postgres schema is declared in
app/internal/db/migrations/*.sql
and surfaced here at Database schema. Migrations
are the source of truth — sqlc type-checks Go queries against
them at code-generation time, so type drift gets caught before code
ships.
The schema is mirrored in app/schema.sql, kept in lockstep with the
single squashed baseline migration (00001_baseline_v0_1.sql, per
ADR 0046); sqlc
type-checks Go queries against it at code-generation time.
Configuration & system_config
Section titled “Configuration & system_config”Two layers:
- Process config (host vs container, ports, secrets) — driven by environment variables, see the install guide configuration reference.
- Install-wide settings (site name, SMTP, auth providers, AI
providers, brand fonts, themes) — stored as JSON-typed rows in the
system_configtable, mutated through the admin UI, surfaced via the/admin/system/*API. Theappearancekey drives the per-install font slot picker.
Federation-aware: every system_config row stays local to its
instance. Per-user prefs (theme, language, AI provider override)
ride on user_profiles which carries origin_server_id.
Why these choices
Section titled “Why these choices”- Go for the server — one static binary, fast startup, good concurrency, and native HTTP/2 + SSE for streaming flows like the planned live presenter sessions.
- SvelteKit for the SPA — small bundle, file-based routing that
lines up with the API. Build output is static (
adapter-static), so the binary just serves the files. - Postgres only — one durable database to back up, one query language across every entity. No separate cache, vector store, or search index. Full-text search is a TSVECTOR column with a trigger that rebuilds it on writes; vector similarity uses pgvector in the same database.
go:embedfor the SPA — operators don’t need to think about static asset deployment. The binary is the whole frontend.- Content-addressed storage — uploading the same bytes twice
stores them once. Move the install to a new disk by
rsync-ing the storage directory; integrity is recoverable by re-hashing.
For the rationale behind each big call see Architecture Decisions.