Stage 5: Add Prisma integration and enhance mission management

- Introduced Prisma as a dependency in package.json and updated pnpm-lock.yaml.
- Created Prisma module and service for database interactions.
- Added initial Prisma schema and migration for user, survivor, mission, and related entities.
- Implemented throttling in the API using @nestjs/throttler for rate limiting.
- Enhanced mission management logic to utilize Prisma for database transactions.
- Updated missions controller and service to handle mission state and participant management.
- Added Twitch PubSub service for real-time updates on mission states.
This commit is contained in:
Maurycy
2026-05-07 15:42:52 +00:00
parent e8523d270e
commit 21f1a5319f
22 changed files with 1676 additions and 96 deletions

View File

@@ -0,0 +1,91 @@
-- CreateTable
CREATE TABLE "users" (
"id" UUID NOT NULL,
"twitch_opaque_user_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "survivors" (
"id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"channel_id" TEXT NOT NULL,
"name" VARCHAR(32) NOT NULL,
"state" TEXT NOT NULL DEFAULT 'active',
"stats" JSONB NOT NULL,
"perk_slots" JSONB NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "survivors_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "missions" (
"id" UUID NOT NULL,
"group_id" UUID,
"channel_id" TEXT NOT NULL,
"difficulty" SMALLINT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'active',
"encounter_library_version" TEXT NOT NULL,
"started_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"ended_at" TIMESTAMP(3),
"tick_index" INTEGER NOT NULL DEFAULT 0,
"next_tick_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "missions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "mission_participants" (
"id" UUID NOT NULL,
"mission_id" UUID NOT NULL,
"survivor_id" UUID NOT NULL,
"state" TEXT NOT NULL DEFAULT 'active',
"hook_count" SMALLINT NOT NULL DEFAULT 0,
CONSTRAINT "mission_participants_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "mission_logs" (
"id" UUID NOT NULL,
"mission_id" UUID NOT NULL,
"tick_index" INTEGER NOT NULL,
"encounter_key" TEXT NOT NULL,
"rendered_text" TEXT NOT NULL,
"seed" TEXT NOT NULL,
"modifiers_applied" JSONB NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "mission_logs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "users_twitch_opaque_user_id_key" ON "users"("twitch_opaque_user_id");
-- CreateIndex
CREATE INDEX "missions_channel_id_idx" ON "missions"("channel_id");
-- CreateIndex
CREATE INDEX "missions_status_next_tick_at_idx" ON "missions"("status", "next_tick_at");
-- CreateIndex
CREATE UNIQUE INDEX "mission_participants_mission_id_survivor_id_key" ON "mission_participants"("mission_id", "survivor_id");
-- CreateIndex
CREATE INDEX "mission_logs_mission_id_tick_index_idx" ON "mission_logs"("mission_id", "tick_index");
-- AddForeignKey
ALTER TABLE "survivors" ADD CONSTRAINT "survivors_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "mission_participants" ADD CONSTRAINT "mission_participants_mission_id_fkey" FOREIGN KEY ("mission_id") REFERENCES "missions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "mission_participants" ADD CONSTRAINT "mission_participants_survivor_id_fkey" FOREIGN KEY ("survivor_id") REFERENCES "survivors"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "mission_logs" ADD CONSTRAINT "mission_logs_mission_id_fkey" FOREIGN KEY ("mission_id") REFERENCES "missions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,80 @@
generator client {
provider = "prisma-client-js"
output = "../../../node_modules/.prisma/client"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid()) @db.Uuid
opaqueUserId String @unique @map("twitch_opaque_user_id")
createdAt DateTime @default(now()) @map("created_at")
survivors Survivor[]
@@map("users")
}
model Survivor {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
user User @relation(fields: [userId], references: [id])
channelId String @map("channel_id")
name String @db.VarChar(32)
state String @default("active")
stats Json
perkSlots Json @map("perk_slots")
createdAt DateTime @default(now()) @map("created_at")
participants MissionParticipant[]
@@map("survivors")
}
model Mission {
id String @id @default(uuid()) @db.Uuid
groupId String? @map("group_id") @db.Uuid
channelId String @map("channel_id")
difficulty Int @db.SmallInt
status String @default("active")
encounterLibraryVersion String @map("encounter_library_version")
startedAt DateTime @default(now()) @map("started_at")
endedAt DateTime? @map("ended_at")
tickIndex Int @default(0) @map("tick_index")
nextTickAt DateTime @map("next_tick_at")
participants MissionParticipant[]
logs MissionLog[]
@@index([channelId])
@@index([status, nextTickAt])
@@map("missions")
}
model MissionParticipant {
id String @id @default(uuid()) @db.Uuid
missionId String @map("mission_id") @db.Uuid
mission Mission @relation(fields: [missionId], references: [id])
survivorId String @map("survivor_id") @db.Uuid
survivor Survivor @relation(fields: [survivorId], references: [id])
state String @default("active")
hookCount Int @default(0) @map("hook_count") @db.SmallInt
@@unique([missionId, survivorId])
@@map("mission_participants")
}
model MissionLog {
id String @id @default(uuid()) @db.Uuid
missionId String @map("mission_id") @db.Uuid
mission Mission @relation(fields: [missionId], references: [id])
tickIndex Int @map("tick_index")
encounterKey String @map("encounter_key")
renderedText String @map("rendered_text")
seed String
modifiersApplied Json @map("modifiers_applied")
createdAt DateTime @default(now()) @map("created_at")
@@index([missionId, tickIndex])
@@map("mission_logs")
}