pnpm workspace with apps/web, apps/api, apps/collab, and packages/shared; strict TypeScript base config, repo-wide ESLint (flat) + Prettier, Vitest per package, and root scripts lint/typecheck/test/ build. @dorfteich/shared ships a first health-response helper consumed by apps/api to prove workspace linking. Existing markdown docs are reformatted once by the new Prettier setup. Closes #1 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
54 lines
2.4 KiB
Markdown
54 lines
2.4 KiB
Markdown
# ADR 0011: Filesystem volume for uploads, DB-tracked quotas
|
|
|
|
- Status: accepted
|
|
- Date: 2026-07-04
|
|
|
|
## Context
|
|
|
|
Pages contain pasted/uploaded images; kickoff added non-image attachments
|
|
(PDF, office files, …) with a type allowlist and size limits. Self-hosting
|
|
must not require object storage. Quotas exist on three levels (instance
|
|
default → per user → per pond, most specific wins) per kickoff decision;
|
|
storage volume is one of the quota dimensions (kickoff assumption #1,
|
|
confirmed).
|
|
|
|
## Decision
|
|
|
|
- **Uploads live on a dedicated Docker volume**, laid out as
|
|
`uploads/<pondId>/<fileId>` (opaque ids; original filename and metadata in
|
|
the database). No S3 dependency; the storage access goes through a thin
|
|
`FileStorage` service so an S3 binding stays possible later.
|
|
- **Serving**: files are streamed by the API with permission checks (an
|
|
attachment inherits the permissions of its pond/page); no direct static
|
|
serving of user uploads. `Content-Disposition` and strict
|
|
`Content-Type` handling prevent inline execution (see `security.md`).
|
|
- **Validation on upload**: configurable MIME/extension allowlist (instance
|
|
setting; images always allowed), size limit per file, magic-byte sniffing
|
|
for images. SVG uploads are sanitized (script stripping) or rejected per
|
|
instance setting.
|
|
- **Quota dimensions** (each on the three-level override ladder):
|
|
- storage bytes per pond (default: 1 GiB),
|
|
- max file size (default: 25 MiB),
|
|
- editors per pond (default 5) and readers per pond (default 50) for
|
|
self-signup personal ponds,
|
|
- additional ponds a user may create (default 0).
|
|
Current usage is tracked in the database (`pond_usage`), updated
|
|
transactionally with upload/delete; quota exceedance yields a clear,
|
|
i18n-ed error.
|
|
- **Orphan cleanup**: deleting a page moves it to trash (ADR 0013); its
|
|
files are removed when the trash entry is purged. A nightly job reconciles
|
|
volume contents against the database and reports drift.
|
|
|
|
## Consequences
|
|
|
|
- Backup must cover the uploads volume in addition to `pg_dump` (ADR 0015).
|
|
- Multi-node scaling would need shared storage or the S3 binding — out of
|
|
scope at target size, but not blocked.
|
|
|
|
## Alternatives considered
|
|
|
|
- **Files as bytea in PostgreSQL**: single backup artifact, but bloats the
|
|
database, slows dumps/restores, and complicates streaming; rejected.
|
|
- **MinIO/S3 required**: another stateful service for every self-host;
|
|
rejected for v1, kept possible via the `FileStorage` interface.
|