init
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "User"
|
||||
ADD COLUMN "sessionVersion" INTEGER NOT NULL DEFAULT 0;
|
||||
210
backend/prisma/schema.prisma
Normal file
210
backend/prisma/schema.prisma
Normal 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
79
backend/prisma/seed.ts
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user