This commit is contained in:
talorr
2026-03-27 03:36:08 +03:00
parent 8a97ce6d54
commit cda36918e8
225 changed files with 35641 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
-- CreateEnum
CREATE TYPE "SubscriptionStatus" AS ENUM ('active', 'expired', 'canceled');
-- AlterTable
ALTER TABLE "UserBotAccess"
ADD COLUMN "status" "SubscriptionStatus" NOT NULL DEFAULT 'active',
ADD COLUMN "startsAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "expiresAt" TIMESTAMP(3),
ADD COLUMN "notes" TEXT;
-- Backfill
UPDATE "UserBotAccess"
SET "startsAt" = COALESCE("grantedAt", CURRENT_TIMESTAMP)
WHERE "startsAt" IS NULL;

View File

@@ -0,0 +1,18 @@
CREATE TABLE "PasswordResetToken" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"tokenHash" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"usedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "PasswordResetToken_tokenHash_key" ON "PasswordResetToken"("tokenHash");
CREATE INDEX "PasswordResetToken_userId_idx" ON "PasswordResetToken"("userId");
CREATE INDEX "PasswordResetToken_expiresAt_idx" ON "PasswordResetToken"("expiresAt");
ALTER TABLE "PasswordResetToken"
ADD CONSTRAINT "PasswordResetToken_userId_fkey"
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "User"
ADD COLUMN "sessionVersion" INTEGER NOT NULL DEFAULT 0;

View File

@@ -0,0 +1,210 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum UserRole {
admin
user
}
enum SignalStatus {
pending
win
lose
void
manual_review
unpublished
}
enum SourceType {
manual
provider
}
enum SubscriptionStatus {
active
expired
canceled
}
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
role UserRole @default(user)
active Boolean @default(true)
sessionVersion Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
notificationSetting NotificationSetting?
pushSubscriptions PushSubscription[]
nativePushSubscriptions NativePushSubscription[]
botAccesses UserBotAccess[]
passwordResetTokens PasswordResetToken[]
adminActions AdminActionLog[]
}
model PasswordResetToken {
id String @id @default(cuid())
userId String
tokenHash String @unique
expiresAt DateTime
usedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([expiresAt])
}
model Bot {
id String @id @default(cuid())
key String @unique
name String
sourceUrl String
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userAccesses UserBotAccess[]
}
model UserBotAccess {
id String @id @default(cuid())
userId String
botId String
status SubscriptionStatus @default(active)
startsAt DateTime @default(now())
expiresAt DateTime?
notes String?
grantedAt DateTime @default(now())
grantedById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
@@unique([userId, botId])
}
model Signal {
id String @id @default(cuid())
providerId String?
eventId String
sportType String
leagueName String
homeTeam String
awayTeam String
eventStartTime DateTime
marketType String
selection String
forecast String?
lineValue Float?
odds Float
signalTime DateTime
status SignalStatus @default(pending)
sourceType SourceType
comment String?
published Boolean @default(true)
dedupeKey String @unique
rawPayload Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
settlement Settlement?
notifications NotificationLog[]
}
model EventResult {
id String @id @default(cuid())
eventId String @unique
homeScore Int?
awayScore Int?
status String
payload Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Settlement {
id String @id @default(cuid())
signalId String @unique
result SignalStatus
explanation String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
signal Signal @relation(fields: [signalId], references: [id], onDelete: Cascade)
}
model PushSubscription {
id String @id @default(cuid())
userId String
endpoint String
p256dh String
auth String
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, endpoint])
}
model NativePushSubscription {
id String @id @default(cuid())
userId String
token String
platform String
deviceId String?
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, token])
}
model NotificationSetting {
id String @id @default(cuid())
userId String @unique
signalsPushEnabled Boolean @default(true)
resultsPushEnabled Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model NotificationLog {
id String @id @default(cuid())
signalId String?
type String
recipients Int
successCount Int
failedCount Int
payload Json?
createdAt DateTime @default(now())
signal Signal? @relation(fields: [signalId], references: [id], onDelete: SetNull)
}
model AdminActionLog {
id String @id @default(cuid())
adminId String
action String
entityType String
entityId String
metadata Json?
createdAt DateTime @default(now())
admin User @relation(fields: [adminId], references: [id], onDelete: Cascade)
}
model IntegrationLog {
id String @id @default(cuid())
provider String
level String
message String
payload Json?
createdAt DateTime @default(now())
}

79
backend/prisma/seed.ts Normal file
View File

@@ -0,0 +1,79 @@
import bcrypt from "bcryptjs";
import { PrismaClient, UserRole } from "@prisma/client";
const prisma = new PrismaClient();
const defaultBots = [
{
key: "raketafon",
name: "Raketafon",
sourceUrl: "https://alpinbet.com/dispatch/antigol/raketafon"
},
{
key: "pobeda-1-comand",
name: "Pobeda 1 Comand",
sourceUrl: "https://alpinbet.com/dispatch/antigol/pobeda-1-comand"
},
{
key: "raketabas",
name: "Raketabas",
sourceUrl: "https://alpinbet.com/dispatch/antigol/raketabas"
},
{
key: "sol-1www",
name: "Sol 1www",
sourceUrl: "https://alpinbet.com/dispatch/antigol/sol-1www"
},
{
key: "fon-stb",
name: "Fon Stb",
sourceUrl: "https://alpinbet.com/dispatch/antigol/fon-stb"
},
{
key: "fonat",
name: "Fonat",
sourceUrl: "https://alpinbet.com/dispatch/antigol/fonat"
}
];
async function main() {
const adminPasswordHash = await bcrypt.hash("admin12345", 10);
await prisma.user.upsert({
where: { email: "admin@example.com" },
update: {},
create: {
email: "admin@example.com",
passwordHash: adminPasswordHash,
role: UserRole.admin,
notificationSetting: {
create: {
signalsPushEnabled: true,
resultsPushEnabled: true
}
}
}
});
for (const bot of defaultBots) {
await prisma.bot.upsert({
where: { key: bot.key },
update: {
name: bot.name,
sourceUrl: bot.sourceUrl,
active: true
},
create: bot
});
}
}
main()
.finally(async () => {
await prisma.$disconnect();
})
.catch(async (error) => {
console.error(error);
await prisma.$disconnect();
process.exit(1);
});