chahinebrini 343f9ab567 fix(mail): DSGVO Art. 17 — manuelles Sample-Cleanup bei Account-Delete
MailClassificationSample hat keine userId-FK-Cascade im Schema (connectionId
ist nullable). Samples ohne connectionId blieben nach deleteAllMailConnections()
als Orphans stehen. Neuer Helper deleteUserMailClassificationSamples() löscht
explizit nach userId — wird in delete.delete.ts parallel zu anderen Lösch-Ops
ausgeführt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 22:05:35 +02:00

73 lines
2.6 KiB
TypeScript

import { serverSupabaseServiceRole } from "../../utils/useSupabase";
import { deleteUserUrgeLogs } from "../../db/urge";
import { deleteUserSosSessions } from "../../db/sosSession";
import { deleteUserStreaks } from "../../db/streak";
import { deleteUserPosts } from "../../db/community";
import { deleteAllUserCustomDomains } from "../../db/domains";
import {
deleteUserTrustedContacts,
deleteUserCoachSessions,
} from "../../db/user";
import { deleteProfile } from "../../db/profile";
import {
deleteAllMailConnections,
deleteUserMailClassificationSamples,
} from "../../db/mail";
import { writeConsentRevoke } from "../../db/consent";
import { usePrisma } from "../../utils/prisma";
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const supabase = serverSupabaseServiceRole(event);
const userId = user.id;
// DSGVO Art. 9: Consent-Widerruf für alle MailConnections vor Löschung
// (append-only — consent_logs-Rows bleiben für Beweiszwecke erhalten)
const db = usePrisma();
const mailConnections = await db.mailConnection.findMany({
where: { userId },
select: { id: true, consentVersion: true, authMethod: true },
});
const now = new Date();
for (const conn of mailConnections) {
// Widerruf-Log schreiben — fire-and-forget, kein throw wenn es fehlschlägt
writeConsentRevoke({
userId,
consentType: "art9-mail",
consentVersion: conn.consentVersion ?? "none",
revokedAt: now,
revokeReason: "account_deleted",
mailConnectionId: conn.id,
}).catch(() => {});
// TODO (mo — Mail-Stack): OAuth Token-Revoke bei MS bevor Row gelöscht wird.
// Wenn conn.authMethod === 'oauth2_microsoft': Token-Revoke best-effort.
// Tracking: consent-gap-plan.md TODO #2
}
// 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),
deleteUserStreaks(userId),
deleteUserPosts(userId),
deleteAllUserCustomDomains(userId),
deleteUserTrustedContacts(userId),
deleteUserCoachSessions(userId),
deleteUserMailClassificationSamples(userId),
deleteAllMailConnections(userId),
]);
// Profil zuletzt löschen (FK-Abhängigkeiten sind bereits entfernt)
await deleteProfile(userId).catch(() => {});
// Auth-User löschen (bleibt Supabase)
await supabase.auth.admin.deleteUser(userId);
return { success: true };
});