Files
tabungin/apps/api/prisma/schema.prisma
dwindown c3bc181063 feat: add admin dashboard schema and seeder
- 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
2025-10-11 14:06:55 +07:00

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])
}