All checks were successful
CD / Build and push images (push) Successful in 2m54s
CI / Lint, typecheck, test (push) Successful in 2m25s
CI / Auth e2e pack (push) Successful in 2m58s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m12s
CD / Promote to Int (push) Successful in 12s
Every route now declares its access rule explicitly and is enforced through the shared resolution algorithm (permissions.md): - PermissionGuard + decorators (@RequiresPondRole, @RequiresPagePermission, @RequiresAttachmentPermission, @AuthenticatedOnly) applied to every route; a route-enumeration test proves full coverage alongside @Public()/Site-Admin-guarded routes. - 404/403 policy (documented in README conventions): denied reads answer 404 (existence hiding), denied writes on readable things answer 403; trash views need write capability (ADR 0013). - PermissionService resolves page/pond questions via the shared resolver, with an in-process pond-context cache (grants + label parents) that is invalidated on every grant/label-tree change and TTL-bounded as a multi-process safety net. Grant changes also fire pond_access_changed for collab revalidation (#39/#53). - shared: pond-scope resolution (hasPondRole, canSeePond) next to the page resolver; grant wire schemas + GrantView. - Owner Pond-Admin grants: migration backfill for all existing ponds, created transactionally with every new pond (shared + personal + seed). - Grant CRUD under /ponds/:id/grants (pond_admin-gated) with structural and referential validation, last-admin protection, audit logs. - InterimAccessService deleted; page lists, search, backlinks, phantom links, and trash listings are filtered per page through the resolver; collab tokens are now truly ro for readers. - Fixture-matrix e2e (reader/editor/pond admin/foreign, label-deny, authenticated-subject, revoke-then-immediate-deny cache test). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
113 lines
6.6 KiB
Markdown
113 lines
6.6 KiB
Markdown
# 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).
|
|
|
|
```mermaid
|
|
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](adr/0001-typescript-monorepo.md) | TypeScript everywhere, pnpm monorepo |
|
|
| [0002](adr/0002-postgresql.md) | PostgreSQL as the only database |
|
|
| [0003](adr/0003-yjs-crdt-collaboration.md) | Yjs CRDT + Hocuspocus for real-time and offline collaboration |
|
|
| [0004](adr/0004-tiptap-editor.md) | TipTap (ProseMirror) as the WYSIWYG editor |
|
|
| [0005](adr/0005-react-vite-frontend.md) | React + Vite single-page application |
|
|
| [0006](adr/0006-nestjs-prisma-backend.md) | NestJS + Prisma for the API server |
|
|
| [0007](adr/0007-auth-sessions-oidc-ready.md) | Cookie sessions, Argon2id, OIDC-ready identity model |
|
|
| [0008](adr/0008-plugin-sandbox.md) | Sandboxed iframe plugins with a message-based API |
|
|
| [0009](adr/0009-import-export-converters.md) | Pandoc + Gotenberg sidecars for import/export |
|
|
| [0010](adr/0010-postgres-fulltext-search.md) | PostgreSQL full-text search behind a search interface |
|
|
| [0011](adr/0011-file-storage-quotas.md) | Filesystem volume for uploads, DB-tracked quotas |
|
|
| [0012](adr/0012-i18n.md) | i18next with German and English from the start |
|
|
| [0013](adr/0013-versioning-and-trash.md) | Page version history via Yjs snapshots, soft-delete trash |
|
|
| [0014](adr/0014-gitea-actions-cicd.md) | CI/CD with Gitea Actions, staged promotion |
|
|
| [0015](adr/0015-backup-strategy.md) | Nightly pg_dump + uploads sync, 30-day retention, off-host mirror |
|
|
| [0016](adr/0016-self-hosted-fonts.md) | Self-hosted Google Fonts, per-pond font configuration |
|
|
|
|
### Concept documents
|
|
|
|
| Document | Contents |
|
|
| ------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
| [data-model.md](data-model.md) | Entities and relations: users, ponds, pages, labels, grants, quotas, plugins |
|
|
| [permissions.md](permissions.md) | Role model and the "most specific setting wins" resolution algorithm |
|
|
| [realtime-collaboration.md](realtime-collaboration.md) | CRDT document lifecycle, cursor sync, offline behavior, versioning hooks |
|
|
| [plugin-architecture.md](plugin-architecture.md) | Plugin manifest, packaging, sandbox runtime, extension points, admin flows |
|
|
| [deployment.md](deployment.md) | Compose stacks for Dev/Test/Int/Prod, environments, promotion pipeline |
|
|
| [operations.md](operations.md) | Monitoring, logging, backup/restore, update strategy for self-hosters |
|
|
| [security.md](security.md) | Threat-driven security concept: authn/authz, sandboxing, uploads, secrets |
|
|
| [roadmap.md](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_ answers `404` so the existence
|
|
of ponds and pages is not revealed; a denied _write_ on something the
|
|
user may read answers `403`. Trash views require write capability and
|
|
answer `404` on denial (ADR 0013).
|