|
Some checks failed
CI / Auth e2e pack (push) Waiting to run
CI / Import/export fidelity gate (push) Waiting to run
CI / Build container images (push) Waiting to run
CD / Build and push images (push) Failing after 1m33s
CD / Deploy to Test (push) Has been skipped
CD / Smoke tests against Test (push) Has been skipped
CD / Promote to Int (push) Has been skipped
CI / Lint, typecheck, test (push) Has been cancelled
Backend for installing plugin ZIPs (ADR 0008, plugin-architecture.md §Lifecycle, security.md §Plugins). Consumes the #70 SDK for validation. - Schema: `plugins` (id, name, version, apiVersion, kind, mode, manifest jsonb, removedAt soft-delete) + `pond_plugins` (per-pond activation) + `PluginInstanceMode` enum; migration 20260710130000_plugins. - `PluginPackageService`: pure, stateless ZIP → validated package via fflate — structure check, manifest validation (SDK), apiVersion gate, kind/bundle/styles rules, CSS sanitation (no @import / external url() / expression()), zip-slip and unpacked-size guards. Each failure carries a stable PluginErrorCode; manifest issues travel as ApiError details. - `PluginStorageService`: on-disk layout `<PLUGINS_DIR>/<id>/<version>/`; atomic writeVersion (staging dir + rename, no 404 window mid-update), removeVersion/removePlugin, traversal-safe asset resolution, dropzone + quarantine dirs. - `PluginsService`: install/update (update only to a strictly higher version, preserving the admin's instance mode; files land before the metadata pointer flips) / uninstall (refused while required; soft-delete + files removed + pond activations dropped) / list / get. - `POST/GET/DELETE /admin/plugins` (SiteAdminGuard, multer memory upload), error→HTTP-status mapping. Public version-pinned static serving at `GET /plugins/:id/:version/*rest` with immutable cache + nosniff, only for the installed current version. - `PluginWatcherService`: watches `<PLUGINS_DIR>/_dropzone/`, runs the same validation, installs valid drops and quarantines invalid ones with the error logged; inert under NODE_ENV=test (tests drive processDropped). - SDK: `compareVersions`/`isHigherVersion`. shared: `PluginView`, `PluginInstanceMode`, `PLUGIN_ERROR_CODES`, `PLUGINS_DIR` env, plugin error i18n (de+en). Compose: `plugins` volume + `PLUGINS_DIR`. - Tests: package unit test (valid + each invalid class) and an e2e DB test (GUI install + immutable serving, non-admin 403, invalid-manifest details, dropzone install + quarantine, atomic higher-only update, required-guarded uninstall that removes files and tombstones metadata). Co-Authored-By: Claude Opus 4.8 <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).