Skip to content

Commit 5ad5e7f

Browse files
committed
feat(database): UserDeletionAuditLog + cascade FKs for admin user delete
1 parent 41434b5 commit 5ad5e7f

2 files changed

Lines changed: 63 additions & 2 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-- DropForeignKey
2+
ALTER TABLE "public"."MfaBackupCode" DROP CONSTRAINT "MfaBackupCode_userId_fkey";
3+
4+
-- DropForeignKey
5+
ALTER TABLE "public"."PersonalAccessToken" DROP CONSTRAINT "PersonalAccessToken_userId_fkey";
6+
7+
-- CreateTable
8+
CREATE TABLE "public"."UserDeletionAuditLog" (
9+
"id" TEXT NOT NULL,
10+
"adminUserId" TEXT NOT NULL,
11+
"adminEmail" TEXT NOT NULL,
12+
"targetUserId" TEXT NOT NULL,
13+
"targetEmail" TEXT NOT NULL,
14+
"softDeletedOrgIds" TEXT[],
15+
"reason" TEXT,
16+
"ipAddress" TEXT,
17+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
18+
19+
CONSTRAINT "UserDeletionAuditLog_pkey" PRIMARY KEY ("id")
20+
);
21+
22+
-- CreateIndex
23+
CREATE INDEX "UserDeletionAuditLog_adminUserId_idx" ON "public"."UserDeletionAuditLog"("adminUserId");
24+
25+
-- CreateIndex
26+
CREATE INDEX "UserDeletionAuditLog_targetUserId_idx" ON "public"."UserDeletionAuditLog"("targetUserId");
27+
28+
-- CreateIndex
29+
CREATE INDEX "UserDeletionAuditLog_createdAt_idx" ON "public"."UserDeletionAuditLog"("createdAt");
30+
31+
-- AddForeignKey
32+
ALTER TABLE "public"."MfaBackupCode" ADD CONSTRAINT "MfaBackupCode_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
33+
34+
-- AddForeignKey
35+
ALTER TABLE "public"."PersonalAccessToken" ADD CONSTRAINT "PersonalAccessToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

internal-packages/database/prisma/schema.prisma

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ model MfaBackupCode {
7474
/// Hash of the actual code
7575
code String
7676
77-
user User @relation(fields: [userId], references: [id])
77+
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
7878
userId String
7979
8080
usedAt DateTime?
@@ -131,7 +131,7 @@ model PersonalAccessToken {
131131
/// This is used to find the token in the database
132132
hashedToken String @unique
133133
134-
user User @relation(fields: [userId], references: [id])
134+
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
135135
userId String
136136
137137
revokedAt DateTime?
@@ -2590,6 +2590,32 @@ model ImpersonationAuditLog {
25902590
@@index([createdAt])
25912591
}
25922592

2593+
model UserDeletionAuditLog {
2594+
id String @id @default(cuid())
2595+
2596+
/// Denormalized — audit row must survive after the target user is deleted.
2597+
/// No FKs to User; store IDs and emails as plain text.
2598+
adminUserId String
2599+
adminEmail String
2600+
targetUserId String
2601+
targetEmail String
2602+
2603+
/// Organization IDs that were soft-deleted as part of this user delete
2604+
/// (orgs where the deleted user was the sole member).
2605+
softDeletedOrgIds String[]
2606+
2607+
/// Optional: ticket link / GDPR request id / free-text SE note.
2608+
reason String?
2609+
2610+
ipAddress String?
2611+
2612+
createdAt DateTime @default(now())
2613+
2614+
@@index([adminUserId])
2615+
@@index([targetUserId])
2616+
@@index([createdAt])
2617+
}
2618+
25932619
enum CustomerQuerySource {
25942620
DASHBOARD
25952621
API

0 commit comments

Comments
 (0)