generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") shadowDatabaseUrl = env("DATABASE_URL_SHADOW") } model User { id String @id @default(uuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt status String @default("active") email String @unique emailVerified Boolean @default(false) passwordHash String? name String? avatarUrl String? phone String? @unique defaultCurrency String? timeZone String? // OTP/MFA fields otpEmailEnabled Boolean @default(false) otpWhatsappEnabled Boolean @default(false) otpTotpEnabled Boolean @default(false) otpTotpSecret String? // Admin fields role String @default("user") // "user" | "admin" suspendedAt DateTime? suspendedReason String? lastLoginAt DateTime? // Relations authAccounts AuthAccount[] categories Category[] Recurrence Recurrence[] sessions Session[] transactions Transaction[] wallets Wallet[] subscriptions Subscription[] payments Payment[] apiKeys ApiKey[] webhooks Webhook[] } model AuthAccount { id String @id @default(uuid()) userId String provider String issuer String subject String lastLogin DateTime @default(now()) user User @relation(fields: [userId], references: [id]) @@unique([issuer, subject]) @@index([userId]) } model Session { id String @id @default(uuid()) userId String createdAt DateTime @default(now()) expiresAt DateTime ip String? userAgent String? revokedAt DateTime? user User @relation(fields: [userId], references: [id]) @@index([userId]) } model Wallet { id String @id @default(uuid()) userId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt kind String name String currency String? unit String? initialAmount Decimal? @db.Decimal(18, 2) pricePerUnit Decimal? @db.Decimal(18, 2) deletedAt DateTime? transactions Transaction[] user User @relation(fields: [userId], references: [id]) @@index([userId]) } model Category { id String @id @default(uuid()) userId String name String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) @@unique([userId, name]) @@index([userId]) } model Transaction { id String @id @default(uuid()) userId String walletId String createdAt DateTime @default(now()) date DateTime amount Decimal @db.Decimal(18, 2) direction String category String? memo String? recurrenceId String? user User @relation(fields: [userId], references: [id]) wallet Wallet @relation(fields: [walletId], references: [id]) @@index([userId, walletId, date]) } model Recurrence { id String @id @default(uuid()) userId String rule String nextRunAt DateTime lastRunAt DateTime? idempotency String @unique user User @relation(fields: [userId], references: [id]) @@index([userId]) } model CurrencyRate { id String @id @default(uuid()) base String quote String at DateTime rate Decimal @db.Decimal(18, 6) @@unique([base, quote, at]) @@index([base, quote]) } // ============================================ // SUBSCRIPTION & PAYMENT MODELS // ============================================ model Plan { id String @id @default(uuid()) name String slug String @unique description String? price Decimal @db.Decimal(10, 2) currency String @default("IDR") durationType String // "monthly" | "yearly" | "lifetime" | "custom" durationDays Int? trialDays Int @default(7) features Json badge String? badgeColor String? highlightColor String? sortOrder Int @default(0) isActive Boolean @default(true) isVisible Boolean @default(true) isFeatured Boolean @default(false) maxWallets Int? maxGoals Int? maxTeamMembers Int? apiEnabled Boolean @default(false) apiRateLimit Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt subscriptions Subscription[] @@index([slug]) @@index([isActive]) @@index([isVisible]) @@index([sortOrder]) } model Subscription { id String @id @default(uuid()) userId String @unique user User @relation(fields: [userId], references: [id]) planId String plan Plan @relation(fields: [planId], references: [id]) status String // "active" | "expired" | "cancelled" | "grace_period" startDate DateTime endDate DateTime isTrialUsed Boolean @default(false) trialEndDate DateTime? cancelledAt DateTime? cancellationReason String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt payments Payment[] @@index([userId]) @@index([status]) @@index([endDate]) } model Payment { id String @id @default(uuid()) userId String user User @relation(fields: [userId], references: [id]) subscriptionId String? subscription Subscription? @relation(fields: [subscriptionId], references: [id]) invoiceNumber String @unique amount Decimal @db.Decimal(10, 2) currency String @default("IDR") method String tripayReference String? @unique tripayFee Decimal? @db.Decimal(10, 2) totalAmount Decimal @db.Decimal(10, 2) paymentChannel String? paymentUrl String? qrUrl String? status String proofImageUrl String? transferDate DateTime? verifiedBy String? verifiedAt DateTime? rejectionReason String? couponId String? coupon Coupon? @relation(fields: [couponId], references: [id]) discountAmount Decimal? @db.Decimal(10, 2) notes String? expiresAt DateTime? paidAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([status]) @@index([invoiceNumber]) @@index([tripayReference]) } model PaymentMethod { id String @id @default(uuid()) type String provider String accountName String accountNumber String displayName String logoUrl String? instructions String? isActive Boolean @default(true) sortOrder Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([isActive]) @@index([sortOrder]) } model Coupon { id String @id @default(uuid()) code String @unique name String description String? discountType String discountValue Decimal @db.Decimal(10, 2) maxDiscount Decimal? @db.Decimal(10, 2) validFrom DateTime validUntil DateTime maxUses Int? usedCount Int @default(0) minPurchase Decimal? @db.Decimal(10, 2) applicablePlans String[] isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt payments Payment[] @@index([code]) @@index([isActive]) } model AppConfig { id String @id @default(uuid()) key String @unique value String category String label String description String? type String isSecret Boolean @default(false) updatedAt DateTime @updatedAt updatedBy String? @@index([category]) } // ============================================ // API & WEBHOOK MODELS // ============================================ model ApiKey { id String @id @default(uuid()) userId String user User @relation(fields: [userId], references: [id]) name String keyHash String @unique prefix String scopes String[] lastUsedAt DateTime? expiresAt DateTime? revokedAt DateTime? createdAt DateTime @default(now()) usage ApiKeyUsage[] @@index([userId]) @@index([keyHash]) } model ApiKeyUsage { id String @id @default(uuid()) apiKeyId String apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade) endpoint String method String statusCode Int responseTime Int timestamp DateTime @default(now()) @@index([apiKeyId, timestamp]) } model Webhook { id String @id @default(uuid()) userId String user User @relation(fields: [userId], references: [id]) url String events String[] secret String isActive Boolean @default(true) lastTriggeredAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deliveries WebhookDelivery[] @@index([userId]) } model WebhookDelivery { id String @id @default(uuid()) webhookId String webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade) event String payload Json status String statusCode Int? response String? attempts Int @default(0) nextRetryAt DateTime? deliveredAt DateTime? createdAt DateTime @default(now()) @@index([webhookId]) @@index([status]) }