dorfteich/docs/architecture/adr/0011-file-storage-quotas.md
Claude Fable 5 0629411966 Add architecture documentation, ADRs, and operations concept
Initial deliverable of the architecture phase: 16 ADRs (stack, CRDT
collaboration, plugin sandbox, import/export, backups, CI/CD), data
model, permission model, real-time collaboration and plugin concepts,
deployment/operations/security documentation, and the milestone roadmap
that the implementation issues are derived from.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 14:36:16 +02:00

2.4 KiB

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.