first commit
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AdminStatus" AS ENUM ('ACTIVE', 'DISABLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AdminRole" AS ENUM ('SUPER_ADMIN', 'OPERATOR', 'SUPPORT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "UserStatus" AS ENUM ('ACTIVE', 'BLOCKED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CampaignStatus" AS ENUM ('DRAFT', 'ACTIVE', 'PAUSED', 'FINISHED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PrizeType" AS ENUM ('PHYSICAL', 'DISCOUNT_CODE', 'CREDIT', 'CASH', 'NO_PRIZE', 'OTHER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StockType" AS ENUM ('LIMITED', 'UNLIMITED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PrizeStatus" AS ENUM ('ACTIVE', 'INACTIVE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SpinStatus" AS ENUM ('SUCCESS', 'FAILED', 'DUPLICATE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ChanceTxnType" AS ENUM ('SIGNUP_BONUS', 'ADMIN_ADJUSTMENT', 'SPIN_CONSUME', 'CAMPAIGN_BONUS', 'REFUND');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "UserPrizeStatus" AS ENUM ('NEW', 'CONTACTED', 'DELIVERED', 'ACTIVE_CODE', 'USED', 'EXPIRED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SmsType" AS ENUM ('OTP', 'WINNING', 'NO_PRIZE', 'PRIZE_REMINDER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SmsStatus" AS ENUM ('PENDING', 'SENT', 'FAILED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WheelDesign" AS ENUM ('CLASSIC_CASINO', 'NEON_CYBER', 'MINIMAL_FLAT', 'LUXURY_GOLD', 'PLAYFUL_CARTOON');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "admins" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"mobile" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password_hash" TEXT NOT NULL,
|
||||
"role" "AdminRole" NOT NULL DEFAULT 'OPERATOR',
|
||||
"status" "AdminStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "admins_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"mobile" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"status" "UserStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"last_login_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "otp_codes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"mobile" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"user_id" TEXT,
|
||||
"attempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"consumed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "otp_codes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "campaigns" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"image" TEXT,
|
||||
"banner" TEXT,
|
||||
"logo" TEXT,
|
||||
"start_at" TIMESTAMP(3) NOT NULL,
|
||||
"end_at" TIMESTAMP(3) NOT NULL,
|
||||
"status" "CampaignStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"default_chances" INTEGER NOT NULL DEFAULT 0,
|
||||
"rules" TEXT,
|
||||
"cta_text" TEXT,
|
||||
"wheel_design" "WheelDesign" NOT NULL DEFAULT 'CLASSIC_CASINO',
|
||||
"settings" JSONB,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "campaigns_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "prizes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"campaign_id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"image" TEXT,
|
||||
"type" "PrizeType" NOT NULL,
|
||||
"stock_type" "StockType" NOT NULL,
|
||||
"initial_stock" INTEGER,
|
||||
"remaining_stock" INTEGER,
|
||||
"weight" INTEGER NOT NULL,
|
||||
"status" "PrizeStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"sort_order" INTEGER NOT NULL DEFAULT 0,
|
||||
"segment_color" TEXT NOT NULL,
|
||||
"expires_at" TIMESTAMP(3),
|
||||
"sms_template" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "prizes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_campaigns" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"campaign_id" TEXT NOT NULL,
|
||||
"total_chances" INTEGER NOT NULL DEFAULT 0,
|
||||
"used_chances" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "user_campaigns_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "chance_transactions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"campaign_id" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"type" "ChanceTxnType" NOT NULL,
|
||||
"reference_type" TEXT,
|
||||
"reference_id" TEXT,
|
||||
"description" TEXT,
|
||||
"created_by" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "chance_transactions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "spins" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"campaign_id" TEXT NOT NULL,
|
||||
"prize_id" TEXT,
|
||||
"status" "SpinStatus" NOT NULL DEFAULT 'SUCCESS',
|
||||
"idempotency_key" TEXT NOT NULL,
|
||||
"ip" TEXT,
|
||||
"user_agent" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "spins_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_prizes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"campaign_id" TEXT NOT NULL,
|
||||
"prize_id" TEXT NOT NULL,
|
||||
"spin_id" TEXT NOT NULL,
|
||||
"code" TEXT,
|
||||
"status" "UserPrizeStatus" NOT NULL DEFAULT 'NEW',
|
||||
"won_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"redeemed_at" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "user_prizes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sms_logs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT,
|
||||
"mobile" TEXT NOT NULL,
|
||||
"type" "SmsType" NOT NULL,
|
||||
"message" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL DEFAULT 'raygansms',
|
||||
"status" "SmsStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"provider_response" JSONB,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "sms_logs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "admin_logs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"admin_id" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"entity_type" TEXT NOT NULL,
|
||||
"entity_id" TEXT NOT NULL,
|
||||
"old_value" JSONB,
|
||||
"new_value" JSONB,
|
||||
"ip" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "admin_logs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "admins_mobile_key" ON "admins"("mobile");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "admins_email_key" ON "admins"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_mobile_key" ON "users"("mobile");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "users_mobile_idx" ON "users"("mobile");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "otp_codes_mobile_consumed_idx" ON "otp_codes"("mobile", "consumed");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "campaigns_slug_key" ON "campaigns"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "campaigns_status_start_at_end_at_idx" ON "campaigns"("status", "start_at", "end_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "prizes_campaign_id_status_idx" ON "prizes"("campaign_id", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_campaigns_user_id_campaign_id_key" ON "user_campaigns"("user_id", "campaign_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "chance_transactions_user_id_campaign_id_idx" ON "chance_transactions"("user_id", "campaign_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "spins_idempotency_key_key" ON "spins"("idempotency_key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "spins_user_id_campaign_id_idx" ON "spins"("user_id", "campaign_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "spins_campaign_id_created_at_idx" ON "spins"("campaign_id", "created_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_prizes_spin_id_key" ON "user_prizes"("spin_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_prizes_code_key" ON "user_prizes"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_prizes_user_id_idx" ON "user_prizes"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_prizes_status_idx" ON "user_prizes"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "sms_logs_mobile_type_idx" ON "sms_logs"("mobile", "type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "admin_logs_entity_type_entity_id_idx" ON "admin_logs"("entity_type", "entity_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "otp_codes" ADD CONSTRAINT "otp_codes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "prizes" ADD CONSTRAINT "prizes_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_campaigns" ADD CONSTRAINT "user_campaigns_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_campaigns" ADD CONSTRAINT "user_campaigns_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "chance_transactions" ADD CONSTRAINT "chance_transactions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "chance_transactions" ADD CONSTRAINT "chance_transactions_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "chance_transactions" ADD CONSTRAINT "chance_transactions_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "admins"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "spins" ADD CONSTRAINT "spins_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "spins" ADD CONSTRAINT "spins_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "spins" ADD CONSTRAINT "spins_prize_id_fkey" FOREIGN KEY ("prize_id") REFERENCES "prizes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_prizes" ADD CONSTRAINT "user_prizes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_prizes" ADD CONSTRAINT "user_prizes_campaign_id_fkey" FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_prizes" ADD CONSTRAINT "user_prizes_prize_id_fkey" FOREIGN KEY ("prize_id") REFERENCES "prizes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_prizes" ADD CONSTRAINT "user_prizes_spin_id_fkey" FOREIGN KEY ("spin_id") REFERENCES "spins"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "sms_logs" ADD CONSTRAINT "sms_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "admin_logs" ADD CONSTRAINT "admin_logs_admin_id_fkey" FOREIGN KEY ("admin_id") REFERENCES "admins"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
@@ -0,0 +1,313 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ==================== ENUMS ====================
|
||||
|
||||
enum AdminStatus {
|
||||
ACTIVE
|
||||
DISABLED
|
||||
}
|
||||
|
||||
enum AdminRole {
|
||||
SUPER_ADMIN
|
||||
OPERATOR
|
||||
SUPPORT
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ==================== 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)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
auditLogs AdminLog[]
|
||||
chanceTxns ChanceTransaction[] @relation("AdminCreatedTxn")
|
||||
|
||||
@@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...
|
||||
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])
|
||||
@@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")
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const user = await prisma.user.upsert({
|
||||
where: { mobile: '09382507041' },
|
||||
create: {
|
||||
mobile: '09382507041',
|
||||
name: 'کاربر ویژه',
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
update: {
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
});
|
||||
|
||||
const campaign = await prisma.campaign.findUnique({
|
||||
where: { slug: 'default-campaign' },
|
||||
});
|
||||
|
||||
if (campaign) {
|
||||
await prisma.userCampaign.upsert({
|
||||
where: {
|
||||
userId_campaignId: {
|
||||
userId: user.id,
|
||||
campaignId: campaign.id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
userId: user.id,
|
||||
campaignId: campaign.id,
|
||||
totalChances: 10,
|
||||
usedChances: 0,
|
||||
},
|
||||
update: {
|
||||
totalChances: { increment: 10 },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('SUCCESS: User 09382507041 is authorized and ready with 10 chances!');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('Seeding default demo campaign and prizes...');
|
||||
|
||||
const existingCampaign = await prisma.campaign.findUnique({
|
||||
where: { slug: 'default-campaign' },
|
||||
});
|
||||
|
||||
if (existingCampaign) {
|
||||
console.log('Default campaign already exists.');
|
||||
return;
|
||||
}
|
||||
|
||||
const campaign = await prisma.campaign.create({
|
||||
data: {
|
||||
title: 'گردونه شانس جشنواره بزرگ نوروزی',
|
||||
slug: 'default-campaign',
|
||||
description: 'هر روز با چرخاندن گردونه، شانس برنده شدن آیفون، سکه طلا، و کدهای تخفیف شگفتانگیز را داشته باشید!',
|
||||
startAt: new Date(Date.now() - 24 * 3600 * 1000),
|
||||
endAt: new Date(Date.now() + 60 * 24 * 3600 * 1000),
|
||||
status: 'ACTIVE',
|
||||
defaultChances: 3,
|
||||
ctaText: 'بچرخون و برنده شو!',
|
||||
wheelDesign: 'CLASSIC_CASINO',
|
||||
rules: '۱. هر کاربر در بدو ورود ۳ شانس چرخاندن رایگان دریافت میکند.\n۲. جوایز فیزیکی پس از هماهنگی تلفنی توسط پشتیبانی ارسال خواهد شد.\n۳. کدهای تخفیف بلافاصله پیامک شده و تا ۷ روز پس از دریافت معتبر هستند.',
|
||||
prizes: {
|
||||
create: [
|
||||
{
|
||||
title: 'آیفون ۱۵ پرو',
|
||||
type: 'PHYSICAL',
|
||||
stockType: 'LIMITED',
|
||||
initialStock: 3,
|
||||
remainingStock: 3,
|
||||
weight: 1,
|
||||
sortOrder: 1,
|
||||
segmentColor: '#b91c1c',
|
||||
},
|
||||
{
|
||||
title: 'تخفیف ۵۰٪ دیجیکالا',
|
||||
type: 'DISCOUNT_CODE',
|
||||
stockType: 'UNLIMITED',
|
||||
weight: 25,
|
||||
sortOrder: 2,
|
||||
segmentColor: '#1e293b',
|
||||
},
|
||||
{
|
||||
title: 'کارت هدیه ۵۰۰ تومانی',
|
||||
type: 'CREDIT',
|
||||
stockType: 'LIMITED',
|
||||
initialStock: 50,
|
||||
remainingStock: 50,
|
||||
weight: 10,
|
||||
sortOrder: 3,
|
||||
segmentColor: '#b91c1c',
|
||||
},
|
||||
{
|
||||
title: 'پوچ (دفعه بعد)',
|
||||
type: 'NO_PRIZE',
|
||||
stockType: 'UNLIMITED',
|
||||
weight: 20,
|
||||
sortOrder: 4,
|
||||
segmentColor: '#1e293b',
|
||||
},
|
||||
{
|
||||
title: 'تخفیف ۳۰٪ اسنپ',
|
||||
type: 'DISCOUNT_CODE',
|
||||
stockType: 'UNLIMITED',
|
||||
weight: 20,
|
||||
sortOrder: 5,
|
||||
segmentColor: '#b91c1c',
|
||||
},
|
||||
{
|
||||
title: 'ساعت هوشمند',
|
||||
type: 'PHYSICAL',
|
||||
stockType: 'LIMITED',
|
||||
initialStock: 10,
|
||||
remainingStock: 10,
|
||||
weight: 4,
|
||||
sortOrder: 6,
|
||||
segmentColor: '#1e293b',
|
||||
},
|
||||
{
|
||||
title: 'شانس مجدد',
|
||||
type: 'OTHER',
|
||||
stockType: 'UNLIMITED',
|
||||
weight: 15,
|
||||
sortOrder: 7,
|
||||
segmentColor: '#b91c1c',
|
||||
},
|
||||
{
|
||||
title: 'پوچ (بدون جایزه)',
|
||||
type: 'NO_PRIZE',
|
||||
stockType: 'UNLIMITED',
|
||||
weight: 5,
|
||||
sortOrder: 8,
|
||||
segmentColor: '#1e293b',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Campaign created with ID: ${campaign.id} and 8 prizes.`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user