wedding-platform / prisma /schema.prisma
imeshuek's picture
Upload prisma/schema.prisma
39d82a7 verified
// PostgreSQL 16 Schema
// Run: npx prisma migrate dev --name init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ── Users & Auth ──
model User {
id String @id @default(uuid()) @db.Uuid
email String @unique
emailVerified DateTime?
passwordHash String? // bcrypt/Argon2 β€” null for OIDC users
fullName String
phone String?
avatarUrl String?
// Multi-role support
roles UserRole[]
// OIDC identities
identities OidcIdentity[]
// Profile type (for account switching)
clientProfile ClientProfile?
vendorProfile VendorProfile?
adminProfile AdminProfile?
// Sessions
sessions Session[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // soft delete for GDPR
@@index([email])
@@index([deletedAt])
}
model UserRole {
id String @id @default(uuid()) @db.Uuid
userId String @db.Uuid
role Role // CLIENT | VENDOR | ADMIN
isPrimary Boolean @default(false)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, role])
@@index([userId])
}
enum Role {
CLIENT
VENDOR
ADMIN
}
model OidcIdentity {
id String @id @default(uuid()) @db.Uuid
userId String @db.Uuid
provider String // google | apple | cognito | keycloak
subject String // OIDC sub claim
idToken String?
accessToken String?
refreshToken String?
expiresAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, subject])
@@index([userId])
}
model Session {
id String @id @default(uuid()) @db.Uuid
userId String @db.Uuid
token String @unique
portal String // client | vendor | admin
ipAddress String?
userAgent String?
expiresAt DateTime
revokedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([token])
@@index([userId, portal])
}
// ── Client Profile ──
model ClientProfile {
id String @id @default(uuid()) @db.Uuid
userId String @unique @db.Uuid
partnerName String?
weddingDate DateTime?
location String?
guestCount Int?
budget Decimal? @db.Decimal(12, 2)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
bookings Booking[]
shortlists Shortlist[]
workspaces Workspace[]
}
// ── Vendor Profile ──
model VendorProfile {
id String @id @default(uuid()) @db.Uuid
userId String @unique @db.Uuid
businessName String
categoryId String @db.Uuid
description String?
district String?
address String?
website String?
socialLinks Json? // {facebook, instagram, etc}
isVerified Boolean @default(false)
isPublished Boolean @default(false)
kycStatus KycStatus @default(PENDING)
kycSubmittedAt DateTime?
kycApprovedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
category Category @relation(fields: [categoryId], references: [id])
packages Package[]
bookings Booking[]
leads Lead[]
availability VendorAvailability[]
}
enum KycStatus {
PENDING
SUBMITTED
APPROVED
REJECTED
}
// ── Admin Profile ──
model AdminProfile {
id String @id @default(uuid()) @db.Uuid
userId String @unique @db.Uuid
permissions String[] // MANAGE_VENDORS | MANAGE_CONTRACTS | MANAGE_USERS | SYSTEM_ADMIN
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
// ── Categories ──
model Category {
id String @id @default(uuid()) @db.Uuid
name String @unique
slug String @unique
emoji String?
status ContentStatus @default(PUBLISHED)
sortOrder Int @default(0)
parentId String? @db.Uuid
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id])
children Category[] @relation("CategoryHierarchy")
vendors VendorProfile[]
}
enum ContentStatus {
DRAFT
PUBLISHED
ARCHIVED
}
// ── Packages ──
model Package {
id String @id @default(uuid()) @db.Uuid
vendorId String @db.Uuid
name String
description String?
price Decimal @db.Decimal(12, 2)
guestCapacity Int?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor VendorProfile @relation(fields: [vendorId], references: [id], onDelete: Cascade)
@@index([vendorId])
}
// ── Vendor Availability ──
model VendorAvailability {
id String @id @default(uuid()) @db.Uuid
vendorId String @db.Uuid
date DateTime @db.Date
type AvailabilityType
label String?
createdAt DateTime @default(now())
vendor VendorProfile @relation(fields: [vendorId], references: [id], onDelete: Cascade)
@@unique([vendorId, date])
@@index([vendorId, date])
}
enum AvailabilityType {
AVAILABLE
BOOKED
BLOCKED
}
// ── Bookings ──
model Booking {
id String @id @default(uuid()) @db.Uuid
clientId String @db.Uuid
vendorId String @db.Uuid
packageId String? @db.Uuid
status BookingStatus @default(PENDING)
idempotencyKey String @unique // prevent duplicate writes
date DateTime
guestCount Int?
notes String?
totalAmount Decimal? @db.Decimal(12, 2)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
client ClientProfile @relation(fields: [clientId], references: [id])
vendor VendorProfile @relation(fields: [vendorId], references: [id])
package Package? @relation(fields: [packageId], references: [id])
contracts Contract[]
@@index([clientId])
@@index([vendorId])
@@index([status])
}
enum BookingStatus {
PENDING
CONFIRMED
CANCELLED
COMPLETED
}
// ── Shortlist ──
model Shortlist {
id String @id @default(uuid()) @db.Uuid
clientId String @db.Uuid
vendorId String @db.Uuid
notes String?
createdAt DateTime @default(now())
client ClientProfile @relation(fields: [clientId], references: [id], onDelete: Cascade)
@@unique([clientId, vendorId])
@@index([clientId])
}
// ── Workspaces (client planning) ──
model Workspace {
id String @id @default(uuid()) @db.Uuid
clientId String @db.Uuid
name String
tasks Json? // embedded checklist items
timeline Json? // embedded timeline events
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
client ClientProfile @relation(fields: [clientId], references: [id], onDelete: Cascade)
@@index([clientId])
}
// ── Leads ──
model Lead {
id String @id @default(uuid()) @db.Uuid
vendorId String @db.Uuid
clientId String? @db.Uuid
name String
type String
status LeadStatus @default(NEW)
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor VendorProfile @relation(fields: [vendorId], references: [id])
@@index([vendorId, status])
}
enum LeadStatus {
NEW
VIEWED
QUOTED
CONVERTED
LOST
}
// ── Reviews ──
model Review {
id String @id @default(uuid()) @db.Uuid
vendorId String @db.Uuid
clientId String @db.Uuid
rating Int // 1-5
content String?
isFlagged Boolean @default(false)
createdAt DateTime @default(now())
vendor VendorProfile @relation(fields: [vendorId], references: [id])
@@unique([vendorId, clientId])
@@index([vendorId])
}
// ── CONTRACTS ──
model Contract {
id String @id @default(uuid()) @db.Uuid
bookingId String @db.Uuid
vendorId String @db.Uuid
clientId String @db.Uuid
// Contract metadata
title String
serviceType String?
totalAmount Decimal @db.Decimal(12, 2)
status ContractStatus @default(DRAFT)
version Int @default(1)
// Timestamps
sentAt DateTime?
viewedAt DateTime?
signedAt DateTime?
vendorSignedAt DateTime?
clientSignedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
booking Booking @relation(fields: [bookingId], references: [id])
sections ContractSection[]
versions ContractVersion[]
signatures ContractSignature[]
auditLogs ContractAuditLog[]
deliverables ContractDeliverable[]
@@index([bookingId])
@@index([vendorId, status])
@@index([clientId, status])
@@index([status])
}
enum ContractStatus {
DRAFT
SENT
VIEWED
SIGNED
ACTIVE
AMENDED
DISPUTED
COMPLETED
CANCELLED
}
model ContractSection {
id String @id @default(uuid()) @db.Uuid
contractId String @db.Uuid
sortOrder Int
title String
content String
createdAt DateTime @default(now())
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
@@index([contractId])
}
model ContractDeliverable {
id String @id @default(uuid()) @db.Uuid
contractId String @db.Uuid
description String
dueDate DateTime?
quantity Int?
acceptanceCriteria String?
isCompleted Boolean @default(false)
completedAt DateTime?
createdAt DateTime @default(now())
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
@@index([contractId])
}
// Immutable contract version snapshot
model ContractVersion {
id String @id @default(uuid()) @db.Uuid
contractId String @db.Uuid
version Int
reason String? // reason for amendment
snapshot Json // full frozen copy of contract + sections + deliverables
createdBy String // userId
createdAt DateTime @default(now())
contract Contract @relation(fields: [contractId], references: [id])
@@unique([contractId, version])
@@index([contractId])
}
model ContractSignature {
id String @id @default(uuid()) @db.Uuid
contractId String @db.Uuid
contractVersionId String @db.Uuid
signerId String // userId
signerRole Role
signature String? // digital signature data
ipAddress String?
userAgent String?
signedAt DateTime @default(now())
contract Contract @relation(fields: [contractId], references: [id])
contractVersion ContractVersion @relation(fields: [contractVersionId], references: [id])
@@index([contractId])
@@index([signerId])
}
// Append-only audit log β€” rows are NEVER updated or deleted
model ContractAuditLog {
id String @id @default(uuid()) @db.Uuid
contractId String @db.Uuid
action String // created | sent | viewed | signed | declined | amended | disputed | resolved | completed
actorId String? // userId
actorRole Role?
detail String?
metadata Json? // version diff, IP, etc
createdAt DateTime @default(now())
contract Contract @relation(fields: [contractId], references: [id])
@@index([contractId, createdAt])
}
// ── Contract Templates (vendor-submitted, admin-approved) ──
model ContractTemplate {
id String @id @default(uuid()) @db.Uuid
vendorId String? @db.Uuid
name String
category String
status ContentStatus @default(DRAFT) // admin approval workflow
structure Json // frozen section structure + deliverable templates
submittedAt DateTime @default(now())
approvedAt DateTime?
approvedBy String? // admin userId
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
vendor VendorProfile? @relation(fields: [vendorId], references: [id])
@@index([vendorId])
@@index([status])
}
// ── Admin Audit Log ──
model AdminAuditLog {
id String @id @default(uuid()) @db.Uuid
adminId String @db.Uuid
action String
target String? // resource acted on
detail String?
createdAt DateTime @default(now())
@@index([adminId, createdAt])
}
// ── Notifications ──
model Notification {
id String @id @default(uuid()) @db.Uuid
userId String @db.Uuid
title String
body String
type String // booking | contract | review | system
isRead Boolean @default(false)
link String?
createdAt DateTime @default(now())
@@index([userId, isRead, createdAt])
}
// ── Rate Limiting (backed by Redis; schema for reference) ──
// Key pattern: ratelimit:{ip}:{method}:{path}:{window}
// Redis INCR + EXPIRE