750 lines
29 KiB
TypeScript
750 lines
29 KiB
TypeScript
import { integer, pgTable, varchar, uuid, timestamp, pgEnum, boolean, text, date, decimal } from "drizzle-orm/pg-core";
|
|
import { relations } from "drizzle-orm";
|
|
|
|
// Users table - synced from Clerk
|
|
export const users = pgTable("users", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
clerkId: varchar("clerk_id", { length: 255 }).notNull().unique(),
|
|
email: varchar("email", { length: 255 }).notNull(),
|
|
username: varchar("username", { length: 255 }),
|
|
displayName: varchar("display_name", { length: 255 }),
|
|
firstName: varchar("first_name", { length: 255 }),
|
|
lastName: varchar("last_name", { length: 255 }),
|
|
imageUrl: varchar("image_url", { length: 512 }),
|
|
isAdmin: boolean("is_admin").notNull().default(false),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Fantasy League Tables
|
|
export const seasonStatusEnum = pgEnum("season_status", [
|
|
"pre_draft",
|
|
"draft",
|
|
"active",
|
|
"completed",
|
|
]);
|
|
|
|
// Sports Tables - Enums
|
|
export const sportTypeEnum = pgEnum("sport_type", ["team", "individual"]);
|
|
|
|
export const sportsSeasonStatusEnum = pgEnum("sports_season_status", [
|
|
"upcoming",
|
|
"active",
|
|
"completed",
|
|
]);
|
|
|
|
export const scoringTypeEnum = pgEnum("scoring_type", [
|
|
"playoffs",
|
|
"regular_season",
|
|
"majors",
|
|
]);
|
|
|
|
export const scoringPatternEnum = pgEnum("scoring_pattern", [
|
|
"single_elimination_playoff",
|
|
"page_playoff",
|
|
"season_standings",
|
|
"qualifying_points",
|
|
]);
|
|
|
|
export const eventTypeEnum = pgEnum("event_type", [
|
|
"playoff_game",
|
|
"major_tournament",
|
|
"final_standings",
|
|
]);
|
|
|
|
export const pickedByTypeEnum = pgEnum("picked_by_type", [
|
|
"owner",
|
|
"commissioner",
|
|
"auto",
|
|
]);
|
|
|
|
export const autodraftModeEnum = pgEnum("autodraft_mode", [
|
|
"next_pick",
|
|
"while_on",
|
|
]);
|
|
|
|
export const leagues = pgTable("leagues", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
createdBy: varchar("created_by", { length: 255 }).notNull(), // Clerk user ID
|
|
currentSeasonId: uuid("current_season_id"), // References the active season
|
|
isPublicDraftBoard: boolean("is_public_draft_board").notNull().default(false),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const seasonTemplates = pgTable("season_templates", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
year: integer("year").notNull(),
|
|
isActive: boolean("is_active").notNull().default(true),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const seasons = pgTable("seasons", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
leagueId: uuid("league_id")
|
|
.notNull()
|
|
.references(() => leagues.id, { onDelete: "cascade" }),
|
|
year: integer("year").notNull(),
|
|
status: seasonStatusEnum("status").notNull().default("pre_draft"),
|
|
templateId: uuid("template_id")
|
|
.references(() => seasonTemplates.id, { onDelete: "set null" }),
|
|
draftRounds: integer("draft_rounds").notNull().default(20),
|
|
flexSpots: integer("flex_spots").notNull().default(0),
|
|
draftDateTime: timestamp("draft_date_time"),
|
|
draftInitialTime: integer("draft_initial_time").notNull().default(120), // seconds
|
|
draftIncrementTime: integer("draft_increment_time").notNull().default(30), // seconds
|
|
currentPickNumber: integer("current_pick_number").default(1),
|
|
draftStartedAt: timestamp("draft_started_at"),
|
|
draftPaused: boolean("draft_paused").notNull().default(false),
|
|
inviteCode: varchar("invite_code", { length: 20 }).notNull().unique(),
|
|
// Scoring configuration (8 values for 1st through 8th)
|
|
pointsFor1st: integer("points_for_1st").notNull().default(100),
|
|
pointsFor2nd: integer("points_for_2nd").notNull().default(70),
|
|
pointsFor3rd: integer("points_for_3rd").notNull().default(50),
|
|
pointsFor4th: integer("points_for_4th").notNull().default(40),
|
|
pointsFor5th: integer("points_for_5th").notNull().default(25),
|
|
pointsFor6th: integer("points_for_6th").notNull().default(25),
|
|
pointsFor7th: integer("points_for_7th").notNull().default(15),
|
|
pointsFor8th: integer("points_for_8th").notNull().default(15),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const teams = pgTable("teams", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
logoUrl: varchar("logo_url", { length: 512 }),
|
|
ownerId: varchar("owner_id", { length: 255 }), // Clerk user ID, nullable
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const draftSlots = pgTable("draft_slots", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
teamId: uuid("team_id")
|
|
.notNull()
|
|
.references(() => teams.id, { onDelete: "cascade" }),
|
|
draftOrder: integer("draft_order").notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const draftPicks = pgTable("draft_picks", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
teamId: uuid("team_id")
|
|
.notNull()
|
|
.references(() => teams.id, { onDelete: "cascade" }),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
pickNumber: integer("pick_number").notNull(),
|
|
round: integer("round").notNull(),
|
|
pickInRound: integer("pick_in_round").notNull(),
|
|
pickedByUserId: varchar("picked_by_user_id", { length: 255 }).notNull(), // Clerk user ID
|
|
pickedByType: pickedByTypeEnum("picked_by_type").notNull(),
|
|
timeUsed: integer("time_used").notNull().default(0), // seconds
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const draftQueue = pgTable("draft_queue", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
teamId: uuid("team_id")
|
|
.notNull()
|
|
.references(() => teams.id, { onDelete: "cascade" }),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
queuePosition: integer("queue_position").notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const draftTimers = pgTable("draft_timers", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
teamId: uuid("team_id")
|
|
.notNull()
|
|
.references(() => teams.id, { onDelete: "cascade" }),
|
|
timeRemaining: integer("time_remaining").notNull(), // seconds
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const autodraftSettings = pgTable("autodraft_settings", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
teamId: uuid("team_id")
|
|
.notNull()
|
|
.references(() => teams.id, { onDelete: "cascade" }),
|
|
isEnabled: boolean("is_enabled").notNull().default(false),
|
|
mode: autodraftModeEnum("mode").notNull().default("next_pick"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const commissioners = pgTable("commissioners", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
leagueId: uuid("league_id")
|
|
.notNull()
|
|
.references(() => leagues.id, { onDelete: "cascade" }),
|
|
userId: varchar("user_id", { length: 255 }).notNull(), // Clerk user ID
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Sports Tables
|
|
export const sports = pgTable("sports", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
type: sportTypeEnum("type").notNull(),
|
|
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
|
description: text("description"),
|
|
iconUrl: varchar("icon_url", { length: 255 }), // Filename of icon in /public/sports-icons/
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const sportsSeasons = pgTable("sports_seasons", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
sportId: uuid("sport_id")
|
|
.notNull()
|
|
.references(() => sports.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
year: integer("year").notNull(),
|
|
startDate: date("start_date"),
|
|
endDate: date("end_date"),
|
|
status: sportsSeasonStatusEnum("status").notNull().default("upcoming"),
|
|
scoringType: scoringTypeEnum("scoring_type").notNull(),
|
|
// New scoring pattern field (replaces/augments scoringType)
|
|
scoringPattern: scoringPatternEnum("scoring_pattern"),
|
|
// For qualifying points sports
|
|
totalMajors: integer("total_majors"),
|
|
majorsCompleted: integer("majors_completed").notNull().default(0),
|
|
qualifyingPointsFinalized: boolean("qualifying_points_finalized").notNull().default(false),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const participants = pgTable("participants", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
shortName: varchar("short_name", { length: 100 }),
|
|
externalId: varchar("external_id", { length: 255 }),
|
|
expectedValue: integer("expected_value").notNull().default(0),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const seasonTemplateSports = pgTable("season_template_sports", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
templateId: uuid("template_id")
|
|
.notNull()
|
|
.references(() => seasonTemplates.id, { onDelete: "cascade" }),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const seasonSports = pgTable("season_sports", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
export const participantResults = pgTable("participant_results", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
finalPosition: integer("final_position"),
|
|
qualifyingPoints: decimal("qualifying_points", { precision: 10, scale: 2 }),
|
|
notes: text("notes"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Scoring System Tables
|
|
|
|
// Individual scoring events (games, tournaments, races)
|
|
export const scoringEvents = pgTable("scoring_events", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
name: varchar("name", { length: 255 }).notNull(), // "2024 Masters", "Super Bowl LIX", etc.
|
|
eventDate: date("event_date"),
|
|
eventType: eventTypeEnum("event_type").notNull(),
|
|
// For playoff events
|
|
playoffRound: varchar("playoff_round", { length: 50 }), // "Quarterfinals", "Semifinals", "Finals"
|
|
// For qualifying events
|
|
isQualifyingEvent: boolean("is_qualifying_event").notNull().default(false),
|
|
// Template system (Phase 2.6)
|
|
bracketTemplateId: varchar("bracket_template_id", { length: 50 }), // "ncaa_68", "nfl_14", "simple_16", etc.
|
|
scoringStartsAtRound: varchar("scoring_starts_at_round", { length: 50 }), // "Elite Eight", "Quarterfinals", etc.
|
|
isComplete: boolean("is_complete").notNull().default(false),
|
|
completedAt: timestamp("completed_at"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Results for participants in specific events
|
|
export const eventResults = pgTable("event_results", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
scoringEventId: uuid("scoring_event_id")
|
|
.notNull()
|
|
.references(() => scoringEvents.id, { onDelete: "cascade" }),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
// Placement in this specific event
|
|
placement: integer("placement"),
|
|
// For qualifying events: QP awarded in this event
|
|
qualifyingPointsAwarded: decimal("qualifying_points_awarded", { precision: 10, scale: 2 }),
|
|
// For playoff events: eliminated or advanced
|
|
eliminated: boolean("eliminated"),
|
|
// Raw sport-specific data (optional)
|
|
rawScore: decimal("raw_score", { precision: 10, scale: 2 }), // strokes, points, time, etc.
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Playoff bracket matches (for display and tracking)
|
|
export const playoffMatches = pgTable("playoff_matches", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
scoringEventId: uuid("scoring_event_id")
|
|
.notNull()
|
|
.references(() => scoringEvents.id, { onDelete: "cascade" }),
|
|
round: varchar("round", { length: 50 }).notNull(), // "Quarterfinals", "Semifinals", "Finals"
|
|
matchNumber: integer("match_number").notNull(), // 1, 2, 3, 4 (for QF); 1, 2 (for SF); 1 (for F)
|
|
participant1Id: uuid("participant1_id").references(() => participants.id, { onDelete: "set null" }),
|
|
participant2Id: uuid("participant2_id").references(() => participants.id, { onDelete: "set null" }),
|
|
winnerId: uuid("winner_id").references(() => participants.id, { onDelete: "set null" }),
|
|
loserId: uuid("loser_id").references(() => participants.id, { onDelete: "set null" }),
|
|
isComplete: boolean("is_complete").notNull().default(false),
|
|
// Optional detailed data
|
|
participant1Score: decimal("participant1_score", { precision: 10, scale: 2 }),
|
|
participant2Score: decimal("participant2_score", { precision: 10, scale: 2 }),
|
|
// Template system (Phase 2.6)
|
|
isScoring: boolean("is_scoring").notNull().default(true), // Does this match affect fantasy points?
|
|
templateRound: varchar("template_round", { length: 50 }), // "First Four", "Round of 64", "Elite Eight", etc.
|
|
seedInfo: varchar("seed_info", { length: 50 }), // "1 vs 16", "11a vs 11b", etc. (for display)
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Aggregated participant qualifying points (for qualifying_points sports)
|
|
export const participantQualifyingTotals = pgTable("participant_qualifying_totals", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
totalQualifyingPoints: decimal("total_qualifying_points", { precision: 10, scale: 2 }).notNull().default("0"),
|
|
eventsScored: integer("events_scored").notNull().default(0),
|
|
// After finalization
|
|
finalRanking: integer("final_ranking"), // 1-8 based on QP totals
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Configurable qualifying point values per sports season
|
|
export const qualifyingPointConfig = pgTable("qualifying_point_config", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
placement: integer("placement").notNull(), // 1-16
|
|
points: decimal("points", { precision: 10, scale: 2 }).notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Team scores aggregated by sport season
|
|
export const teamSportScores = pgTable("team_sport_scores", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
teamId: uuid("team_id")
|
|
.notNull()
|
|
.references(() => teams.id, { onDelete: "cascade" }),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
totalPoints: decimal("total_points", { precision: 10, scale: 2 }).notNull().default("0"),
|
|
participantsCompleted: integer("participants_completed").notNull().default(0), // How many picks have finished
|
|
participantsTotal: integer("participants_total").notNull().default(0), // Total picks for this sport
|
|
calculatedAt: timestamp("calculated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Overall team standings (snapshot-based for movement tracking)
|
|
export const teamStandings = pgTable("team_standings", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
teamId: uuid("team_id")
|
|
.notNull()
|
|
.references(() => teams.id, { onDelete: "cascade" }),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
totalPoints: decimal("total_points", { precision: 10, scale: 2 }).notNull().default("0"),
|
|
currentRank: integer("current_rank").notNull(),
|
|
previousRank: integer("previous_rank"), // For movement indicators
|
|
// Tiebreaker data
|
|
firstPlaceCount: integer("first_place_count").notNull().default(0),
|
|
secondPlaceCount: integer("second_place_count").notNull().default(0),
|
|
thirdPlaceCount: integer("third_place_count").notNull().default(0),
|
|
fourthPlaceCount: integer("fourth_place_count").notNull().default(0),
|
|
fifthPlaceCount: integer("fifth_place_count").notNull().default(0),
|
|
sixthPlaceCount: integer("sixth_place_count").notNull().default(0),
|
|
seventhPlaceCount: integer("seventh_place_count").notNull().default(0),
|
|
eighthPlaceCount: integer("eighth_place_count").notNull().default(0),
|
|
participantsRemaining: integer("participants_remaining").notNull().default(0), // Not yet finished
|
|
calculatedAt: timestamp("calculated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Daily standings snapshots for historical tracking
|
|
export const teamStandingsSnapshots = pgTable("team_standings_snapshots", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
teamId: uuid("team_id")
|
|
.notNull()
|
|
.references(() => teams.id, { onDelete: "cascade" }),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
snapshotDate: date("snapshot_date").notNull(),
|
|
totalPoints: decimal("total_points", { precision: 10, scale: 2 }).notNull().default("0"),
|
|
rank: integer("rank").notNull(),
|
|
// Tiebreaker data
|
|
firstPlaceCount: integer("first_place_count").notNull().default(0),
|
|
secondPlaceCount: integer("second_place_count").notNull().default(0),
|
|
thirdPlaceCount: integer("third_place_count").notNull().default(0),
|
|
fourthPlaceCount: integer("fourth_place_count").notNull().default(0),
|
|
fifthPlaceCount: integer("fifth_place_count").notNull().default(0),
|
|
sixthPlaceCount: integer("sixth_place_count").notNull().default(0),
|
|
seventhPlaceCount: integer("seventh_place_count").notNull().default(0),
|
|
eighthPlaceCount: integer("eighth_place_count").notNull().default(0),
|
|
participantsRemaining: integer("participants_remaining").notNull().default(0),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Expected value tracking (league-specific)
|
|
export const participantExpectedValues = pgTable("participant_expected_values", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
seasonId: uuid("season_id")
|
|
.notNull()
|
|
.references(() => seasons.id, { onDelete: "cascade" }),
|
|
// Probability distribution (stored as percentages)
|
|
probFirst: decimal("prob_first", { precision: 5, scale: 2 }).notNull().default("0"), // e.g., 15.50 = 15.5%
|
|
probSecond: decimal("prob_second", { precision: 5, scale: 2 }).notNull().default("0"),
|
|
probThird: decimal("prob_third", { precision: 5, scale: 2 }).notNull().default("0"),
|
|
probFourth: decimal("prob_fourth", { precision: 5, scale: 2 }).notNull().default("0"),
|
|
probFifth: decimal("prob_fifth", { precision: 5, scale: 2 }).notNull().default("0"),
|
|
probSixth: decimal("prob_sixth", { precision: 5, scale: 2 }).notNull().default("0"),
|
|
probSeventh: decimal("prob_seventh", { precision: 5, scale: 2 }).notNull().default("0"),
|
|
probEighth: decimal("prob_eighth", { precision: 5, scale: 2 }).notNull().default("0"),
|
|
// Calculated EV
|
|
expectedValue: decimal("expected_value", { precision: 10, scale: 2 }).notNull().default("0"),
|
|
calculatedAt: timestamp("calculated_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Participant season results (for F1 current points tracking during season)
|
|
export const participantSeasonResults = pgTable("participant_season_results", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
participantId: uuid("participant_id")
|
|
.notNull()
|
|
.references(() => participants.id, { onDelete: "cascade" }),
|
|
sportsSeasonId: uuid("sports_season_id")
|
|
.notNull()
|
|
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
|
|
currentPoints: decimal("current_points", { precision: 10, scale: 2 }).notNull().default("0"), // Current F1 championship points, etc.
|
|
currentPosition: integer("current_position"), // Current standings position
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Relations
|
|
export const sportsRelations = relations(sports, ({ many }) => ({
|
|
sportsSeasons: many(sportsSeasons),
|
|
}));
|
|
|
|
export const sportsSeasonsRelations = relations(sportsSeasons, ({ one, many }) => ({
|
|
sport: one(sports, {
|
|
fields: [sportsSeasons.sportId],
|
|
references: [sports.id],
|
|
}),
|
|
participants: many(participants),
|
|
seasonTemplateSports: many(seasonTemplateSports),
|
|
seasonSports: many(seasonSports),
|
|
participantResults: many(participantResults),
|
|
}));
|
|
|
|
export const participantsRelations = relations(participants, ({ one, many }) => ({
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [participants.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
results: many(participantResults),
|
|
}));
|
|
|
|
export const seasonTemplatesRelations = relations(seasonTemplates, ({ many }) => ({
|
|
seasonTemplateSports: many(seasonTemplateSports),
|
|
seasons: many(seasons),
|
|
}));
|
|
|
|
export const seasonTemplateSportsRelations = relations(seasonTemplateSports, ({ one }) => ({
|
|
template: one(seasonTemplates, {
|
|
fields: [seasonTemplateSports.templateId],
|
|
references: [seasonTemplates.id],
|
|
}),
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [seasonTemplateSports.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const seasonsRelations = relations(seasons, ({ one, many }) => ({
|
|
league: one(leagues, {
|
|
fields: [seasons.leagueId],
|
|
references: [leagues.id],
|
|
}),
|
|
template: one(seasonTemplates, {
|
|
fields: [seasons.templateId],
|
|
references: [seasonTemplates.id],
|
|
}),
|
|
teams: many(teams),
|
|
seasonSports: many(seasonSports),
|
|
}));
|
|
|
|
export const seasonSportsRelations = relations(seasonSports, ({ one }) => ({
|
|
season: one(seasons, {
|
|
fields: [seasonSports.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [seasonSports.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const participantResultsRelations = relations(participantResults, ({ one }) => ({
|
|
participant: one(participants, {
|
|
fields: [participantResults.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [participantResults.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const teamsRelations = relations(teams, ({ one }) => ({
|
|
season: one(seasons, {
|
|
fields: [teams.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
draftSlot: one(draftSlots, {
|
|
fields: [teams.id],
|
|
references: [draftSlots.teamId],
|
|
}),
|
|
}));
|
|
|
|
export const draftSlotsRelations = relations(draftSlots, ({ one }) => ({
|
|
season: one(seasons, {
|
|
fields: [draftSlots.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
team: one(teams, {
|
|
fields: [draftSlots.teamId],
|
|
references: [teams.id],
|
|
}),
|
|
}));
|
|
|
|
export const draftPicksRelations = relations(draftPicks, ({ one }) => ({
|
|
season: one(seasons, {
|
|
fields: [draftPicks.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
team: one(teams, {
|
|
fields: [draftPicks.teamId],
|
|
references: [teams.id],
|
|
}),
|
|
participant: one(participants, {
|
|
fields: [draftPicks.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
}));
|
|
|
|
export const draftQueueRelations = relations(draftQueue, ({ one }) => ({
|
|
season: one(seasons, {
|
|
fields: [draftQueue.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
team: one(teams, {
|
|
fields: [draftQueue.teamId],
|
|
references: [teams.id],
|
|
}),
|
|
participant: one(participants, {
|
|
fields: [draftQueue.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
}));
|
|
|
|
export const autodraftSettingsRelations = relations(autodraftSettings, ({ one }) => ({
|
|
season: one(seasons, {
|
|
fields: [autodraftSettings.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
team: one(teams, {
|
|
fields: [autodraftSettings.teamId],
|
|
references: [teams.id],
|
|
}),
|
|
}));
|
|
|
|
// Scoring System Relations
|
|
|
|
export const scoringEventsRelations = relations(scoringEvents, ({ one, many }) => ({
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [scoringEvents.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
eventResults: many(eventResults),
|
|
playoffMatches: many(playoffMatches),
|
|
}));
|
|
|
|
export const eventResultsRelations = relations(eventResults, ({ one }) => ({
|
|
scoringEvent: one(scoringEvents, {
|
|
fields: [eventResults.scoringEventId],
|
|
references: [scoringEvents.id],
|
|
}),
|
|
participant: one(participants, {
|
|
fields: [eventResults.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
}));
|
|
|
|
export const playoffMatchesRelations = relations(playoffMatches, ({ one }) => ({
|
|
scoringEvent: one(scoringEvents, {
|
|
fields: [playoffMatches.scoringEventId],
|
|
references: [scoringEvents.id],
|
|
}),
|
|
participant1: one(participants, {
|
|
fields: [playoffMatches.participant1Id],
|
|
references: [participants.id],
|
|
}),
|
|
participant2: one(participants, {
|
|
fields: [playoffMatches.participant2Id],
|
|
references: [participants.id],
|
|
}),
|
|
winner: one(participants, {
|
|
fields: [playoffMatches.winnerId],
|
|
references: [participants.id],
|
|
}),
|
|
loser: one(participants, {
|
|
fields: [playoffMatches.loserId],
|
|
references: [participants.id],
|
|
}),
|
|
}));
|
|
|
|
export const participantQualifyingTotalsRelations = relations(participantQualifyingTotals, ({ one }) => ({
|
|
participant: one(participants, {
|
|
fields: [participantQualifyingTotals.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [participantQualifyingTotals.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const qualifyingPointConfigRelations = relations(qualifyingPointConfig, ({ one }) => ({
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [qualifyingPointConfig.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const teamSportScoresRelations = relations(teamSportScores, ({ one }) => ({
|
|
team: one(teams, {
|
|
fields: [teamSportScores.teamId],
|
|
references: [teams.id],
|
|
}),
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [teamSportScores.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const teamStandingsRelations = relations(teamStandings, ({ one }) => ({
|
|
team: one(teams, {
|
|
fields: [teamStandings.teamId],
|
|
references: [teams.id],
|
|
}),
|
|
season: one(seasons, {
|
|
fields: [teamStandings.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const teamStandingsSnapshotsRelations = relations(teamStandingsSnapshots, ({ one }) => ({
|
|
team: one(teams, {
|
|
fields: [teamStandingsSnapshots.teamId],
|
|
references: [teams.id],
|
|
}),
|
|
season: one(seasons, {
|
|
fields: [teamStandingsSnapshots.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const participantExpectedValuesRelations = relations(participantExpectedValues, ({ one }) => ({
|
|
participant: one(participants, {
|
|
fields: [participantExpectedValues.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
season: one(seasons, {
|
|
fields: [participantExpectedValues.seasonId],
|
|
references: [seasons.id],
|
|
}),
|
|
}));
|
|
|
|
export const participantSeasonResultsRelations = relations(participantSeasonResults, ({ one }) => ({
|
|
participant: one(participants, {
|
|
fields: [participantSeasonResults.participantId],
|
|
references: [participants.id],
|
|
}),
|
|
sportsSeason: one(sportsSeasons, {
|
|
fields: [participantSeasonResults.sportsSeasonId],
|
|
references: [sportsSeasons.id],
|
|
}),
|
|
}));
|