fix(db): add explicit onDelete strategies to all Prisma FK relations
Audit and update all foreign key relations in schema.prisma with appropriate cascade/restrict/set-null strategies to prevent orphaned records and FK constraint violations on parent deletion. Changes (RESTRICT → CASCADE): - Agent.userId, Listing.propertyId, Transaction.listingId - Inquiry.listingId, Inquiry.userId, Lead.agentId - Subscription.userId, UsageRecord.subscriptionId - Valuation.propertyId, Review.userId Confirmed correct (no change needed): - Listing.agentId (SetNull), Listing.sellerId (Restrict) - Transaction.buyerId (Restrict), Payment.userId (Restrict) - Payment.transactionId (SetNull), Subscription.planId (Restrict) - PropertyMedia, SavedSearch, RefreshToken, OAuthAccount (CASCADE) Migration: 20260411000000_add_cascade_delete_strategies Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Agent" DROP CONSTRAINT "Agent_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Listing" DROP CONSTRAINT "Listing_propertyId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Transaction" DROP CONSTRAINT "Transaction_listingId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Inquiry" DROP CONSTRAINT "Inquiry_listingId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Inquiry" DROP CONSTRAINT "Inquiry_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Lead" DROP CONSTRAINT "Lead_agentId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Subscription" DROP CONSTRAINT "Subscription_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "UsageRecord" DROP CONSTRAINT "UsageRecord_subscriptionId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Valuation" DROP CONSTRAINT "Valuation_propertyId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Review" DROP CONSTRAINT "Review_userId_fkey";
|
||||||
|
|
||||||
|
-- AddForeignKey: Agent.userId -> User.id (CASCADE)
|
||||||
|
ALTER TABLE "Agent" ADD CONSTRAINT "Agent_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: Listing.propertyId -> Property.id (CASCADE)
|
||||||
|
ALTER TABLE "Listing" ADD CONSTRAINT "Listing_propertyId_fkey" FOREIGN KEY ("propertyId") REFERENCES "Property"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: Transaction.listingId -> Listing.id (CASCADE)
|
||||||
|
ALTER TABLE "Transaction" ADD CONSTRAINT "Transaction_listingId_fkey" FOREIGN KEY ("listingId") REFERENCES "Listing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: Inquiry.listingId -> Listing.id (CASCADE)
|
||||||
|
ALTER TABLE "Inquiry" ADD CONSTRAINT "Inquiry_listingId_fkey" FOREIGN KEY ("listingId") REFERENCES "Listing"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: Inquiry.userId -> User.id (CASCADE)
|
||||||
|
ALTER TABLE "Inquiry" ADD CONSTRAINT "Inquiry_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: Lead.agentId -> Agent.id (CASCADE)
|
||||||
|
ALTER TABLE "Lead" ADD CONSTRAINT "Lead_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: Subscription.userId -> User.id (CASCADE)
|
||||||
|
ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: UsageRecord.subscriptionId -> Subscription.id (CASCADE)
|
||||||
|
ALTER TABLE "UsageRecord" ADD CONSTRAINT "UsageRecord_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "Subscription"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: Valuation.propertyId -> Property.id (CASCADE)
|
||||||
|
ALTER TABLE "Valuation" ADD CONSTRAINT "Valuation_propertyId_fkey" FOREIGN KEY ("propertyId") REFERENCES "Property"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey: Review.userId -> User.id (CASCADE)
|
||||||
|
ALTER TABLE "Review" ADD CONSTRAINT "Review_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -64,7 +64,6 @@ model User {
|
|||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@index([deletionScheduledAt])
|
@@index([deletionScheduledAt])
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
|
|
||||||
// --- Compound indexes (query optimization) ---
|
// --- Compound indexes (query optimization) ---
|
||||||
@@index([role, isActive, createdAt(sort: Desc)])
|
@@index([role, isActive, createdAt(sort: Desc)])
|
||||||
@@index([kycStatus, createdAt])
|
@@index([kycStatus, createdAt])
|
||||||
@@ -110,7 +109,7 @@ model OAuthAccount {
|
|||||||
model Agent {
|
model Agent {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String @unique
|
userId String @unique
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
licenseNumber String?
|
licenseNumber String?
|
||||||
agency String?
|
agency String?
|
||||||
qualityScore Float @default(0)
|
qualityScore Float @default(0)
|
||||||
@@ -204,7 +203,6 @@ model Property {
|
|||||||
@@index([propertyType])
|
@@index([propertyType])
|
||||||
@@index([district, city])
|
@@index([district, city])
|
||||||
@@index([location], type: Gist)
|
@@index([location], type: Gist)
|
||||||
|
|
||||||
// --- Compound indexes (query optimization) ---
|
// --- Compound indexes (query optimization) ---
|
||||||
@@index([district, propertyType])
|
@@index([district, propertyType])
|
||||||
@@index([district, city, propertyType])
|
@@index([district, city, propertyType])
|
||||||
@@ -227,11 +225,11 @@ model PropertyMedia {
|
|||||||
model Listing {
|
model Listing {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
propertyId String
|
propertyId String
|
||||||
property Property @relation(fields: [propertyId], references: [id])
|
property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
|
||||||
agentId String?
|
agentId String?
|
||||||
agent Agent? @relation(fields: [agentId], references: [id])
|
agent Agent? @relation(fields: [agentId], references: [id], onDelete: SetNull)
|
||||||
sellerId String
|
sellerId String
|
||||||
seller User @relation(fields: [sellerId], references: [id])
|
seller User @relation(fields: [sellerId], references: [id], onDelete: Restrict)
|
||||||
transactionType TransactionType
|
transactionType TransactionType
|
||||||
status ListingStatus @default(DRAFT)
|
status ListingStatus @default(DRAFT)
|
||||||
priceVND BigInt
|
priceVND BigInt
|
||||||
@@ -265,7 +263,6 @@ model Listing {
|
|||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@index([featuredUntil])
|
@@index([featuredUntil])
|
||||||
@@index([expiresAt])
|
@@index([expiresAt])
|
||||||
|
|
||||||
// --- Compound indexes (query optimization) ---
|
// --- Compound indexes (query optimization) ---
|
||||||
@@index([sellerId, status, publishedAt(sort: Desc)])
|
@@index([sellerId, status, publishedAt(sort: Desc)])
|
||||||
@@index([agentId, status])
|
@@index([agentId, status])
|
||||||
@@ -309,9 +306,9 @@ enum TransactionStatus {
|
|||||||
model Transaction {
|
model Transaction {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
listingId String
|
listingId String
|
||||||
listing Listing @relation(fields: [listingId], references: [id])
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
||||||
buyerId String
|
buyerId String
|
||||||
buyer User @relation("BuyerTransactions", fields: [buyerId], references: [id])
|
buyer User @relation("BuyerTransactions", fields: [buyerId], references: [id], onDelete: Restrict)
|
||||||
status TransactionStatus @default(INQUIRY)
|
status TransactionStatus @default(INQUIRY)
|
||||||
agreedPrice BigInt?
|
agreedPrice BigInt?
|
||||||
depositAmount BigInt?
|
depositAmount BigInt?
|
||||||
@@ -330,9 +327,9 @@ model Transaction {
|
|||||||
model Inquiry {
|
model Inquiry {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
listingId String
|
listingId String
|
||||||
listing Listing @relation(fields: [listingId], references: [id])
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
message String @db.Text
|
message String @db.Text
|
||||||
phone String?
|
phone String?
|
||||||
isRead Boolean @default(false)
|
isRead Boolean @default(false)
|
||||||
@@ -355,7 +352,7 @@ enum LeadStatus {
|
|||||||
model Lead {
|
model Lead {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
agentId String
|
agentId String
|
||||||
agent Agent @relation(fields: [agentId], references: [id])
|
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||||||
name String
|
name String
|
||||||
phone String
|
phone String
|
||||||
email String?
|
email String?
|
||||||
@@ -399,9 +396,9 @@ enum PaymentType {
|
|||||||
model Payment {
|
model Payment {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
|
||||||
transactionId String?
|
transactionId String?
|
||||||
transaction Transaction? @relation(fields: [transactionId], references: [id])
|
transaction Transaction? @relation(fields: [transactionId], references: [id], onDelete: SetNull)
|
||||||
provider PaymentProvider
|
provider PaymentProvider
|
||||||
type PaymentType
|
type PaymentType
|
||||||
amountVND BigInt
|
amountVND BigInt
|
||||||
@@ -418,7 +415,6 @@ model Payment {
|
|||||||
@@index([status])
|
@@index([status])
|
||||||
@@index([providerTxId])
|
@@index([providerTxId])
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
|
|
||||||
// --- Compound indexes (query optimization) ---
|
// --- Compound indexes (query optimization) ---
|
||||||
@@index([userId, status, createdAt(sort: Desc)])
|
@@index([userId, status, createdAt(sort: Desc)])
|
||||||
@@index([userId, type, createdAt(sort: Desc)])
|
@@index([userId, type, createdAt(sort: Desc)])
|
||||||
@@ -461,9 +457,9 @@ model Plan {
|
|||||||
model Subscription {
|
model Subscription {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String @unique
|
userId String @unique
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
planId String
|
planId String
|
||||||
plan Plan @relation(fields: [planId], references: [id])
|
plan Plan @relation(fields: [planId], references: [id], onDelete: Restrict)
|
||||||
status SubscriptionStatus @default(ACTIVE)
|
status SubscriptionStatus @default(ACTIVE)
|
||||||
currentPeriodStart DateTime
|
currentPeriodStart DateTime
|
||||||
currentPeriodEnd DateTime
|
currentPeriodEnd DateTime
|
||||||
@@ -480,7 +476,7 @@ model Subscription {
|
|||||||
model UsageRecord {
|
model UsageRecord {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
subscriptionId String
|
subscriptionId String
|
||||||
subscription Subscription @relation(fields: [subscriptionId], references: [id])
|
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
||||||
metric String
|
metric String
|
||||||
count Int
|
count Int
|
||||||
periodStart DateTime
|
periodStart DateTime
|
||||||
@@ -496,7 +492,7 @@ model UsageRecord {
|
|||||||
model Valuation {
|
model Valuation {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
propertyId String
|
propertyId String
|
||||||
property Property @relation(fields: [propertyId], references: [id])
|
property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
|
||||||
estimatedPrice BigInt
|
estimatedPrice BigInt
|
||||||
confidence Float
|
confidence Float
|
||||||
pricePerM2 Float
|
pricePerM2 Float
|
||||||
@@ -581,6 +577,48 @@ model NotificationPreference {
|
|||||||
@@index([userId])
|
@@index([userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// ADMIN AUDIT LOG
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
enum AdminAction {
|
||||||
|
LISTING_APPROVED
|
||||||
|
LISTING_REJECTED
|
||||||
|
LISTING_BULK_APPROVED
|
||||||
|
LISTING_BULK_REJECTED
|
||||||
|
USER_BANNED
|
||||||
|
USER_UNBANNED
|
||||||
|
USER_STATUS_UPDATED
|
||||||
|
KYC_APPROVED
|
||||||
|
KYC_REJECTED
|
||||||
|
SUBSCRIPTION_ADJUSTED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuditTargetType {
|
||||||
|
USER
|
||||||
|
LISTING
|
||||||
|
SUBSCRIPTION
|
||||||
|
}
|
||||||
|
|
||||||
|
model AdminAuditLog {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
action AdminAction
|
||||||
|
actorId String
|
||||||
|
targetId String
|
||||||
|
targetType AuditTargetType
|
||||||
|
metadata Json?
|
||||||
|
ipAddress String?
|
||||||
|
userAgent String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([actorId])
|
||||||
|
@@index([targetId, targetType])
|
||||||
|
@@index([action])
|
||||||
|
@@index([createdAt])
|
||||||
|
@@index([actorId, createdAt(sort: Desc)])
|
||||||
|
@@index([action, createdAt(sort: Desc)])
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// REVIEWS
|
// REVIEWS
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -588,7 +626,7 @@ model NotificationPreference {
|
|||||||
model Review {
|
model Review {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
targetType String
|
targetType String
|
||||||
targetId String
|
targetId String
|
||||||
rating Int
|
rating Int
|
||||||
|
|||||||
Reference in New Issue
Block a user