686 lines
19 KiB
Plaintext
686 lines
19 KiB
Plaintext
// Luxe Platform Production Database Schema
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
// ==========================================
|
|
// ENUMS
|
|
// ==========================================
|
|
|
|
enum RoleType {
|
|
GUEST
|
|
USER
|
|
ADVERTISER
|
|
AGENCY
|
|
MODERATOR
|
|
SUPPORT
|
|
CONTENT_MODERATOR
|
|
FINANCE
|
|
ADMIN
|
|
SUPER_ADMIN
|
|
}
|
|
|
|
enum VerificationStatus {
|
|
UNVERIFIED
|
|
PENDING
|
|
VERIFIED
|
|
REJECTED
|
|
}
|
|
|
|
enum ListingStatus {
|
|
DRAFT
|
|
PENDING_APPROVAL
|
|
ACTIVE
|
|
REJECTED
|
|
EXPIRED
|
|
ARCHIVED
|
|
SUSPENDED
|
|
}
|
|
|
|
enum TransactionType {
|
|
RECHARGE
|
|
DEBIT
|
|
CREDIT
|
|
WITHDRAWAL
|
|
REFUND
|
|
BONUS
|
|
}
|
|
|
|
enum TransactionStatus {
|
|
PENDING
|
|
SUCCESS
|
|
FAILED
|
|
CANCELLED
|
|
}
|
|
|
|
enum PaymentProvider {
|
|
STRIPE
|
|
RAZORPAY
|
|
PAYPAL
|
|
WALLET
|
|
}
|
|
|
|
enum AdType {
|
|
BANNER
|
|
SPONSORED_LISTING
|
|
BOOST_LISTING
|
|
}
|
|
|
|
enum AdTargetType {
|
|
CITY
|
|
CATEGORY
|
|
GLOBAL
|
|
}
|
|
|
|
enum NotificationChannel {
|
|
EMAIL
|
|
SMS
|
|
PUSH
|
|
WHATSAPP
|
|
TELEGRAM
|
|
IN_APP
|
|
}
|
|
|
|
enum ReportStatus {
|
|
OPEN
|
|
UNDER_REVIEW
|
|
RESOLVED
|
|
DISMISSED
|
|
}
|
|
|
|
enum TicketPriority {
|
|
LOW
|
|
MEDIUM
|
|
HIGH
|
|
URGENT
|
|
}
|
|
|
|
enum TicketStatus {
|
|
OPEN
|
|
IN_PROGRESS
|
|
WAITING_ON_USER
|
|
RESOLVED
|
|
CLOSED
|
|
}
|
|
|
|
// ==========================================
|
|
// AUTH & USER IDENTITY
|
|
// ==========================================
|
|
|
|
model User {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
email String @unique
|
|
phone String? @unique
|
|
username String @unique
|
|
passwordHash String
|
|
role RoleType @default(USER)
|
|
status String @default("ACTIVE") // ACTIVE, SUSPENDED, BANNED
|
|
emailVerifiedAt DateTime?
|
|
phoneVerifiedAt DateTime?
|
|
twoFactorEnabled Boolean @default(false)
|
|
twoFactorSecret String?
|
|
avatar String?
|
|
bio String?
|
|
language String @default("en")
|
|
currency String @default("USD")
|
|
lastLoginAt DateTime?
|
|
lastLoginIp String?
|
|
failedLoginCount Int @default(0)
|
|
lockedUntil DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
// Relations
|
|
sessions Session[]
|
|
devices Device[]
|
|
userRoles UserRole[]
|
|
profile AdvertiserProfile?
|
|
wallet Wallet?
|
|
reviews Review[]
|
|
favorites Favorite[]
|
|
savedSearches SavedSearch[]
|
|
notifications Notification[]
|
|
sentMessages Message[] @relation("SentMessages")
|
|
receivedMessages Message[] @relation("ReceivedMessages")
|
|
blogComments BlogComment[]
|
|
forumThreads ForumThread[]
|
|
forumReplies ForumReply[]
|
|
reportsSubmitted Report[] @relation("ReporterUser")
|
|
supportTickets SupportTicket[]
|
|
auditLogs AuditLog[]
|
|
|
|
@@index([email])
|
|
@@index([phone])
|
|
@@index([username])
|
|
@@index([role, status])
|
|
}
|
|
|
|
model Role {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
name String @unique
|
|
code RoleType @unique
|
|
description String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
rolePermissions RolePermission[]
|
|
userRoles UserRole[]
|
|
}
|
|
|
|
model Permission {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
resource String // e.g. listing, user, invoice
|
|
action String // e.g. create, read, update, delete, approve
|
|
description String?
|
|
createdAt DateTime @default(now())
|
|
|
|
rolePermissions RolePermission[]
|
|
|
|
@@unique([resource, action])
|
|
}
|
|
|
|
model RolePermission {
|
|
roleId String @db.Uuid
|
|
permissionId String @db.Uuid
|
|
|
|
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([roleId, permissionId])
|
|
}
|
|
|
|
model UserRole {
|
|
userId String @db.Uuid
|
|
roleId String @db.Uuid
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([userId, roleId])
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
refreshToken String @unique
|
|
ipAddress String?
|
|
userAgent String?
|
|
isRevoked Boolean @default(false)
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([refreshToken])
|
|
}
|
|
|
|
model Device {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
deviceToken String @unique
|
|
platform String // ios, android, web
|
|
ipAddress String?
|
|
lastSeenAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
// ==========================================
|
|
// ADVERTISER & LISTING DOMAIN
|
|
// ==========================================
|
|
|
|
model AdvertiserProfile {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @unique @db.Uuid
|
|
type String @default("INDIVIDUAL") // INDIVIDUAL, AGENCY
|
|
agencyName String?
|
|
contactEmail String?
|
|
contactPhone String?
|
|
whatsapp String?
|
|
telegram String?
|
|
website String?
|
|
verificationStatus VerificationStatus @default(UNVERIFIED)
|
|
rating Float @default(0.0)
|
|
reviewCount Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
listings Listing[]
|
|
verifications VerificationRequest[]
|
|
}
|
|
|
|
model Category {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
name String
|
|
slug String @unique
|
|
icon String?
|
|
description String?
|
|
parentId String? @db.Uuid
|
|
isFeatured Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id])
|
|
children Category[] @relation("CategoryHierarchy")
|
|
listings Listing[]
|
|
|
|
@@index([slug])
|
|
}
|
|
|
|
model City {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
name String
|
|
slug String @unique
|
|
state String?
|
|
country String @default("US")
|
|
image String?
|
|
latitude Float?
|
|
longitude Float?
|
|
createdAt DateTime @default(now())
|
|
|
|
areas Area[]
|
|
listings Listing[]
|
|
|
|
@@index([slug])
|
|
}
|
|
|
|
model Area {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
cityId String @db.Uuid
|
|
name String
|
|
slug String
|
|
latitude Float?
|
|
longitude Float?
|
|
createdAt DateTime @default(now())
|
|
|
|
city City @relation(fields: [cityId], references: [id], onDelete: Cascade)
|
|
listings Listing[]
|
|
|
|
@@unique([cityId, slug])
|
|
}
|
|
|
|
model Listing {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
profileId String @db.Uuid
|
|
categoryId String @db.Uuid
|
|
cityId String @db.Uuid
|
|
areaId String? @db.Uuid
|
|
title String
|
|
slug String @unique
|
|
description String
|
|
price Float
|
|
currency String @default("USD")
|
|
age Int?
|
|
height String?
|
|
tagline String?
|
|
isOnline Boolean @default(true)
|
|
isFeatured Boolean @default(false)
|
|
isPremium Boolean @default(false)
|
|
isVerified Boolean @default(false)
|
|
status ListingStatus @default(PENDING_APPROVAL)
|
|
viewsCount Int @default(0)
|
|
callsCount Int @default(0)
|
|
languages String[] @default([])
|
|
tags String[] @default([])
|
|
attributes Json? @default("{}")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
advertiser AdvertiserProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
category Category @relation(fields: [categoryId], references: [id])
|
|
city City @relation(fields: [cityId], references: [id])
|
|
area Area? @relation(fields: [areaId], references: [id])
|
|
gallery ListingGallery[]
|
|
reviews Review[]
|
|
favorites Favorite[]
|
|
ads Advertisement[]
|
|
|
|
@@index([slug])
|
|
@@index([cityId, categoryId, status])
|
|
@@index([isFeatured, isOnline])
|
|
@@index([price])
|
|
}
|
|
|
|
model ListingGallery {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
listingId String @db.Uuid
|
|
url String
|
|
mediaType String @default("IMAGE") // IMAGE, VIDEO
|
|
thumbnail String?
|
|
isPrimary Boolean @default(false)
|
|
order Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
|
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([listingId])
|
|
}
|
|
|
|
model Review {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
listingId String @db.Uuid
|
|
userId String @db.Uuid
|
|
rating Int // 1 to 5
|
|
comment String
|
|
isApproved Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
|
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([listingId])
|
|
}
|
|
|
|
model Favorite {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
listingId String @db.Uuid
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, listingId])
|
|
}
|
|
|
|
model SavedSearch {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
name String
|
|
filters Json
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
// ==========================================
|
|
// WALLET, FINANCIAL LEDGER & PAYMENTS
|
|
// ==========================================
|
|
|
|
model Wallet {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @unique @db.Uuid
|
|
balance Float @default(0.0)
|
|
credits Float @default(0.0)
|
|
currency String @default("USD")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
ledgers WalletLedger[]
|
|
invoices Invoice[]
|
|
}
|
|
|
|
model WalletLedger {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
walletId String @db.Uuid
|
|
type TransactionType
|
|
amount Float
|
|
balanceBefore Float
|
|
balanceAfter Float
|
|
referenceId String? // Order or Transaction ID
|
|
description String
|
|
createdAt DateTime @default(now())
|
|
|
|
wallet Wallet @relation(fields: [walletId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([walletId])
|
|
}
|
|
|
|
model Transaction {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
provider PaymentProvider
|
|
type TransactionType
|
|
status TransactionStatus @default(PENDING)
|
|
amount Float
|
|
currency String @default("USD")
|
|
gatewayTxnId String? @unique
|
|
metadata Json? @default("{}")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([gatewayTxnId])
|
|
}
|
|
|
|
model Invoice {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
walletId String @db.Uuid
|
|
invoiceNumber String @unique
|
|
amount Float
|
|
taxAmount Float @default(0.0)
|
|
totalAmount Float
|
|
currency String @default("USD")
|
|
pdfUrl String?
|
|
createdAt DateTime @default(now())
|
|
|
|
wallet Wallet @relation(fields: [walletId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model Coupon {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
code String @unique
|
|
discountPct Float?
|
|
fixedAmount Float?
|
|
minPurchase Float @default(0.0)
|
|
maxUses Int @default(100)
|
|
usedCount Int @default(0)
|
|
expiresAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model Subscription {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
planName String // BASIC, VIP, AGENCY_PRO
|
|
price Float
|
|
status String @default("ACTIVE")
|
|
startsAt DateTime @default(now())
|
|
endsAt DateTime
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model Advertisement {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
listingId String? @db.Uuid
|
|
type AdType
|
|
target AdTargetType @default(GLOBAL)
|
|
targetId String? // City ID or Category ID
|
|
imageUrl String?
|
|
linkUrl String?
|
|
impressions Int @default(0)
|
|
clicks Int @default(0)
|
|
startsAt DateTime
|
|
endsAt DateTime
|
|
status String @default("ACTIVE")
|
|
createdAt DateTime @default(now())
|
|
|
|
listing Listing? @relation(fields: [listingId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
// ==========================================
|
|
// MESSAGING & NOTIFICATIONS
|
|
// ==========================================
|
|
|
|
model Message {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
senderId String @db.Uuid
|
|
receiverId String @db.Uuid
|
|
content String
|
|
isRead Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
sender User @relation("SentMessages", fields: [senderId], references: [id], onDelete: Cascade)
|
|
receiver User @relation("ReceivedMessages", fields: [receiverId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([senderId, receiverId])
|
|
}
|
|
|
|
model Notification {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
channel NotificationChannel @default(IN_APP)
|
|
title String
|
|
body String
|
|
data Json?
|
|
isRead Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, isRead])
|
|
}
|
|
|
|
// ==========================================
|
|
// BLOG, FORUM & CONTENT
|
|
// ==========================================
|
|
|
|
model BlogPost {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
title String
|
|
slug String @unique
|
|
excerpt String
|
|
content String
|
|
coverImage String?
|
|
authorName String @default("Luxe Editorial")
|
|
category String
|
|
tags String[] @default([])
|
|
readingTime Int @default(3)
|
|
likesCount Int @default(0)
|
|
isPublished Boolean @default(true)
|
|
publishedAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
comments BlogComment[]
|
|
|
|
@@index([slug])
|
|
}
|
|
|
|
model BlogComment {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
postId String @db.Uuid
|
|
userId String @db.Uuid
|
|
comment String
|
|
createdAt DateTime @default(now())
|
|
|
|
post BlogPost @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model ForumThread {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
title String
|
|
slug String @unique
|
|
content String
|
|
category String
|
|
isPinned Boolean @default(false)
|
|
viewsCount Int @default(0)
|
|
likesCount Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
replies ForumReply[]
|
|
|
|
@@index([slug])
|
|
}
|
|
|
|
model ForumReply {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
threadId String @db.Uuid
|
|
userId String @db.Uuid
|
|
content String
|
|
createdAt DateTime @default(now())
|
|
|
|
thread ForumThread @relation(fields: [threadId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
// ==========================================
|
|
// MODERATION, VERIFICATION & AUDIT LOGS
|
|
// ==========================================
|
|
|
|
model VerificationRequest {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
profileId String @db.Uuid
|
|
idProofUrl String
|
|
selfieUrl String
|
|
notes String?
|
|
status VerificationStatus @default(PENDING)
|
|
rejectionReason String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
profile AdvertiserProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model Report {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
reporterId String @db.Uuid
|
|
targetType String // LISTING, USER, REVIEW, FORUM
|
|
targetId String
|
|
reason String
|
|
description String?
|
|
status ReportStatus @default(OPEN)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
reporter User @relation("ReporterUser", fields: [reporterId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model SupportTicket {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @db.Uuid
|
|
ticketNum String @unique
|
|
subject String
|
|
description String
|
|
priority TicketPriority @default(MEDIUM)
|
|
status TicketStatus @default(OPEN)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model AuditLog {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String? @db.Uuid
|
|
action String // e.g. USER_BAN, PROFILE_VERIFY, ROLE_UPDATE
|
|
resource String // e.g. User, Listing
|
|
resourceId String?
|
|
ipAddress String?
|
|
userAgent String?
|
|
metadata Json? @default("{}")
|
|
createdAt DateTime @default(now())
|
|
|
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([action])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model FeatureFlag {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
key String @unique
|
|
isEnabled Boolean @default(false)
|
|
description String?
|
|
metadata Json? @default("{}")
|
|
updatedAt DateTime @updatedAt
|
|
}
|