- Added a new script command `dev:studio` to facilitate studio management in development. - Updated the Prisma dependency in BFF package to version 6.16.0 for improved features and bug fixes. - Refactored Dockerfiles for BFF and Portal to enhance health check mechanisms during startup. - Removed deprecated WhmcsApiMethodsService to streamline the integration services. - Cleaned up various components and services in the SIM management module for better maintainability and clarity.
269 lines
9.1 KiB
Plaintext
269 lines
9.1 KiB
Plaintext
// =============================================================================
|
||
// Prisma Schema - Customer Portal BFF
|
||
// =============================================================================
|
||
// IMPORTANT: When building Docker images, the Prisma client must be regenerated
|
||
// from the production directory structure. See prisma/README.md for details.
|
||
// =============================================================================
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
// Only include engines we actually need:
|
||
// - native: for local development
|
||
// - linux-musl-openssl-3.0.x: for Alpine production
|
||
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
model User {
|
||
id String @id @default(uuid())
|
||
email String @unique
|
||
passwordHash String? @map("password_hash")
|
||
role UserRole @default(USER)
|
||
|
||
// Authentication state only - profile data comes from WHMCS
|
||
mfaSecret String? @map("mfa_secret")
|
||
emailVerified Boolean @default(false) @map("email_verified")
|
||
failedLoginAttempts Int @default(0) @map("failed_login_attempts")
|
||
lockedUntil DateTime? @map("locked_until")
|
||
lastLoginAt DateTime? @map("last_login_at")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
auditLogs AuditLog[]
|
||
idMapping IdMapping?
|
||
idempotencyKeys IdempotencyKey[]
|
||
invoicesMirror InvoiceMirror[]
|
||
subscriptionsMirror SubscriptionMirror[]
|
||
|
||
@@map("users")
|
||
}
|
||
|
||
model IdMapping {
|
||
userId String @id @map("user_id")
|
||
whmcsClientId Int @unique @map("whmcs_client_id")
|
||
sfAccountId String? @map("sf_account_id")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@map("id_mappings")
|
||
}
|
||
|
||
model InvoiceMirror {
|
||
invoiceId Int @id @map("invoice_id")
|
||
userId String @map("user_id")
|
||
number String
|
||
status String
|
||
amountCents Int @map("amount_cents")
|
||
currency String @db.Char(3)
|
||
dueDate DateTime? @map("due_date") @db.Date
|
||
issuedAt DateTime? @map("issued_at")
|
||
paidAt DateTime? @map("paid_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([userId, status])
|
||
@@index([userId, dueDate])
|
||
@@map("invoices_mirror")
|
||
}
|
||
|
||
model SubscriptionMirror {
|
||
serviceId Int @id @map("service_id")
|
||
userId String @map("user_id")
|
||
productName String @map("product_name")
|
||
domain String?
|
||
cycle String
|
||
status String
|
||
nextDue DateTime? @map("next_due")
|
||
amountCents Int @map("amount_cents")
|
||
currency String @db.Char(3)
|
||
registeredAt DateTime @map("registered_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([userId, status])
|
||
@@index([userId, nextDue])
|
||
@@map("subscriptions_mirror")
|
||
}
|
||
|
||
model IdempotencyKey {
|
||
key String @id
|
||
userId String @map("user_id")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([userId])
|
||
@@index([createdAt])
|
||
@@map("idempotency_keys")
|
||
}
|
||
|
||
model Job {
|
||
id String @id @default(uuid())
|
||
name String
|
||
data Json
|
||
status JobStatus @default(PENDING)
|
||
attempts Int @default(0)
|
||
maxRetries Int @default(3) @map("max_retries")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
processedAt DateTime? @map("processed_at")
|
||
failedAt DateTime? @map("failed_at")
|
||
error String?
|
||
|
||
@@index([status, createdAt])
|
||
@@map("jobs")
|
||
}
|
||
|
||
model AuditLog {
|
||
id String @id @default(uuid())
|
||
userId String? @map("user_id")
|
||
action AuditAction
|
||
resource String?
|
||
details Json?
|
||
ipAddress String? @map("ip_address")
|
||
userAgent String? @map("user_agent")
|
||
success Boolean @default(true)
|
||
error String?
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
user User? @relation(fields: [userId], references: [id])
|
||
|
||
@@index([userId, action])
|
||
@@index([action, createdAt])
|
||
@@index([createdAt])
|
||
@@map("audit_logs")
|
||
}
|
||
|
||
enum UserRole {
|
||
USER
|
||
ADMIN
|
||
}
|
||
|
||
enum JobStatus {
|
||
PENDING
|
||
PROCESSING
|
||
COMPLETED
|
||
FAILED
|
||
RETRYING
|
||
}
|
||
|
||
enum AuditAction {
|
||
LOGIN_SUCCESS
|
||
LOGIN_FAILED
|
||
LOGOUT
|
||
SIGNUP
|
||
PASSWORD_RESET
|
||
PASSWORD_CHANGE
|
||
ACCOUNT_LOCKED
|
||
ACCOUNT_UNLOCKED
|
||
PROFILE_UPDATE
|
||
MFA_ENABLED
|
||
MFA_DISABLED
|
||
API_ACCESS
|
||
SYSTEM_MAINTENANCE
|
||
}
|
||
|
||
// Per-SIM daily usage snapshot used to build full-month charts
|
||
model SimUsageDaily {
|
||
id Int @id @default(autoincrement())
|
||
account String
|
||
date DateTime @db.Date
|
||
usageMb Float
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@unique([account, date])
|
||
@@index([account, date])
|
||
@@map("sim_usage_daily")
|
||
}
|
||
|
||
model SimVoiceOptions {
|
||
account String @id
|
||
voiceMailEnabled Boolean @default(false) @map("voice_mail_enabled")
|
||
callWaitingEnabled Boolean @default(false) @map("call_waiting_enabled")
|
||
internationalRoamingEnabled Boolean @default(false) @map("international_roaming_enabled")
|
||
networkType String @default("4G") @map("network_type")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("sim_voice_options")
|
||
}
|
||
|
||
// Call history from SFTP monthly imports (domestic calls)
|
||
model SimCallHistoryDomestic {
|
||
id String @id @default(uuid())
|
||
account String // Customer phone number (e.g., "08077052946")
|
||
callDate DateTime @db.Date @map("call_date") // Date the call was made
|
||
callTime String @map("call_time") // Start time of the call (HHMMSS)
|
||
calledTo String @map("called_to") // Phone number called
|
||
location String? // Location info
|
||
durationSec Int @map("duration_sec") // Duration in seconds (320 = 32.0 sec)
|
||
chargeYen Int @map("charge_yen") // Call charge in JPY
|
||
month String // YYYY-MM format for filtering
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@unique([account, callDate, callTime, calledTo])
|
||
@@index([account, month])
|
||
@@index([account, callDate])
|
||
@@map("sim_call_history_domestic")
|
||
}
|
||
|
||
// Call history from SFTP monthly imports (international calls)
|
||
model SimCallHistoryInternational {
|
||
id String @id @default(uuid())
|
||
account String // Customer phone number
|
||
callDate DateTime @db.Date @map("call_date") // Date the call was made
|
||
startTime String @map("start_time") // Start time of the call
|
||
stopTime String? @map("stop_time") // Stop time (if available)
|
||
country String? // Country/location for international calls
|
||
calledTo String @map("called_to") // Phone number called
|
||
durationSec Int @map("duration_sec") // Duration in seconds
|
||
chargeYen Int @map("charge_yen") // Call charge in JPY
|
||
month String // YYYY-MM format for filtering
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@unique([account, callDate, startTime, calledTo])
|
||
@@index([account, month])
|
||
@@index([account, callDate])
|
||
@@map("sim_call_history_international")
|
||
}
|
||
|
||
// SMS history from SFTP monthly imports
|
||
model SimSmsHistory {
|
||
id String @id @default(uuid())
|
||
account String // Customer phone number
|
||
smsDate DateTime @db.Date @map("sms_date") // Date the SMS was sent
|
||
smsTime String @map("sms_time") // Time the SMS was sent
|
||
sentTo String @map("sent_to") // Phone number SMS was sent to
|
||
smsType SmsType @default(DOMESTIC) @map("sms_type") // SMS or 国際SMS
|
||
month String // YYYY-MM format for filtering
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@unique([account, smsDate, smsTime, sentTo])
|
||
@@index([account, month])
|
||
@@index([account, smsDate])
|
||
@@map("sim_sms_history")
|
||
}
|
||
|
||
enum SmsType {
|
||
DOMESTIC // SMS
|
||
INTERNATIONAL // 国際SMS
|
||
}
|
||
|
||
// Track which months have been imported
|
||
model SimHistoryImport {
|
||
id String @id @default(uuid())
|
||
month String @unique // YYYY-MM format
|
||
talkFile String? @map("talk_file") // Filename imported
|
||
smsFile String? @map("sms_file") // Filename imported
|
||
talkRecords Int @default(0) @map("talk_records") // Records imported
|
||
smsRecords Int @default(0) @map("sms_records") // Records imported
|
||
importedAt DateTime @default(now()) @map("imported_at")
|
||
status String @default("completed") // completed, failed, partial
|
||
|
||
@@map("sim_history_imports")
|
||
}
|