From 8f1f2bf19621d3603abf1c2d745d7156ece87afe Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Sun, 29 Mar 2026 19:25:02 -0700 Subject: [PATCH] feat(db): add tournament_admin role and ownership fields to schema --- .../20260328045020_init/migration.sql | 232 +++++++++++ .../migration.sql | 61 +++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 380 ++++++++++-------- 4 files changed, 503 insertions(+), 173 deletions(-) create mode 100644 prisma/migrations/20260328045020_init/migration.sql create mode 100644 prisma/migrations/20260329201249_add_tournament_admin_role/migration.sql create mode 100644 prisma/migrations/migration_lock.toml diff --git a/prisma/migrations/20260328045020_init/migration.sql b/prisma/migrations/20260328045020_init/migration.sql new file mode 100644 index 0000000..6930997 --- /dev/null +++ b/prisma/migrations/20260328045020_init/migration.sql @@ -0,0 +1,232 @@ +-- CreateTable +CREATE TABLE "players" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "rating" INTEGER NOT NULL DEFAULT 0, + "currentElo" INTEGER NOT NULL DEFAULT 1000, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "users" ( + "id" TEXT NOT NULL PRIMARY KEY, + "email" TEXT NOT NULL, + "emailVerified" BOOLEAN NOT NULL DEFAULT false, + "name" TEXT, + "image" TEXT, + "role" TEXT NOT NULL DEFAULT 'player', + "playerId" INTEGER, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "users_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "players" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "events" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "event_id" INTEGER, + "name" TEXT NOT NULL, + "description" TEXT, + "eventDate" DATETIME, + "eventType" TEXT NOT NULL DEFAULT 'tournament', + "format" TEXT NOT NULL DEFAULT 'round_robin', + "status" TEXT NOT NULL DEFAULT 'planned', + "maxParticipants" INTEGER, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "event_participants" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "eventId" INTEGER NOT NULL, + "playerId" INTEGER NOT NULL, + "teamId" INTEGER, + "seed" INTEGER, + "status" TEXT NOT NULL DEFAULT 'registered', + "registrationDate" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "event_participants_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "event_participants_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "event_participants_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "events" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "teams" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "eventId" INTEGER NOT NULL, + "teamName" TEXT, + "player1Id" INTEGER NOT NULL, + "player2Id" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "teams_player2Id_fkey" FOREIGN KEY ("player2Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "teams_player1Id_fkey" FOREIGN KEY ("player1Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "teams_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "events" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "tournament_rounds" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "eventId" INTEGER NOT NULL, + "roundNumber" INTEGER NOT NULL, + "status" TEXT NOT NULL DEFAULT 'pending', + "scheduledStart" DATETIME, + "actualStart" DATETIME, + "completedAt" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "tournament_rounds_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "events" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "bracket_matchups" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "roundId" INTEGER NOT NULL, + "eventId" INTEGER NOT NULL, + "team1Id" INTEGER, + "team2Id" INTEGER, + "matchId" INTEGER, + "tableNumber" INTEGER, + "bracketPosition" INTEGER, + "winnerTeamId" INTEGER, + "loserTeamId" INTEGER, + "status" TEXT NOT NULL DEFAULT 'pending', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "bracket_matchups_matchId_fkey" FOREIGN KEY ("matchId") REFERENCES "matches" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "bracket_matchups_team2Id_fkey" FOREIGN KEY ("team2Id") REFERENCES "teams" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "bracket_matchups_team1Id_fkey" FOREIGN KEY ("team1Id") REFERENCES "teams" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "bracket_matchups_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "events" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "bracket_matchups_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "tournament_rounds" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "matches" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "eventId" INTEGER, + "playedAt" DATETIME, + "team1P1Id" INTEGER NOT NULL, + "team1P2Id" INTEGER NOT NULL, + "team2P1Id" INTEGER NOT NULL, + "team2P2Id" INTEGER NOT NULL, + "team1Score" INTEGER NOT NULL, + "team2Score" INTEGER NOT NULL, + "status" TEXT NOT NULL DEFAULT 'completed', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "matches_team2P2Id_fkey" FOREIGN KEY ("team2P2Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "matches_team2P1Id_fkey" FOREIGN KEY ("team2P1Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "matches_team1P2Id_fkey" FOREIGN KEY ("team1P2Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "matches_team1P1Id_fkey" FOREIGN KEY ("team1P1Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "matches_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "events" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "elo_snapshots" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "playerId" INTEGER NOT NULL, + "matchId" INTEGER NOT NULL, + "ratingBefore" INTEGER NOT NULL, + "ratingAfter" INTEGER NOT NULL, + "ratingChange" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "elo_snapshots_matchId_fkey" FOREIGN KEY ("matchId") REFERENCES "matches" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "elo_snapshots_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "partnership_games" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "player1Id" INTEGER NOT NULL, + "player2Id" INTEGER NOT NULL, + "matchId" INTEGER NOT NULL, + "teamNumber" INTEGER, + "wonMatch" INTEGER, + "player1EloChange" INTEGER, + "player2EloChange" INTEGER, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "partnership_games_matchId_fkey" FOREIGN KEY ("matchId") REFERENCES "matches" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "partnership_games_player2Id_fkey" FOREIGN KEY ("player2Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "partnership_games_player1Id_fkey" FOREIGN KEY ("player1Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "partnership_stats" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "player1Id" INTEGER NOT NULL, + "player2Id" INTEGER NOT NULL, + "gamesPlayed" INTEGER NOT NULL DEFAULT 0, + "wins" INTEGER NOT NULL DEFAULT 0, + "losses" INTEGER NOT NULL DEFAULT 0, + "totalEloChange" INTEGER NOT NULL DEFAULT 0, + "lastPlayed" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "partnership_stats_player2Id_fkey" FOREIGN KEY ("player2Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "partnership_stats_player1Id_fkey" FOREIGN KEY ("player1Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "session" ( + "id" TEXT NOT NULL PRIMARY KEY, + "expiresAt" DATETIME NOT NULL, + "token" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "userId" TEXT NOT NULL, + CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "account" ( + "id" TEXT NOT NULL PRIMARY KEY, + "accountId" TEXT NOT NULL, + "providerId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "accessToken" TEXT, + "refreshToken" TEXT, + "idToken" TEXT, + "accessTokenExpiresAt" DATETIME, + "refreshTokenExpiresAt" DATETIME, + "scope" TEXT, + "password" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "verification" ( + "id" TEXT NOT NULL PRIMARY KEY, + "identifier" TEXT NOT NULL, + "value" TEXT NOT NULL, + "expiresAt" DATETIME NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "players_name_key" ON "players"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_playerId_key" ON "users"("playerId"); + +-- CreateIndex +CREATE INDEX "session_userId_idx" ON "session"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_token_key" ON "session"("token"); + +-- CreateIndex +CREATE INDEX "account_userId_idx" ON "account"("userId"); + +-- CreateIndex +CREATE INDEX "verification_identifier_idx" ON "verification"("identifier"); diff --git a/prisma/migrations/20260329201249_add_tournament_admin_role/migration.sql b/prisma/migrations/20260329201249_add_tournament_admin_role/migration.sql new file mode 100644 index 0000000..76cffa9 --- /dev/null +++ b/prisma/migrations/20260329201249_add_tournament_admin_role/migration.sql @@ -0,0 +1,61 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_events" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "event_id" INTEGER, + "name" TEXT NOT NULL, + "description" TEXT, + "eventDate" DATETIME, + "eventType" TEXT NOT NULL DEFAULT 'tournament', + "format" TEXT NOT NULL DEFAULT 'round_robin', + "status" TEXT NOT NULL DEFAULT 'planned', + "maxParticipants" INTEGER, + "ownerId" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "events_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_events" ("createdAt", "description", "eventDate", "eventType", "event_id", "format", "id", "maxParticipants", "name", "status", "updatedAt") SELECT "createdAt", "description", "eventDate", "eventType", "event_id", "format", "id", "maxParticipants", "name", "status", "updatedAt" FROM "events"; +DROP TABLE "events"; +ALTER TABLE "new_events" RENAME TO "events"; +CREATE TABLE "new_matches" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "eventId" INTEGER, + "playedAt" DATETIME, + "team1P1Id" INTEGER NOT NULL, + "team1P2Id" INTEGER NOT NULL, + "team2P1Id" INTEGER NOT NULL, + "team2P2Id" INTEGER NOT NULL, + "team1Score" INTEGER NOT NULL, + "team2Score" INTEGER NOT NULL, + "status" TEXT NOT NULL DEFAULT 'completed', + "createdById" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "matches_team2P2Id_fkey" FOREIGN KEY ("team2P2Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "matches_team2P1Id_fkey" FOREIGN KEY ("team2P1Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "matches_team1P2Id_fkey" FOREIGN KEY ("team1P2Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "matches_team1P1Id_fkey" FOREIGN KEY ("team1P1Id") REFERENCES "players" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "matches_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "events" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "matches_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_matches" ("createdAt", "eventId", "id", "playedAt", "status", "team1P1Id", "team1P2Id", "team1Score", "team2P1Id", "team2P2Id", "team2Score", "updatedAt") SELECT "createdAt", "eventId", "id", "playedAt", "status", "team1P1Id", "team1P2Id", "team1Score", "team2P1Id", "team2P2Id", "team2Score", "updatedAt" FROM "matches"; +DROP TABLE "matches"; +ALTER TABLE "new_matches" RENAME TO "matches"; +CREATE TABLE "new_players" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "rating" INTEGER NOT NULL DEFAULT 0, + "currentElo" INTEGER NOT NULL DEFAULT 1000, + "gamesPlayed" INTEGER NOT NULL DEFAULT 0, + "wins" INTEGER NOT NULL DEFAULT 0, + "losses" INTEGER NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +INSERT INTO "new_players" ("createdAt", "currentElo", "id", "name", "rating", "updatedAt") SELECT "createdAt", "currentElo", "id", "name", "rating", "updatedAt" FROM "players"; +DROP TABLE "players"; +ALTER TABLE "new_players" RENAME TO "players"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..2a5a444 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "sqlite" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e8615b8..cec4d74 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,5 +1,3 @@ -// EuchreCamp Database Schema -// Generated from existing Ruby/ROM implementation generator client { provider = "prisma-client-js" @@ -10,192 +8,185 @@ datasource db { url = env("DATABASE_URL") } -// Player Model - Tracks individual player information model Player { - id Int @id @default(autoincrement()) - name String @unique - rating Int @default(0) - currentElo Int @default(1000) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relationships - users User[] - eventParticipants EventParticipant[] - teamsAsPlayer1 Team[] @relation("TeamPlayer1") - teamsAsPlayer2 Team[] @relation("TeamPlayer2") - matchesAsP1 Match[] @relation("MatchPlayer1") - matchesAsP2 Match[] @relation("MatchPlayer2") - matchesAsP3 Match[] @relation("MatchPlayer3") - matchesAsP4 Match[] @relation("MatchPlayer4") + id Int @id @default(autoincrement()) + name String + rating Int @default(0) // Historical rating (used for rankings) + currentElo Int @default(1000) // Current Elo rating + gamesPlayed Int @default(0) // Total games played + wins Int @default(0) // Total wins + losses Int @default(0) // Total losses + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt eloSnapshots EloSnapshot[] - partnershipGames PartnershipGame[] @relation("PartnershipPlayer1") - partnershipGames2 PartnershipGame[] @relation("PartnershipPlayer2") - partnershipStats PartnershipStat[] @relation("StatPlayer1") - partnershipStats2 PartnershipStat[] @relation("StatPlayer2") + eventParticipants EventParticipant[] + matchesAsP4 Match[] @relation("MatchPlayer4") + matchesAsP3 Match[] @relation("MatchPlayer3") + matchesAsP2 Match[] @relation("MatchPlayer2") + matchesAsP1 Match[] @relation("MatchPlayer1") + partnershipGames2 PartnershipGame[] @relation("PartnershipPlayer2") + partnershipGames PartnershipGame[] @relation("PartnershipPlayer1") + partnershipStats2 PartnershipStat[] @relation("StatPlayer2") + partnershipStats PartnershipStat[] @relation("StatPlayer1") + teamsAsPlayer2 Team[] @relation("TeamPlayer2") + teamsAsPlayer1 Team[] @relation("TeamPlayer1") + user User? @@map("players") } -// User Model - Authentication and authorization model User { - id Int @id @default(autoincrement()) - playerId Int @unique - email String @unique - passwordDigest String - role String @default("player") - confirmed Boolean @default(false) - confirmationToken String? - confirmationSentAt DateTime? - resetPasswordToken String? - resetPasswordSentAt DateTime? - failedLoginAttempts Int @default(0) - lockedUntil DateTime? - lastLoginAt DateTime? - lastLoginIp String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - player Player @relation(fields: [playerId], references: [id]) + id String @id @default(cuid()) + email String @unique + emailVerified Boolean @default(false) + name String? + image String? + role String @default("player") // player, tournament_admin, club_admin + + // Session and account references for Better Auth + sessions Session[] + accounts Account[] + + // Link to EuchreCamp player (optional) + playerId Int? @unique + player Player? @relation(fields: [playerId], references: [id]) + + // Tournaments owned by this user + ownedTournaments Event[] @relation("TournamentOwner") + + // Matches created by this user + createdMatches Match[] @relation("MatchCreator") + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@map("users") } -// Event (Tournament) Model model Event { - id Int @id @default(autoincrement()) - event_id Int? - name String - description String? - eventDate DateTime? - eventType String @default("tournament") - format String @default("round_robin") - status String @default("planned") + id Int @id @default(autoincrement()) + event_id Int? + name String + description String? + eventDate DateTime? + eventType String @default("tournament") + format String @default("round_robin") + status String @default("planned") maxParticipants Int? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relationships - participants EventParticipant[] - teams Team[] - rounds TournamentRound[] - bracketMatchups BracketMatchup[] - matches Match[] + ownerId String? // User ID of the tournament owner (for tournament_admin) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + bracketMatchups BracketMatchup[] + participants EventParticipant[] + matches Match[] + teams Team[] + rounds TournamentRound[] + owner User? @relation("TournamentOwner", fields: [ownerId], references: [id]) @@map("events") } -// EventParticipant Model model EventParticipant { - id Int @id @default(autoincrement()) - eventId Int - playerId Int - teamId Int? - seed Int? - status String @default("registered") + id Int @id @default(autoincrement()) + eventId Int + playerId Int + teamId Int? + seed Int? + status String @default("registered") registrationDate DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - event Event @relation(fields: [eventId], references: [id]) - player Player @relation(fields: [playerId], references: [id]) - team Team? @relation(fields: [teamId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + team Team? @relation(fields: [teamId], references: [id]) + player Player @relation(fields: [playerId], references: [id]) + event Event @relation(fields: [eventId], references: [id]) @@map("event_participants") } -// Team Model model Team { - id Int @id @default(autoincrement()) - eventId Int - teamName String? - player1Id Int - player2Id Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relationships - event Event @relation(fields: [eventId], references: [id]) - player1 Player @relation("TeamPlayer1", fields: [player1Id], references: [id]) - player2 Player @relation("TeamPlayer2", fields: [player2Id], references: [id]) - eventParticipants EventParticipant[] - bracketMatchups1 BracketMatchup[] @relation("BracketTeam1") - bracketMatchups2 BracketMatchup[] @relation("BracketTeam2") + id Int @id @default(autoincrement()) + eventId Int + teamName String? + player1Id Int + player2Id Int + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + bracketMatchups2 BracketMatchup[] @relation("BracketTeam2") + bracketMatchups1 BracketMatchup[] @relation("BracketTeam1") + eventParticipants EventParticipant[] + player2 Player @relation("TeamPlayer2", fields: [player2Id], references: [id]) + player1 Player @relation("TeamPlayer1", fields: [player1Id], references: [id]) + event Event @relation(fields: [eventId], references: [id]) @@map("teams") } -// TournamentRound Model model TournamentRound { - id Int @id @default(autoincrement()) - eventId Int - roundNumber Int - status String @default("pending") - scheduledStart DateTime? - actualStart DateTime? - completedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - event Event @relation(fields: [eventId], references: [id]) + id Int @id @default(autoincrement()) + eventId Int + roundNumber Int + status String @default("pending") + scheduledStart DateTime? + actualStart DateTime? + completedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt bracketMatchups BracketMatchup[] + event Event @relation(fields: [eventId], references: [id]) @@map("tournament_rounds") } -// BracketMatchup Model model BracketMatchup { - id Int @id @default(autoincrement()) - roundId Int - eventId Int - team1Id Int? - team2Id Int? - matchId Int? - tableNumber Int? + id Int @id @default(autoincrement()) + roundId Int + eventId Int + team1Id Int? + team2Id Int? + matchId Int? + tableNumber Int? bracketPosition Int? - winnerTeamId Int? - loserTeamId Int? - status String @default("pending") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - round TournamentRound @relation(fields: [roundId], references: [id]) - event Event @relation(fields: [eventId], references: [id]) - team1 Team? @relation("BracketTeam1", fields: [team1Id], references: [id]) - team2 Team? @relation("BracketTeam2", fields: [team2Id], references: [id]) - match Match? @relation(fields: [matchId], references: [id]) + winnerTeamId Int? + loserTeamId Int? + status String @default("pending") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + match Match? @relation(fields: [matchId], references: [id]) + team2 Team? @relation("BracketTeam2", fields: [team2Id], references: [id]) + team1 Team? @relation("BracketTeam1", fields: [team1Id], references: [id]) + event Event @relation(fields: [eventId], references: [id]) + round TournamentRound @relation(fields: [roundId], references: [id]) @@map("bracket_matchups") } -// Match Model model Match { - id Int @id @default(autoincrement()) - eventId Int? - playedAt DateTime? - team1P1Id Int - team1P2Id Int - team2P1Id Int - team2P2Id Int - team1Score Int - team2Score Int - status String @default("completed") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - event Event? @relation(fields: [eventId], references: [id]) - team1P1 Player @relation("MatchPlayer1", fields: [team1P1Id], references: [id]) - team1P2 Player @relation("MatchPlayer2", fields: [team1P2Id], references: [id]) - team2P1 Player @relation("MatchPlayer3", fields: [team2P1Id], references: [id]) - team2P2 Player @relation("MatchPlayer4", fields: [team2P2Id], references: [id]) - eloSnapshots EloSnapshot[] + id Int @id @default(autoincrement()) + eventId Int? + playedAt DateTime? + team1P1Id Int + team1P2Id Int + team2P1Id Int + team2P2Id Int + team1Score Int + team2Score Int + status String @default("completed") + createdById String? // User ID of the match creator (for tournament_admin) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + bracketMatchups BracketMatchup[] + eloSnapshots EloSnapshot[] + team2P2 Player @relation("MatchPlayer4", fields: [team2P2Id], references: [id]) + team2P1 Player @relation("MatchPlayer3", fields: [team2P1Id], references: [id]) + team1P2 Player @relation("MatchPlayer2", fields: [team1P2Id], references: [id]) + team1P1 Player @relation("MatchPlayer1", fields: [team1P1Id], references: [id]) + event Event? @relation(fields: [eventId], references: [id]) partnershipGames PartnershipGame[] - bracketMatchups BracketMatchup[] + createdBy User? @relation("MatchCreator", fields: [createdById], references: [id]) @@map("matches") } -// EloSnapshot Model model EloSnapshot { id Int @id @default(autoincrement()) playerId Int @@ -204,47 +195,90 @@ model EloSnapshot { ratingAfter Int ratingChange Int createdAt DateTime @default(now()) - - player Player @relation(fields: [playerId], references: [id]) - match Match @relation(fields: [matchId], references: [id]) + match Match @relation(fields: [matchId], references: [id]) + player Player @relation(fields: [playerId], references: [id]) @@map("elo_snapshots") } -// PartnershipGame Model - Tracks each game played by a partnership model PartnershipGame { - id Int @id @default(autoincrement()) - player1Id Int - player2Id Int - matchId Int - teamNumber Int? - wonMatch Int? - player1EloChange Int? - player2EloChange Int? - createdAt DateTime @default(now()) - - player1 Player @relation("PartnershipPlayer1", fields: [player1Id], references: [id]) - player2 Player @relation("PartnershipPlayer2", fields: [player2Id], references: [id]) - match Match @relation(fields: [matchId], references: [id]) + id Int @id @default(autoincrement()) + player1Id Int + player2Id Int + matchId Int + teamNumber Int? + wonMatch Int? + player1EloChange Int? + player2EloChange Int? + createdAt DateTime @default(now()) + match Match @relation(fields: [matchId], references: [id]) + player2 Player @relation("PartnershipPlayer2", fields: [player2Id], references: [id]) + player1 Player @relation("PartnershipPlayer1", fields: [player1Id], references: [id]) @@map("partnership_games") } -// PartnershipStat Model - Aggregated partnership statistics model PartnershipStat { - id Int @id @default(autoincrement()) - player1Id Int - player2Id Int - gamesPlayed Int @default(0) - wins Int @default(0) - losses Int @default(0) - totalEloChange Int @default(0) - lastPlayed DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - player1 Player @relation("StatPlayer1", fields: [player1Id], references: [id]) - player2 Player @relation("StatPlayer2", fields: [player2Id], references: [id]) + id Int @id @default(autoincrement()) + player1Id Int + player2Id Int + gamesPlayed Int @default(0) + wins Int @default(0) + losses Int @default(0) + totalEloChange Int @default(0) + lastPlayed DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + player2 Player @relation("StatPlayer2", fields: [player2Id], references: [id]) + player1 Player @relation("StatPlayer1", fields: [player1Id], references: [id]) @@map("partnership_stats") } + +model Session { + id String @id + expiresAt DateTime + token String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + ipAddress String? + userAgent String? + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([token]) + @@index([userId]) + @@map("session") +} + +model Account { + id String @id + accountId String + providerId String + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + accessToken String? + refreshToken String? + idToken String? + accessTokenExpiresAt DateTime? + refreshTokenExpiresAt DateTime? + scope String? + password String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([userId]) + @@map("account") +} + +model Verification { + id String @id + identifier String + value String + expiresAt DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([identifier]) + @@map("verification") +}