- Add Plan, Subscription, Payment, PaymentMethod, Coupon models - Add ApiKey, Webhook models for API access - Add AppConfig model for dynamic configuration - Add role, suspendedAt fields to User model - Create comprehensive seeder with: - Admin user (dwindi.ramadhana@gmail.com) - Default plans (Free, Pro Monthly, Pro Yearly) - Payment methods (BCA, Mandiri, GoPay) - App config (maintenance mode) - Zero data loss migration strategy
363 lines
10 KiB
Plaintext
363 lines
10 KiB
Plaintext
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])
|
|
}
|