Files

414 lines
12 KiB
Plaintext

generator client {
provider = "prisma-client-js"
// native = local Windows/macOS; debian-openssl-3.0.x = node:20-bookworm (Arvan)
binaryTargets = ["native", "debian-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ==================== ENUMS ====================
enum AdminStatus {
ACTIVE
DISABLED
}
enum AdminRole {
SUPER_ADMIN
OPERATOR
SUPPORT
}
enum AccessScopeType {
ALL
CUSTOM_CAMPAIGNS
}
enum UserStatus {
ACTIVE
BLOCKED
}
enum CampaignStatus {
DRAFT
ACTIVE
PAUSED
FINISHED
}
enum PrizeType {
PHYSICAL
DISCOUNT_CODE
CREDIT
CASH
NO_PRIZE
OTHER
}
enum StockType {
LIMITED
UNLIMITED
}
enum PrizeStatus {
ACTIVE
INACTIVE
}
enum SpinStatus {
SUCCESS
FAILED
DUPLICATE
}
enum ChanceTxnType {
SIGNUP_BONUS
ADMIN_ADJUSTMENT
SPIN_CONSUME
CAMPAIGN_BONUS
REFUND
}
enum UserPrizeStatus {
NEW
CONTACTED
DELIVERED
ACTIVE_CODE
USED
EXPIRED
}
enum SmsType {
OTP
WINNING
NO_PRIZE
PRIZE_REMINDER
}
enum SmsStatus {
PENDING
SENT
FAILED
}
enum WheelDesign {
CLASSIC_CASINO
NEON_CYBER
MINIMAL_FLAT
LUXURY_GOLD
PLAYFUL_CARTOON
RUSTIC_WOOD
GALAXY_SPACE
CANDY_LAND
FIRE_FLAME
CRYSTAL_ICE
}
// ==================== MODELS ====================
model Admin {
id String @id @default(uuid())
name String
mobile String @unique
email String @unique
passwordHash String @map("password_hash")
role AdminRole @default(OPERATOR)
status AdminStatus @default(ACTIVE)
isSuperAdmin Boolean @default(false) @map("is_super_admin")
lastLoginAt DateTime? @map("last_login_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
auditLogs AdminLog[]
chanceTxns ChanceTransaction[] @relation("AdminCreatedTxn")
roles AdminRoleAssignment[]
workspaceAssignments AdminWorkspaceAssignment[]
@@map("admins")
}
model User {
id String @id @default(uuid())
mobile String @unique
name String?
status UserStatus @default(ACTIVE)
lastLoginAt DateTime? @map("last_login_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
userCampaigns UserCampaign[]
chanceTransactions ChanceTransaction[]
spins Spin[]
userPrizes UserPrize[]
otpCodes OtpCode[]
smsLogs SmsLog[]
@@index([mobile])
@@map("users")
}
model OtpCode {
id String @id @default(uuid())
mobile String
code String
userId String? @map("user_id")
user User? @relation(fields: [userId], references: [id])
attempts Int @default(0)
consumed Boolean @default(false)
expiresAt DateTime @map("expires_at")
createdAt DateTime @default(now()) @map("created_at")
@@index([mobile, consumed])
@@map("otp_codes")
}
model Campaign {
id String @id @default(uuid())
title String
slug String @unique
description String?
image String?
banner String?
logo String?
startAt DateTime @map("start_at")
endAt DateTime @map("end_at")
status CampaignStatus @default(DRAFT)
defaultChances Int @default(0) @map("default_chances")
rules String?
ctaText String? @map("cta_text")
wheelDesign WheelDesign @default(CLASSIC_CASINO) @map("wheel_design")
settings Json? // primaryColor, secondaryColor, headerText, buttonText, headerImage, celebrationEffect...
workspaceId String? @map("workspace_id")
workspace Workspace? @relation(fields: [workspaceId], references: [id])
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
prizes Prize[]
userCampaigns UserCampaign[]
chanceTxns ChanceTransaction[]
spins Spin[]
userPrizes UserPrize[]
@@index([status, startAt, endAt])
@@index([workspaceId])
@@map("campaigns")
}
model Prize {
id String @id @default(uuid())
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
title String
description String?
image String?
type PrizeType
stockType StockType @map("stock_type")
initialStock Int? @map("initial_stock")
remainingStock Int? @map("remaining_stock")
weight Int
status PrizeStatus @default(ACTIVE)
sortOrder Int @default(0) @map("sort_order")
segmentColor String @map("segment_color")
expiresAt DateTime? @map("expires_at")
smsTemplate String? @map("sms_template")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
spins Spin[]
userPrizes UserPrize[]
@@index([campaignId, status])
@@map("prizes")
}
model UserCampaign {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id])
totalChances Int @default(0) @map("total_chances")
usedChances Int @default(0) @map("used_chances")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([userId, campaignId])
@@map("user_campaigns")
}
model ChanceTransaction {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id])
amount Int // مثبت یا منفی
type ChanceTxnType
referenceType String? @map("reference_type") // e.g. "spin", "manual"
referenceId String? @map("reference_id")
description String?
createdById String? @map("created_by") // admin id در صورت تغییر دستی
createdBy Admin? @relation("AdminCreatedTxn", fields: [createdById], references: [id])
createdAt DateTime @default(now()) @map("created_at")
@@index([userId, campaignId])
@@map("chance_transactions")
}
model Spin {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id])
prizeId String? @map("prize_id")
prize Prize? @relation(fields: [prizeId], references: [id])
status SpinStatus @default(SUCCESS)
idempotencyKey String @unique @map("idempotency_key")
ip String?
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
userPrize UserPrize?
@@index([userId, campaignId])
@@index([campaignId, createdAt])
@@map("spins")
}
model UserPrize {
id String @id @default(uuid())
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
campaignId String @map("campaign_id")
campaign Campaign @relation(fields: [campaignId], references: [id])
prizeId String @map("prize_id")
prize Prize @relation(fields: [prizeId], references: [id])
spinId String @unique @map("spin_id")
spin Spin @relation(fields: [spinId], references: [id])
code String? @unique
status UserPrizeStatus @default(NEW)
wonAt DateTime @default(now()) @map("won_at")
redeemedAt DateTime? @map("redeemed_at")
@@index([userId])
@@index([status])
@@map("user_prizes")
}
model SmsLog {
id String @id @default(uuid())
userId String? @map("user_id")
user User? @relation(fields: [userId], references: [id])
mobile String
type SmsType
message String
provider String @default("raygansms")
status SmsStatus @default(PENDING)
providerResponse Json? @map("provider_response")
createdAt DateTime @default(now()) @map("created_at")
@@index([mobile, type])
@@map("sms_logs")
}
model AdminLog {
id String @id @default(uuid())
adminId String @map("admin_id")
admin Admin @relation(fields: [adminId], references: [id])
action String // e.g. "prize.update", "chance.add"
entityType String @map("entity_type")
entityId String @map("entity_id")
oldValue Json? @map("old_value")
newValue Json? @map("new_value")
ip String?
createdAt DateTime @default(now()) @map("created_at")
@@index([entityType, entityId])
@@map("admin_logs")
}
// ==================== WORKSPACE & RBAC MODELS ====================
model Workspace {
id String @id @default(uuid())
name String
slug String @unique
description String?
status String @default("ACTIVE")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
campaigns Campaign[]
adminAssignments AdminWorkspaceAssignment[]
@@map("workspaces")
}
model Role {
id String @id @default(uuid())
code String @unique // e.g. "SUPER_ADMIN", "OPERATOR", "SUPPORT"
name String // نام فارسی
description String?
isSystem Boolean @default(false) @map("is_system")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
permissions RolePermission[]
admins AdminRoleAssignment[]
@@map("roles")
}
model Permission {
id String @id @default(uuid())
code String @unique // e.g. "campaigns:read", "campaigns:create"
module String // e.g. "campaigns", "users", "prizes", "reports", "admins"
name String // نام فارسی
description String?
roles RolePermission[]
@@index([module])
@@map("permissions")
}
model RolePermission {
roleId String @map("role_id")
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
permissionId String @map("permission_id")
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
@@id([roleId, permissionId])
@@map("role_permissions")
}
model AdminRoleAssignment {
id String @id @default(uuid())
adminId String @map("admin_id")
admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
roleId String @map("role_id")
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
assignedAt DateTime @default(now()) @map("assigned_at")
@@unique([adminId, roleId])
@@map("admin_role_assignments")
}
model AdminWorkspaceAssignment {
id String @id @default(uuid())
adminId String @map("admin_id")
admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
workspaceId String @map("workspace_id")
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
scopeType AccessScopeType @default(ALL) @map("scope_type")
specificCampaignIds Json? @map("specific_campaign_ids")
createdAt DateTime @default(now()) @map("created_at")
@@unique([adminId, workspaceId])
@@map("admin_workspace_assignments")
}