// 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 }