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