feat(api): implement GDPR-compliant user data deletion

- Add deletedAt/deletionScheduledAt fields to User model with indexes
- Implement 5 CQRS command handlers:
  - RequestUserDeletion: 30-day soft-delete grace period
  - CancelUserDeletion: restore within grace period
  - ForceDeleteUser: admin immediate deletion with PII anonymization
  - ProcessScheduledDeletions: cron-ready batch processor
  - ExportUserData: GDPR Article 20 data portability
- Cascade strategy: anonymize PII, expire listings, cancel subscriptions,
  delete reviews/inquiries/searches/notifications, preserve payments for audit
- Add UserDataController with DELETE /users/me, POST /users/me/cancel-deletion,
  GET /users/me/export, DELETE /users/:id/force (admin)
- 22 unit tests covering all handlers (160 files, 853 tests passing)
- Migration: 20260410000000_add_user_soft_delete_fields

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-10 05:43:54 +07:00
parent 34202f2527
commit e03c4699d0
21 changed files with 914 additions and 4 deletions

View File

@@ -0,0 +1,9 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "deletedAt" TIMESTAMP(3),
ADD COLUMN "deletionScheduledAt" TIMESTAMP(3);
-- CreateIndex
CREATE INDEX "User_deletedAt_idx" ON "User"("deletedAt");
-- CreateIndex
CREATE INDEX "User_deletionScheduledAt_idx" ON "User"("deletionScheduledAt");

View File

@@ -41,9 +41,11 @@ model User {
role UserRole @default(BUYER)
kycStatus KYCStatus @default(NONE)
kycData Json?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isActive Boolean @default(true)
deletedAt DateTime?
deletionScheduledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
agent Agent?
listings Listing[]
@@ -59,6 +61,8 @@ model User {
@@index([role])
@@index([kycStatus])
@@index([isActive])
@@index([deletedAt])
@@index([deletionScheduledAt])
@@index([createdAt])
// --- Compound indexes (query optimization) ---