// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ===== ENUMS ===== enum CampaignStatus { DRAFT IN_REVIEW ACTIVE PAUSED ENDED REJECTED } enum BillingModel { FIXED CPM CPC } enum CampaignObjective { AWARENESS SALES } enum SurfaceKey { HOME_FEATURED_CAROUSEL HOME_TOP_EVENTS CATEGORY_FEATURED CITY_TRENDING SEARCH_BOOSTED } enum TrackingEventType { IMPRESSION CLICK } // ===== MODELS ===== model Campaign { id String @id @default(uuid()) partnerId String name String objective CampaignObjective status CampaignStatus @default(DRAFT) startAt DateTime endAt DateTime billingModel BillingModel totalBudget Decimal @db.Decimal(10, 2) dailyCap Decimal? @db.Decimal(10, 2) spent Decimal @default(0) @db.Decimal(10, 2) // Targeting (stored as JSON) targeting Json // { cityIds: [], categoryIds: [], countryCodes: [] } frequencyCap Int @default(0) // 0 = unlimited // Relations placements SponsoredPlacement[] events CampaignEvent[] // Many-to-many via join table or array of IDs approvedBy String? rejectedReason String? createdBy String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt auditLogs CampaignAuditLog[] } model SponsoredPlacement { id String @id @default(uuid()) campaignId String campaign Campaign @relation(fields: [campaignId], references: [id]) eventId String surfaceKey SurfaceKey priority String @default("SPONSORED") bid Decimal @db.Decimal(10, 2) status String @default("ACTIVE") // ACTIVE, PAUSED rank Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([campaignId, eventId, surfaceKey]) } model CampaignEvent { id String @id @default(uuid()) campaignId String campaign Campaign @relation(fields: [campaignId], references: [id]) eventId String // Reference to Event table (not shown here) @@unique([campaignId, eventId]) } model AdTrackingEvent { id String @id @default(uuid()) type TrackingEventType placementId String campaignId String surfaceKey SurfaceKey eventId String userId String? anonId String sessionId String timestamp DateTime @default(now()) device String? cityId String? @@index([campaignId, type, timestamp]) @@index([anonId, timestamp]) // For frequency capping queries } model PlacementDailyStats { id String @id @default(uuid()) campaignId String placementId String surfaceKey SurfaceKey date DateTime @db.Date impressions Int @default(0) clicks Int @default(0) spend Decimal @default(0) @db.Decimal(10, 2) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([campaignId, placementId, surfaceKey, date]) } model CampaignAuditLog { id String @id @default(uuid()) campaignId String campaign Campaign @relation(fields: [campaignId], references: [id]) actorId String action String // CREATED, UPDATED, SUBMITTED, APPROVED, REJECTED, PAUSED, RESUMED details Json? // Changed fields, reason, etc. createdAt DateTime @default(now()) } // ===== PARTNER GOVERNANCE ===== enum KYCStatus { PENDING VERIFIED REJECTED } enum KYCDocStatus { PENDING APPROVED REJECTED } enum PartnerEventStatus { PENDING_REVIEW LIVE DRAFT COMPLETED CANCELLED REJECTED } model PartnerProfile { id String @id @default(cuid()) userId String @unique // FK to User table verification KYCStatus @default(PENDING) riskScore Int @default(0) documents PartnerDoc[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model PartnerDoc { id String @id @default(cuid()) partnerId String partner PartnerProfile @relation(fields: [partnerId], references: [id]) type String // "PAN", "GST", "AADHAAR", "CANCELLED_CHEQUE", "BUSINESS_REG" name String url String status KYCDocStatus @default(PENDING) mandatory Boolean @default(true) adminNote String? reviewedBy String? reviewedAt DateTime? uploadedBy String uploadedAt DateTime @default(now()) @@index([partnerId, status]) }