|
Some checks failed
CD / Build and push images (push) Successful in 3m16s
CI / Lint, typecheck, test (push) Successful in 3m5s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Failing after 3m35s
CD / Promote to Int (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m6s
CI / Import/export fidelity gate (push) Successful in 43s
CI / Build container images (push) Has been skipped
When the api runs against a database without the setup.completedAt marker, a global SetupGuard answers every non-exempt route with 503 setup_required; only /setup/*, health probes, and the session routes stay reachable. The wizard steps (POST /setup/admin|instance|smtp| registration|complete) write straight to their production homes; the Site Admin step signs its creator in, later steps require that session. Completing sets the marker and locks every step permanently (410, also across restarts, and not reopenable via PATCH /admin/settings). SMTP entered in the wizard is verified with a live delivery test first (failure blocks the step with the transport error as detail) and then persisted to the new env-backed secret store: a mode-600 dotenv file on the new `secrets` volume (SECRETS_FILE). Explicit container env always wins over the store; empty compose-passed strings count as unset. The mail transport now resolves lazily through SmtpConfigService so wizard changes apply without a restart. SETUP_ADMIN_* env pre-seeds the whole wizard at boot for automated deploys; a backfill migration marks instances that already have a Site Admin as completed, and seed/vitest global-setup do the same for fixture databases. The setup e2e suite provisions its own fresh database (CREATE DATABASE + migrate deploy) per run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
||
|---|---|---|
| .. | ||
| adr | ||
| data-model.md | ||
| deployment.md | ||
| operations.md | ||
| permissions.md | ||
| plugin-architecture.md | ||
| README.md | ||
| realtime-collaboration.md | ||
| roadmap.md | ||
| security.md | ||
Dorfteich — Architecture Overview
This directory is the authoritative architecture documentation. Every implementation story references the documents here; when a story and this documentation disagree, clarify before coding.
System context
Dorfteich is shipped as a set of Docker containers behind a reverse proxy.
One deployment = one instance (e.g. dorfteich.online, or a self-hosted
installation).
flowchart LR
subgraph clients [Clients]
B[Browser SPA<br/>React + TipTap + Yjs]
end
subgraph instance [Dorfteich instance - Docker Compose]
RP[Reverse proxy]
WEB[web<br/>static SPA assets]
API[api<br/>NestJS REST]
COLLAB[collab<br/>Hocuspocus WebSocket]
PG[(PostgreSQL)]
PAN[pandoc-server<br/>doc conversion]
GOT[Gotenberg<br/>HTML to PDF]
VOL[/uploads + plugins volume/]
end
B -- HTTPS --> RP
RP --> WEB
RP -- /api --> API
RP -- /collab WebSocket --> COLLAB
API --> PG
COLLAB --> PG
API --> PAN
API --> GOT
API --> VOL
COLLAB -. permission checks .-> API
- web serves the single-page application (static assets).
- api owns all business logic: auth, ponds, pages, labels, permissions, quotas, import/export, plugin management, admin functions.
- collab synchronizes CRDT documents (page content) and awareness (cursors) over WebSocket and persists document state to PostgreSQL. It authenticates clients with short-lived tokens issued by api.
- pandoc-server and Gotenberg are internal-only conversion sidecars (Word/OpenOffice import/export, PDF export).
- All page content lives in PostgreSQL; binary uploads (images, attachments) and installed plugins live on a Docker volume.
Documents
Architecture Decision Records (adr/)
| ADR | Decision |
|---|---|
| 0001 | TypeScript everywhere, pnpm monorepo |
| 0002 | PostgreSQL as the only database |
| 0003 | Yjs CRDT + Hocuspocus for real-time and offline collaboration |
| 0004 | TipTap (ProseMirror) as the WYSIWYG editor |
| 0005 | React + Vite single-page application |
| 0006 | NestJS + Prisma for the API server |
| 0007 | Cookie sessions, Argon2id, OIDC-ready identity model |
| 0008 | Sandboxed iframe plugins with a message-based API |
| 0009 | Pandoc + Gotenberg sidecars for import/export |
| 0010 | PostgreSQL full-text search behind a search interface |
| 0011 | Filesystem volume for uploads, DB-tracked quotas |
| 0012 | i18next with German and English from the start |
| 0013 | Page version history via Yjs snapshots, soft-delete trash |
| 0014 | CI/CD with Gitea Actions, staged promotion |
| 0015 | Nightly pg_dump + uploads sync, 30-day retention, off-host mirror |
| 0016 | Self-hosted Google Fonts, per-pond font configuration |
Concept documents
| Document | Contents |
|---|---|
| data-model.md | Entities and relations: users, ponds, pages, labels, grants, quotas, plugins |
| permissions.md | Role model and the "most specific setting wins" resolution algorithm |
| realtime-collaboration.md | CRDT document lifecycle, cursor sync, offline behavior, versioning hooks |
| plugin-architecture.md | Plugin manifest, packaging, sandbox runtime, extension points, admin flows |
| deployment.md | Compose stacks for Dev/Test/Int/Prod, environments, promotion pipeline |
| operations.md | Monitoring, logging, backup/restore, update strategy for self-hosters |
| security.md | Threat-driven security concept: authn/authz, sandboxing, uploads, secrets |
| roadmap.md | Epics and milestones; the order stories are implemented in |
Terminology
| German (product vision) | English (code, docs, issues) |
|---|---|
| Teich | pond |
| Seite | page |
| Teich-Admin | Pond Admin |
| Bearbeitende | Editor |
| Lesende | Reader |
| Öffentlichkeit | Public |
Conventions for implementers
- Language: English for code, comments, commit messages, and issues.
- Write clear, human-readable code; document the "why", not the "what".
- UI strings never appear hard-coded — always through i18n resources (see ADR 0012), with German and English translations added in the same change.
- Every story lists the ADRs it depends on; read them before starting.
- Every API route declares its access rule explicitly (a permission
decorator,
@Public(), or the Site-Admin guard — issue #52); permission checks run only through the shared resolution (permissions.md), never ad hoc. 404/403 policy: a denied read answers404so the existence of ponds and pages is not revealed; a denied write on something the user may read answers403. Trash views require write capability and answer404on denial (ADR 0013).