feat(database): UserDeletionAuditLog + cascade FKs for admin user delete

This commit is contained in:
isshaddad
2026-04-28 07:36:46 -04:00
parent 41434b536b
commit 5ad5e7fd32
2 changed files with 63 additions and 2 deletions
@@ -0,0 +1,35 @@
-- DropForeignKey
ALTER TABLE "public"."MfaBackupCode" DROP CONSTRAINT "MfaBackupCode_userId_fkey";
-- DropForeignKey
ALTER TABLE "public"."PersonalAccessToken" DROP CONSTRAINT "PersonalAccessToken_userId_fkey";
-- CreateTable
CREATE TABLE "public"."UserDeletionAuditLog" (
"id" TEXT NOT NULL,
"adminUserId" TEXT NOT NULL,
"adminEmail" TEXT NOT NULL,
"targetUserId" TEXT NOT NULL,
"targetEmail" TEXT NOT NULL,
"softDeletedOrgIds" TEXT[],
"reason" TEXT,
"ipAddress" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "UserDeletionAuditLog_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "UserDeletionAuditLog_adminUserId_idx" ON "public"."UserDeletionAuditLog"("adminUserId");
-- CreateIndex
CREATE INDEX "UserDeletionAuditLog_targetUserId_idx" ON "public"."UserDeletionAuditLog"("targetUserId");
-- CreateIndex
CREATE INDEX "UserDeletionAuditLog_createdAt_idx" ON "public"."UserDeletionAuditLog"("createdAt");
-- AddForeignKey
ALTER TABLE "public"."MfaBackupCode" ADD CONSTRAINT "MfaBackupCode_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."PersonalAccessToken" ADD CONSTRAINT "PersonalAccessToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -74,7 +74,7 @@ model MfaBackupCode {
/// Hash of the actual code
code String
user User @relation(fields: [userId], references: [id])
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
usedAt DateTime?
@@ -131,7 +131,7 @@ model PersonalAccessToken {
/// This is used to find the token in the database
hashedToken String @unique
user User @relation(fields: [userId], references: [id])
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
userId String
revokedAt DateTime?
@@ -2590,6 +2590,32 @@ model ImpersonationAuditLog {
@@index([createdAt])
}
model UserDeletionAuditLog {
id String @id @default(cuid())
/// Denormalized — audit row must survive after the target user is deleted.
/// No FKs to User; store IDs and emails as plain text.
adminUserId String
adminEmail String
targetUserId String
targetEmail String
/// Organization IDs that were soft-deleted as part of this user delete
/// (orgs where the deleted user was the sole member).
softDeletedOrgIds String[]
/// Optional: ticket link / GDPR request id / free-text SE note.
reason String?
ipAddress String?
createdAt DateTime @default(now())
@@index([adminUserId])
@@index([targetUserId])
@@index([createdAt])
}
enum CustomerQuerySource {
DASHBOARD
API