diff --git a/backend/server/api/user/delete.delete.ts b/backend/server/api/user/delete.delete.ts index d6ee5f4..72c86de 100644 --- a/backend/server/api/user/delete.delete.ts +++ b/backend/server/api/user/delete.delete.ts @@ -9,7 +9,10 @@ import { deleteUserCoachSessions, } from "../../db/user"; import { deleteProfile } from "../../db/profile"; -import { deleteAllMailConnections } from "../../db/mail"; +import { + deleteAllMailConnections, + deleteUserMailClassificationSamples, +} from "../../db/mail"; import { writeConsentRevoke } from "../../db/consent"; import { usePrisma } from "../../utils/prisma"; @@ -44,6 +47,9 @@ export default defineEventHandler(async (event) => { } // Delete all user data (DSGVO Art. 17) + // Reihenfolge: Samples VOR Connections löschen (oder parallel — FK-Reihenfolge + // egal weil wir nach userId filtern). Samples haben keine userId-FK-Cascade + // im Schema (connectionId ist nullable), daher manuelles Cleanup zwingend. await Promise.all([ deleteUserUrgeLogs(userId), deleteUserSosSessions(userId), @@ -52,6 +58,7 @@ export default defineEventHandler(async (event) => { deleteAllUserCustomDomains(userId), deleteUserTrustedContacts(userId), deleteUserCoachSessions(userId), + deleteUserMailClassificationSamples(userId), deleteAllMailConnections(userId), ]); diff --git a/backend/server/db/mail.ts b/backend/server/db/mail.ts index c2d9004..1933a07 100644 --- a/backend/server/db/mail.ts +++ b/backend/server/db/mail.ts @@ -193,13 +193,33 @@ export async function insertMailBlocked( // ─── MailClassificationSample ───────────────────────────────────────────────── +/** + * Löscht alle MailClassificationSamples eines Users. + * + * Warum manuell und nicht via Prisma-Cascade: + * MailClassificationSample hat KEINE userId-Relation mit onDelete: Cascade im Schema + * (connectionId hat Cascade, aber connectionId ist nullable). Samples mit + * connectionId=null wären nach deleteAllMailConnections() Orphans. + * + * Muss in delete.delete.ts VOR deleteAllMailConnections() aufgerufen werden + * (oder in Promise.all parallel) — FK-Reihenfolge spielt keine Rolle weil wir + * nach userId filtern, nicht nach connectionId. + * + * DSGVO Art. 17: User-Daten müssen vollständig gelöscht werden. + */ +export async function deleteUserMailClassificationSamples(userId: string) { + const db = usePrisma(); + return db.mailClassificationSample.deleteMany({ where: { userId } }); +} + /** * Schreibt einen Klassifikations-Sample-Eintrag für ML-Phase 3. * Wird nach JEDER Klassifikation aufgerufen (außer Layer 0 / Already-blocked Skips). * * DSGVO: Nur Features, keine Mail-Inhalte (kein Body). Subject + Sender sind - * kurzlebige Detection-Signale, kein narrativer Inhalt. Cascade-Delete bei - * User-Löschung (Art. 17). + * kurzlebige Detection-Signale, kein narrativer Inhalt. + * Vollständige Löschung bei Account-Delete via deleteUserMailClassificationSamples() + * (Art. 17) — NICHT via Prisma-Cascade, da userId keine FK-Relation hat. */ export async function insertMailClassificationSample(entry: { userId: string;