feat(quick-260921-pi9): Bilderrahmen-API - Bilder je Benutzer in der Datenbank, Magic-Byte-Pruefung, 5 MiB / 30 Stueck
- Prisma-Modell DashboardImage (bytea) mit Migration 20260921120000: Tabelle, Indizes, RLS ENABLE/FORCE und tenant_isolation_policy mit Benutzerdimension - dashboard-image-rules.ts: detectImageMime ueber Magic Bytes (PNG/JPEG/GIF/ WebP), Grenzen 5 MiB je Datei und 30 je Benutzer - DashboardImagesService: list/upload/getBytes/remove, je Methode forTenant(prisma, tenantId, userId); Besitz = Mandant UND Benutzer, sonst 404 - DashboardImagesController unter dashboard/images: GET, POST (FileInterceptor image, 5 MiB, eine Datei), GET :id mit Content-Type aus dem erkannten Typ, Cache-Control private, nosniff, Content-Disposition inline ohne Dateinamen, CSP sandbox; DELETE :id - CreateWidgetDto kennt 'picture-frame' - Klassifikationsdokument: neues Paar dashboard-images.service.ts/ dashboardImage; Bereichs- und Summenzeilen nachgemessen (dashboard 12->18, settings 3->4 und bug-reports waren in der Summe nie mitgezaehlt) - Befund: Prisma-Bytes verlangt Uint8Array<ArrayBuffer>, multers Buffer wird ohne Zusicherung abgelehnt - Kopie per new Uint8Array(buffer) statt Cast Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
-- 260921-pi9 — Bilderrahmen-Widget: hochgeladene Bilder eines Benutzers.
|
||||
--
|
||||
-- Zweck: die Tabelle "DashboardImage" traegt die Bilddaten (bytea) fuer das
|
||||
-- Dashboard-Widget „Bilderrahmen“. Bilder liegen in der Datenbank statt in
|
||||
-- einem Docker-Volume, damit die bestehende Sicherung sie mit abdeckt.
|
||||
--
|
||||
-- Grenzen (durchgesetzt in der Anwendung, apps/api/src/dashboard/
|
||||
-- dashboard-image-rules.ts + dashboard-images.service.ts): hoechstens 5 MiB
|
||||
-- je Datei (multer-Limit je Route), hoechstens 30 Bilder je Benutzer
|
||||
-- (Zaehler je Mandant+Benutzer vor dem Anlegen); erlaubt sind nur PNG, JPEG,
|
||||
-- GIF und WebP, erkannt an den Magic Bytes — "mimeType" ist der ERKANNTE Typ,
|
||||
-- nie der vom Browser behauptete.
|
||||
--
|
||||
-- Besitz: ein Bild gehoert dem hochladenden Benutzer (gleicher Mandant UND
|
||||
-- gleicher Benutzer). Die Regel unten traegt deshalb von Anfang an die
|
||||
-- Benutzerdimension (Form aus 20260911120000_rls_user_dimension_personal_
|
||||
-- tables); die Anwendung prueft den Besitz zusaetzlich in getBytes/remove und
|
||||
-- antwortet fuer fremde Kennungen mit 404 (nie 403).
|
||||
--
|
||||
-- Rechte fuer die Anwendungsrolle tessera_app: kommen ueber ALTER DEFAULT
|
||||
-- PRIVILEGES aus 20260909130000_rls_app_role automatisch — hier nichts zu tun.
|
||||
--
|
||||
-- WICHTIG: wie alle RLS-Regeln dieses Schemas wirkt die Regel erst, wenn die
|
||||
-- Anwendung als Rolle ohne Umgehungsrecht verbindet (Schalter heute AUS, siehe
|
||||
-- docs/mandantentrennung-datenbankrolle.md).
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DashboardImage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"originalName" TEXT NOT NULL,
|
||||
"mimeType" TEXT NOT NULL,
|
||||
"size" INTEGER NOT NULL,
|
||||
"data" BYTEA NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "DashboardImage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DashboardImage_userId_idx" ON "DashboardImage"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DashboardImage_tenantId_idx" ON "DashboardImage"("tenantId");
|
||||
|
||||
-- Zeilenschutz: Mandant UND Benutzer (Muster 20260911120000)
|
||||
ALTER TABLE "DashboardImage" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "DashboardImage" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "DashboardImage"
|
||||
USING (
|
||||
"tenantId" = current_tenant_id()
|
||||
AND (current_user_id() IS NULL OR "userId" = current_user_id())
|
||||
);
|
||||
Reference in New Issue
Block a user