From fdfce98d87bb967466c01dfedfab7196258b4964 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Wed, 15 Oct 2025 08:58:35 -0700 Subject: [PATCH] feat: add draft order management with UI for manual and random ordering --- DRAFT_ORDER_IMPLEMENTATION.md | 88 ++ app/models/draft-slot.ts | 144 +++ app/models/index.ts | 1 + app/routes/leagues/$leagueId.settings.tsx | 178 +++- app/routes/leagues/$leagueId.tsx | 40 + database/schema.ts | 35 + drizzle/0013_pink_the_order.sql | 20 + drizzle/meta/0013_snapshot.json | 1038 +++++++++++++++++++++ drizzle/meta/_journal.json | 7 + 9 files changed, 1548 insertions(+), 3 deletions(-) create mode 100644 DRAFT_ORDER_IMPLEMENTATION.md create mode 100644 app/models/draft-slot.ts create mode 100644 drizzle/0013_pink_the_order.sql create mode 100644 drizzle/meta/0013_snapshot.json diff --git a/DRAFT_ORDER_IMPLEMENTATION.md b/DRAFT_ORDER_IMPLEMENTATION.md new file mode 100644 index 0000000..daa3547 --- /dev/null +++ b/DRAFT_ORDER_IMPLEMENTATION.md @@ -0,0 +1,88 @@ +# Draft Order Implementation + +## Overview +Implemented a complete draft slot system that allows commissioners to manage draft order for their fantasy leagues. + +## What Was Implemented + +### 1. Database Schema +- **New Table**: `draft_slots` + - `id`: UUID primary key + - `season_id`: Foreign key to seasons (cascade delete) + - `team_id`: Foreign key to teams (cascade delete) + - `draft_order`: Integer representing the draft position + - `created_at`, `updated_at`: Timestamps + +### 2. Model Layer (`app/models/draft-slot.ts`) +- **CRUD Operations**: + - `createDraftSlot()` - Create a single draft slot + - `createManyDraftSlots()` - Create multiple draft slots + - `findDraftSlotsBySeasonId()` - Get all draft slots for a season (with team info) + - `findDraftSlotByTeamId()` - Find draft slot by team + - `updateDraftSlotOrder()` - Update a slot's order + - `deleteDraftSlotsBySeasonId()` - Delete all slots for a season + - `deleteDraftSlot()` - Delete a specific slot + +- **Special Functions**: + - `setDraftOrder()` - Set the complete draft order for a season + - `randomizeDraftOrder()` - Randomize draft order using Fisher-Yates shuffle + +### 3. League Settings Page (`app/routes/leagues/$leagueId.settings.tsx`) +- **New Draft Order Section**: + - Visual list showing current draft order with numbered badges + - Up/Down arrow buttons to manually reorder teams + - "Save Draft Order" button to persist changes + - "šŸŽ² Randomize Draft Order" button for random order generation + - Only editable during `pre_draft` status + - Shows helpful note when draft order hasn't been set yet + +- **Action Handlers**: + - `set-draft-order` - Validates and saves manual draft order + - `randomize-draft-order` - Generates and saves random order + +### 4. League Page (`app/routes/leagues/$leagueId.tsx`) +- **Draft Order Display Box**: + - Shows in right column during `pre_draft` and `draft` statuses + - Displays numbered list of teams in draft order + - Shows team owner names when available + - Automatically hidden once season moves to `active` or `completed` + +### 5. Database Migration +- Generated migration: `drizzle/0013_pink_the_order.sql` +- Successfully applied to database + +## Key Features + +### Commissioner Controls +- **Manual Ordering**: Commissioners can manually arrange teams using up/down buttons +- **Randomization**: One-click randomization for fair draft order +- **Validation**: Ensures all teams are included and valid +- **Status Protection**: Draft order can only be modified during `pre_draft` status + +### User Experience +- **Visual Feedback**: Numbered badges clearly show draft positions +- **Responsive Design**: Works on mobile and desktop +- **Conditional Display**: Draft order only shown when relevant (pre_draft/draft states) +- **Owner Information**: Shows which user owns each team in the draft order + +## Technical Details + +### Data Flow +1. Commissioner sets/randomizes order on settings page +2. Action handler validates and calls model functions +3. Model deletes old slots and creates new ones with correct order +4. League page loader fetches draft slots for display +5. Draft order box conditionally renders based on season status + +### Snake Draft Ready +The current implementation stores draft order as a simple integer sequence (1, 2, 3...). When implementing snake draft logic later, you can use this order to determine: +- Round 1: Order 1→N +- Round 2: Order N→1 +- Round 3: Order 1→N +- etc. + +## Future Enhancements +- Drag-and-drop reordering (currently uses up/down buttons) +- Draft order history/audit log +- Automatic draft order setting when all teams are filled +- Draft order lottery system with weighted probabilities diff --git a/app/models/draft-slot.ts b/app/models/draft-slot.ts new file mode 100644 index 0000000..db94ac7 --- /dev/null +++ b/app/models/draft-slot.ts @@ -0,0 +1,144 @@ +import { eq } from "drizzle-orm"; +import { database } from "~/database/context"; +import * as schema from "~/database/schema"; + +export type DraftSlot = typeof schema.draftSlots.$inferSelect; +export type NewDraftSlot = typeof schema.draftSlots.$inferInsert; + +export interface DraftSlotWithTeam extends DraftSlot { + team: { + id: string; + name: string; + ownerId: string | null; + }; +} + +/** + * Create a single draft slot + */ +export async function createDraftSlot(data: NewDraftSlot): Promise { + const db = database(); + const [draftSlot] = await db + .insert(schema.draftSlots) + .values(data) + .returning(); + return draftSlot; +} + +/** + * Create multiple draft slots at once + */ +export async function createManyDraftSlots( + slots: NewDraftSlot[] +): Promise { + const db = database(); + return await db.insert(schema.draftSlots).values(slots).returning(); +} + +/** + * Find all draft slots for a season, ordered by draft order + */ +export async function findDraftSlotsBySeasonId( + seasonId: string +): Promise { + const db = database(); + return await db.query.draftSlots.findMany({ + where: eq(schema.draftSlots.seasonId, seasonId), + orderBy: (draftSlots, { asc }) => [asc(draftSlots.draftOrder)], + with: { + team: { + columns: { + id: true, + name: true, + ownerId: true, + }, + }, + }, + }) as DraftSlotWithTeam[]; +} + +/** + * Find a draft slot by team ID + */ +export async function findDraftSlotByTeamId( + teamId: string +): Promise { + const db = database(); + return await db.query.draftSlots.findFirst({ + where: eq(schema.draftSlots.teamId, teamId), + }); +} + +/** + * Update a draft slot's order + */ +export async function updateDraftSlotOrder( + id: string, + draftOrder: number +): Promise { + const db = database(); + const [draftSlot] = await db + .update(schema.draftSlots) + .set({ draftOrder, updatedAt: new Date() }) + .where(eq(schema.draftSlots.id, id)) + .returning(); + return draftSlot; +} + +/** + * Delete all draft slots for a season + */ +export async function deleteDraftSlotsBySeasonId( + seasonId: string +): Promise { + const db = database(); + await db + .delete(schema.draftSlots) + .where(eq(schema.draftSlots.seasonId, seasonId)); +} + +/** + * Delete a specific draft slot + */ +export async function deleteDraftSlot(id: string): Promise { + const db = database(); + await db.delete(schema.draftSlots).where(eq(schema.draftSlots.id, id)); +} + +/** + * Set the draft order for all teams in a season + * This will delete existing slots and create new ones + */ +export async function setDraftOrder( + seasonId: string, + teamIds: string[] +): Promise { + // Delete existing draft slots for this season + await deleteDraftSlotsBySeasonId(seasonId); + + // Create new draft slots with the specified order + const slots = teamIds.map((teamId, index) => ({ + seasonId, + teamId, + draftOrder: index + 1, + })); + + return await createManyDraftSlots(slots); +} + +/** + * Randomize the draft order for a season + */ +export async function randomizeDraftOrder( + seasonId: string, + teamIds: string[] +): Promise { + // Shuffle the team IDs using Fisher-Yates algorithm + const shuffled = [...teamIds]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } + + return await setDraftOrder(seasonId, shuffled); +} diff --git a/app/models/index.ts b/app/models/index.ts index ca2dfe2..b4d8fad 100644 --- a/app/models/index.ts +++ b/app/models/index.ts @@ -11,3 +11,4 @@ export * from "./season-template"; export * from "./season-template-sport"; export * from "./season-sport"; export * from "./participant-result"; +export * from "./draft-slot"; diff --git a/app/routes/leagues/$leagueId.settings.tsx b/app/routes/leagues/$leagueId.settings.tsx index cd33f3c..7f02a44 100644 --- a/app/routes/leagues/$leagueId.settings.tsx +++ b/app/routes/leagues/$leagueId.settings.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { Form, Link, redirect } from "react-router"; +import { useState, useEffect } from "react"; +import { Form, Link, redirect, useNavigation } from "react-router"; import { getAuth } from "@clerk/react-router/server"; import { findLeagueById, updateLeague, deleteLeague } from "~/models/league"; import { isCommissioner } from "~/models/commissioner"; @@ -7,6 +7,7 @@ import { findCurrentSeasonWithSports } from "~/models/season"; import { findTeamsBySeasonId, createManyTeams, deleteTeam } from "~/models/team"; import { unlinkSportFromSeason, linkMultipleSportsToSeason } from "~/models/season-sport"; import { findAllSportsSeasons } from "~/models/sports-season"; +import { findDraftSlotsBySeasonId, setDraftOrder, randomizeDraftOrder } from "~/models/draft-slot"; import type { Route } from "./+types/$leagueId.settings"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; @@ -69,6 +70,7 @@ export async function loader(args: Route.LoaderArgs) { const season = await findCurrentSeasonWithSports(leagueId); const teams = season ? await findTeamsBySeasonId(season.id) : []; const allSportsSeasons = await findAllSportsSeasons(); + const draftSlots = season ? await findDraftSlotsBySeasonId(season.id) : []; // Count teams with owners const teamsWithOwners = teams.filter(team => team.ownerId !== null).length; @@ -80,6 +82,7 @@ export async function loader(args: Route.LoaderArgs) { teamCount: teams.length, teamsWithOwners, allSportsSeasons: allSportsSeasons as Array, + draftSlots, }; } @@ -150,6 +153,43 @@ export async function action(args: Route.ActionArgs) { return { success: true }; } + if (intent === "set-draft-order") { + if (season.status !== "pre_draft") { + return { error: "Cannot modify draft order after draft has started" }; + } + + const teams = await findTeamsBySeasonId(season.id); + const teamIds = formData.getAll("teamOrder") as string[]; + + // Validate that all teams are included + if (teamIds.length !== teams.length) { + return { error: "All teams must be included in the draft order" }; + } + + // Validate that all team IDs are valid + const validTeamIds = new Set(teams.map(t => t.id)); + for (const teamId of teamIds) { + if (!validTeamIds.has(teamId)) { + return { error: "Invalid team ID in draft order" }; + } + } + + await setDraftOrder(season.id, teamIds); + return { success: true, message: "Draft order updated successfully" }; + } + + if (intent === "randomize-draft-order") { + if (season.status !== "pre_draft") { + return { error: "Cannot modify draft order after draft has started" }; + } + + const teams = await findTeamsBySeasonId(season.id); + const teamIds = teams.map(t => t.id); + + await randomizeDraftOrder(season.id, teamIds); + return { success: true, message: "Draft order randomized successfully" }; + } + if (intent === "delete") { // Delete the league await deleteLeague(leagueId); @@ -230,14 +270,29 @@ export async function action(args: Route.ActionArgs) { } export default function LeagueSettings({ loaderData, actionData }: Route.ComponentProps) { - const { league, season, teamCount, teamsWithOwners, allSportsSeasons } = loaderData; + const { league, season, teams, teamCount, teamsWithOwners, allSportsSeasons, draftSlots } = loaderData; + const navigation = useNavigation(); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedSports, setSelectedSports] = useState>( new Set(season?.seasonSports?.map((s: any) => s.sportsSeason.id) || []) ); + const [draftOrderTeams, setDraftOrderTeams] = useState( + draftSlots.length > 0 + ? draftSlots.map((slot: any) => slot.teamId) + : teams.map((team: any) => team.id) + ); + + // Update draft order when loader data changes (after randomization) + useEffect(() => { + const newOrder = draftSlots.length > 0 + ? draftSlots.map((slot: any) => slot.teamId) + : teams.map((team: any) => team.id); + setDraftOrderTeams(newOrder); + }, [draftSlots, teams]); const canEditSports = season && season.status === "pre_draft"; const canEditTeamCount = season && season.status === "pre_draft"; + const canEditDraftOrder = season && season.status === "pre_draft"; const minTeamCount = Math.max(6, teamsWithOwners); const handleSportToggle = (sportId: string) => { @@ -255,6 +310,17 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone // The form submission will handle the actual deletion }; + const moveDraftSlot = (fromIndex: number, toIndex: number) => { + const newOrder = [...draftOrderTeams]; + const [movedTeam] = newOrder.splice(fromIndex, 1); + newOrder.splice(toIndex, 0, movedTeam); + setDraftOrderTeams(newOrder); + }; + + const getTeamById = (teamId: string) => { + return teams.find((t: any) => t.id === teamId); + }; + return (
@@ -415,6 +481,112 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone + {/* Draft Order Management */} + + + Draft Order + + {canEditDraftOrder + ? "Set the draft order for your league. Drag teams to reorder or randomize." + : "Draft order cannot be changed after the draft has started"} + + + +
+ {draftSlots.length === 0 && canEditDraftOrder && ( +
+

+ Note: Draft order has not been set yet. Use the form below to set the order manually or click "Randomize" to generate a random order. +

+
+ )} + +
+ + +
+
+ {draftOrderTeams.map((teamId, index) => { + const team = getTeamById(teamId); + if (!team) return null; + + return ( +
+ +
+ {index + 1} +
+
+

{team.name}

+
+ {canEditDraftOrder && ( +
+ + +
+ )} +
+ ); + })} +
+
+ + {canEditDraftOrder && ( +
+ +
+ )} + + {actionData?.success && actionData?.message && ( +
+ {actionData.message} +
+ )} + + {actionData?.error && ( +
+ {actionData.error} +
+ )} +
+ + {canEditDraftOrder && ( +
+ + +
+ )} +
+
+
+ {/* Danger Zone */} diff --git a/app/routes/leagues/$leagueId.tsx b/app/routes/leagues/$leagueId.tsx index 73c3cdf..103669b 100644 --- a/app/routes/leagues/$leagueId.tsx +++ b/app/routes/leagues/$leagueId.tsx @@ -10,6 +10,7 @@ import { isUserLeagueMember, isCommissioner, findUserByClerkId, + findDraftSlotsBySeasonId, } from "~/models"; import type { Route } from "./+types/$leagueId"; import { Button } from "~/components/ui/button"; @@ -66,6 +67,11 @@ export async function loader(args: Route.LoaderArgs) { // Fetch teams for current season const teams = season ? await findTeamsBySeasonId(season.id) : []; + // Fetch draft slots if season is in pre_draft or draft status + const draftSlots = season && (season.status === "pre_draft" || season.status === "draft") + ? await findDraftSlotsBySeasonId(season.id) + : []; + // Fetch user data for team owners const ownerIds = teams .map((t) => t.ownerId) @@ -114,6 +120,7 @@ export async function loader(args: Route.LoaderArgs) { ownerMap: Object.fromEntries(ownerMap), commissionerMap: Object.fromEntries(commissionerMap), availableTeamCount, + draftSlots, }; } @@ -128,6 +135,7 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) { ownerMap, commissionerMap, availableTeamCount, + draftSlots, } = loaderData; const [searchParams, setSearchParams] = useSearchParams(); const [copied, setCopied] = useState(false); @@ -280,6 +288,38 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) { {/* Right Column - 1/3 width on desktop */}
+ {/* Draft Order Display - Only show during pre_draft and draft */} + {season && (season.status === "pre_draft" || season.status === "draft") && draftSlots.length > 0 && ( + + + Draft Order + + {season.status === "pre_draft" ? "Upcoming draft order" : "Current draft order"} + + + +
+ {draftSlots.map((slot: any, index: number) => { + const ownerName = slot.team.ownerId ? ownerMap[slot.team.ownerId] : null; + return ( +
+
+ {index + 1} +
+
+

{slot.team.name}

+ {ownerName && ( +

{ownerName}

+ )} +
+
+ ); + })} +
+
+
+ )} + League Info diff --git a/database/schema.ts b/database/schema.ts index 456d6ba..3ccdcca 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -85,6 +85,19 @@ export const teams = pgTable("teams", { 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 commissioners = pgTable("commissioners", { id: uuid("id").primaryKey().defaultRandom(), leagueId: uuid("league_id") @@ -245,3 +258,25 @@ export const participantResultsRelations = relations(participantResults, ({ one 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], + }), +})); diff --git a/drizzle/0013_pink_the_order.sql b/drizzle/0013_pink_the_order.sql new file mode 100644 index 0000000..ee87ba7 --- /dev/null +++ b/drizzle/0013_pink_the_order.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS "draft_slots" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "season_id" uuid NOT NULL, + "team_id" uuid NOT NULL, + "draft_order" integer NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "draft_slots" ADD CONSTRAINT "draft_slots_season_id_seasons_id_fk" FOREIGN KEY ("season_id") REFERENCES "public"."seasons"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "draft_slots" ADD CONSTRAINT "draft_slots_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/drizzle/meta/0013_snapshot.json b/drizzle/meta/0013_snapshot.json new file mode 100644 index 0000000..d2f94fe --- /dev/null +++ b/drizzle/meta/0013_snapshot.json @@ -0,0 +1,1038 @@ +{ + "id": "38c7d12b-ab73-49e1-a1ca-a0722a7ec5ce", + "prevId": "18540fa4-0d1d-4b45-914b-4bf80ad5dadd", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.commissioners": { + "name": "commissioners", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "commissioners_league_id_leagues_id_fk": { + "name": "commissioners_league_id_leagues_id_fk", + "tableFrom": "commissioners", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_slots": { + "name": "draft_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "draft_order": { + "name": "draft_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_slots_season_id_seasons_id_fk": { + "name": "draft_slots_season_id_seasons_id_fk", + "tableFrom": "draft_slots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_slots_team_id_teams_id_fk": { + "name": "draft_slots_team_id_teams_id_fk", + "tableFrom": "draft_slots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.leagues": { + "name": "leagues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "current_season_id": { + "name": "current_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_results": { + "name": "participant_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "final_position": { + "name": "final_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "points_awarded": { + "name": "points_awarded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qualifying_points": { + "name": "qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_results_participant_id_participants_id_fk": { + "name": "participant_results_participant_id_participants_id_fk", + "tableFrom": "participant_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participants": { + "name": "participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "short_name": { + "name": "short_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participants_sports_season_id_sports_seasons_id_fk": { + "name": "participants_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participants", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_sports": { + "name": "season_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_sports_season_id_seasons_id_fk": { + "name": "season_sports_season_id_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_template_sports": { + "name": "season_template_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_template_sports_template_id_season_templates_id_fk": { + "name": "season_template_sports_template_id_season_templates_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_template_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_template_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_templates": { + "name": "season_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seasons": { + "name": "seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pre_draft'" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "flex_spots": { + "name": "flex_spots", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "invite_code": { + "name": "invite_code", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "seasons_league_id_leagues_id_fk": { + "name": "seasons_league_id_leagues_id_fk", + "tableFrom": "seasons", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "seasons_template_id_season_templates_id_fk": { + "name": "seasons_template_id_season_templates_id_fk", + "tableFrom": "seasons", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "seasons_invite_code_unique": { + "name": "seasons_invite_code_unique", + "nullsNotDistinct": false, + "columns": [ + "invite_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports": { + "name": "sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "sport_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_url": { + "name": "icon_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sports_slug_unique": { + "name": "sports_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports_seasons": { + "name": "sports_seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sport_id": { + "name": "sport_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_date": { + "name": "end_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "sports_season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'upcoming'" + }, + "scoring_type": { + "name": "scoring_type", + "type": "scoring_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sports_seasons_sport_id_sports_id_fk": { + "name": "sports_seasons_sport_id_sports_id_fk", + "tableFrom": "sports_seasons", + "tableTo": "sports", + "columnsFrom": [ + "sport_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_season_id_seasons_id_fk": { + "name": "teams_season_id_seasons_id_fk", + "tableFrom": "teams", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "clerk_id": { + "name": "clerk_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "nullsNotDistinct": false, + "columns": [ + "clerk_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.scoring_type": { + "name": "scoring_type", + "schema": "public", + "values": [ + "playoffs", + "regular_season", + "majors" + ] + }, + "public.season_status": { + "name": "season_status", + "schema": "public", + "values": [ + "pre_draft", + "draft", + "active", + "completed" + ] + }, + "public.sport_type": { + "name": "sport_type", + "schema": "public", + "values": [ + "team", + "individual" + ] + }, + "public.sports_season_status": { + "name": "sports_season_status", + "schema": "public", + "values": [ + "upcoming", + "active", + "completed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index b36358c..4713c74 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1760504208398, "tag": "0012_yielding_phil_sheldon", "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1760543654915, + "tag": "0013_pink_the_order", + "breakpoints": true } ] } \ No newline at end of file