From c9eb0a6f805e0348ee595b28171b0a0bfc505dca Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 8 May 2026 18:42:56 +0200 Subject: [PATCH] Phase 9k: Media-Upload via MinIO-Container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eigener cards-minio-Container im docker-compose (9100/9101 — Plattform auf 9000/9001 bleibt isoliert). cardsadmin/cardsadmin als Dev-Default, prod via env-Vars (CARDS_S3_*). apps/api/src/services/storage.ts — schmaler StorageService um den minio-Client. ensureBucket() ist idempotent (auto-create beim ersten Upload). removeObjectsByPrefix() implementiert den DSGVO-Bucket-Sweep, weil die S3-API kein Cascade kennt. Neue Tabelle media_files in pgSchema('cards'): id, user_id, object_key, mime_type, original_filename, size_bytes, kind, created_at — kein FK auf cards (ein File kann mehreren Karten gehören). objectKey-Format /. für Bucket-Prefix- Sweep beim DSGVO-Delete. Legacy mediaRefs bleibt als Slot. Neuer Router /api/v1/media: POST /upload — multipart, 25 MiB Default-Limit, image/audio/video only (415 sonst), schreibt media_files-Row + speichert in MinIO unter /. GET /:id — streamt aus MinIO mit Cache-Control: private, immutable. Cross-User → 404 (nicht 403, anti-enumeration). GET / — listet alle eigenen Files DSGVO-Pfade (Service-Key + /me/delete) räumen jetzt auch media_files + MinIO-Bucket-Prefix mit ab. Storage-Sweep ist non-fatal — DB ist erst konsistent gelöscht, dead bytes wären die schlimmstmögliche Folge. Anki-Import: parse.ts sanitizeAnkiHtml akzeptiert wieder eine Filename→URL-Map (war in Phase 8c gedroppt). import.ts lädt vor den Karten alle referenzierten Media-Files via uploadMedia() in MinIO, sammelt URLs, ersetzt Anki-Filenames durch /api/v1/media/-Pfade in `` (Markdown) und `[sound:…]` (HTML