diff --git a/app/models/league.ts b/app/models/league.ts index 33254d0..86c65f4 100644 --- a/app/models/league.ts +++ b/app/models/league.ts @@ -1,4 +1,4 @@ -import { eq, desc } from "drizzle-orm"; +import { eq, desc, and } from "drizzle-orm"; import { database } from "~/database/context"; import * as schema from "~/database/schema"; @@ -88,3 +88,30 @@ export async function findLeaguesWithActiveSeasonsByUserId( return leagues; } + +/** + * Check if a user is a member of a league (has a team in current season) + */ +export async function isUserLeagueMember( + leagueId: string, + userId: string +): Promise { + const db = database(); + + const league = await db.query.leagues.findFirst({ + where: eq(schema.leagues.id, leagueId), + }); + + if (!league || !league.currentSeasonId) { + return false; + } + + const team = await db.query.teams.findFirst({ + where: and( + eq(schema.teams.seasonId, league.currentSeasonId), + eq(schema.teams.ownerId, userId) + ), + }); + + return !!team; +} diff --git a/app/models/season.ts b/app/models/season.ts index 2645217..5029529 100644 --- a/app/models/season.ts +++ b/app/models/season.ts @@ -27,9 +27,16 @@ export interface SeasonWithSportsSeasons extends Season { export async function createSeason(data: NewSeason): Promise { const db = database(); + + // Generate invite code if not provided + const seasonData = { + ...data, + inviteCode: data.inviteCode || generateInviteCode(), + }; + const [season] = await db .insert(schema.seasons) - .values(data) + .values(seasonData) .returning(); return season; } @@ -137,3 +144,41 @@ export async function findSeasonWithSportsSeasons( }, }) as SeasonWithSportsSeasons | undefined; } + +/** + * Generate a short, URL-safe invite code + * Uses base62 encoding (alphanumeric) for readability + */ +export function generateInviteCode(): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const length = 10; + let result = ''; + const randomValues = new Uint8Array(length); + crypto.getRandomValues(randomValues); + + for (let i = 0; i < length; i++) { + result += chars[randomValues[i] % chars.length]; + } + + return result; +} + +/** + * Find a season by its invite code + */ +export async function findSeasonByInviteCode( + inviteCode: string +): Promise { + const db = database(); + return await db.query.seasons.findFirst({ + where: eq(schema.seasons.inviteCode, inviteCode), + }); +} + +/** + * Regenerate invite code for a season + */ +export async function regenerateInviteCode(seasonId: string): Promise { + const newInviteCode = generateInviteCode(); + return await updateSeason(seasonId, { inviteCode: newInviteCode }); +} diff --git a/app/routes.ts b/app/routes.ts index d2e8900..6625689 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -2,6 +2,7 @@ import { type RouteConfig, index, route } from "@react-router/dev/routes"; export default [ index("routes/home.tsx"), + route("i/:inviteCode", "routes/i.$inviteCode.tsx"), route("leagues/new", "routes/leagues/new.tsx"), route("leagues/:leagueId", "routes/leagues/$leagueId.tsx"), route("leagues/:leagueId/settings", "routes/leagues/$leagueId.settings.tsx"), diff --git a/app/routes/i.$inviteCode.tsx b/app/routes/i.$inviteCode.tsx new file mode 100644 index 0000000..b30a522 --- /dev/null +++ b/app/routes/i.$inviteCode.tsx @@ -0,0 +1,160 @@ +import { Form, redirect } from "react-router"; +import { getAuth } from "@clerk/react-router/server"; +import { SignInButton } from "@clerk/react-router"; +import type { Route } from "./+types/i.$inviteCode"; +import { + findSeasonByInviteCode, + findLeagueById, + findCommissionersByLeagueId, + findAvailableTeams, + assignTeamOwner, + isUserLeagueMember, +} from "~/models"; +import { Button } from "~/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "~/components/ui/card"; + +export async function loader(args: Route.LoaderArgs) { + const { params } = args; + const { inviteCode } = params; + const { userId } = await getAuth(args); + + // Find season by invite code + const season = await findSeasonByInviteCode(inviteCode); + + if (!season) { + throw new Response("Invalid invite code", { status: 404 }); + } + + // Find the league + const league = await findLeagueById(season.leagueId); + + if (!league) { + throw new Response("League not found", { status: 404 }); + } + + // Find commissioners to get the commissioner's name + const commissioners = await findCommissionersByLeagueId(league.id); + const firstCommissioner = commissioners[0]; + + // Check if user is already a member + let isAlreadyMember = false; + if (userId) { + isAlreadyMember = await isUserLeagueMember(league.id, userId); + } + + return { + league, + season, + inviteCode, + firstCommissioner, + isAlreadyMember, + isLoggedIn: !!userId, + }; +} + +export async function action(args: Route.ActionArgs) { + const { params } = args; + const { inviteCode } = params; + const { userId } = await getAuth(args); + + if (!userId) { + throw new Response("You must be logged in to join a league", { status: 401 }); + } + + // Find season by invite code + const season = await findSeasonByInviteCode(inviteCode); + + if (!season) { + throw new Response("Invalid invite code", { status: 404 }); + } + + // Check if user is already a member + const isAlreadyMember = await isUserLeagueMember(season.leagueId, userId); + + if (isAlreadyMember) { + // Already a member, redirect to league page + return redirect(`/leagues/${season.leagueId}`); + } + + // Find an available team + const availableTeams = await findAvailableTeams(season.id); + + if (availableTeams.length === 0) { + throw new Response("No available teams in this league", { status: 400 }); + } + + // Assign the first available team to the user + await assignTeamOwner(availableTeams[0].id, userId); + + // Redirect to league page + return redirect(`/leagues/${season.leagueId}?joined=true`); +} + +export default function InvitePage({ loaderData }: Route.ComponentProps) { + const { league, firstCommissioner, isAlreadyMember, isLoggedIn } = loaderData; + + return ( +
+
+ + + You're Invited! + + {firstCommissioner ? "A commissioner" : "Someone"} has invited you to join{" "} + {league.name} + + + +
+

League Details

+
+

+ League: {league.name} +

+

+ Created:{" "} + {new Date(league.createdAt).toLocaleDateString()} +

+
+
+ + {isAlreadyMember ? ( +
+

+ You're already a member of this league! +

+ +
+ ) : isLoggedIn ? ( +
+

+ Click the button below to join this league and claim your team. +

+ +
+ ) : ( +
+

+ You'll need to sign in or create an account to join this league. +

+ + + +
+ )} +
+
+
+
+ ); +} diff --git a/app/routes/leagues/$leagueId.tsx b/app/routes/leagues/$leagueId.tsx index e56525e..22a3242 100644 --- a/app/routes/leagues/$leagueId.tsx +++ b/app/routes/leagues/$leagueId.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { Link, useSearchParams } from "react-router"; import { getAuth } from "@clerk/react-router/server"; import { toast } from "sonner"; @@ -7,6 +7,8 @@ import { findCurrentSeason, findTeamsBySeasonId, findCommissionersByLeagueId, + isUserLeagueMember, + isCommissioner, } from "~/models"; import type { Route } from "./+types/$leagueId"; import { Button } from "~/components/ui/button"; @@ -33,14 +35,27 @@ export async function loader(args: Route.LoaderArgs) { // Fetch current season const season = await findCurrentSeason(leagueId); - // Fetch teams for current season - const teams = season ? await findTeamsBySeasonId(season.id) : []; - // Fetch commissioners const commissioners = await findCommissionersByLeagueId(leagueId); // Check if current user is a commissioner - const isUserCommissioner = commissioners.some(c => c.userId === userId); + const isUserCommissioner = userId ? await isCommissioner(leagueId, userId) : false; + + // Check if user is a member (has a team in current season) + const isUserMember = userId ? await isUserLeagueMember(leagueId, userId) : false; + + // Check access: user must be a commissioner, a member, or an admin + // If not logged in or not authorized, throw 403 + if (!userId) { + throw new Response("You must be logged in to view this league", { status: 401 }); + } + + if (!isUserCommissioner && !isUserMember) { + throw new Response("You do not have access to this league", { status: 403 }); + } + + // Fetch teams for current season + const teams = season ? await findTeamsBySeasonId(season.id) : []; return { league, @@ -55,15 +70,33 @@ export async function loader(args: Route.LoaderArgs) { export default function LeagueHome({ loaderData }: Route.ComponentProps) { const { league, season, teams, commissioners, currentUserId, isUserCommissioner } = loaderData; const [searchParams, setSearchParams] = useSearchParams(); + const [copied, setCopied] = useState(false); useEffect(() => { if (searchParams.get("updated") === "true") { toast.success("League updated successfully"); - // Remove the query parameter + setSearchParams({}); + } + if (searchParams.get("joined") === "true") { + toast.success("Welcome to the league! You've been assigned a team."); setSearchParams({}); } }, [searchParams, setSearchParams]); + const handleCopyInviteLink = async () => { + if (!season?.inviteCode) return; + + const inviteUrl = `${window.location.origin}/i/${season.inviteCode}`; + try { + await navigator.clipboard.writeText(inviteUrl); + setCopied(true); + toast.success("Invite link copied to clipboard!"); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + toast.error("Failed to copy invite link"); + } + }; + return (
@@ -154,6 +187,38 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) { + {isUserCommissioner && season && ( + + + Invite Link + + Share this link to invite people to join your league + + + +
+ e.currentTarget.select()} + /> + +
+

+ Anyone with this link can join your league +

+
+
+ )} + Commissioners diff --git a/database/schema.ts b/database/schema.ts index f591039..f7c358d 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -67,6 +67,7 @@ export const seasons = pgTable("seasons", { templateId: uuid("template_id") .references(() => seasonTemplates.id, { onDelete: "set null" }), flexSpots: integer("flex_spots").notNull().default(0), + inviteCode: varchar("invite_code", { length: 20 }).notNull().unique(), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), }); diff --git a/drizzle/0010_complex_the_stranger.sql b/drizzle/0010_complex_the_stranger.sql new file mode 100644 index 0000000..03bd7c6 --- /dev/null +++ b/drizzle/0010_complex_the_stranger.sql @@ -0,0 +1,10 @@ +-- Add invite_code column without NOT NULL constraint first +ALTER TABLE "seasons" ADD COLUMN "invite_code" varchar(20);--> statement-breakpoint + +-- Generate unique invite codes for existing seasons using UUID +-- Takes first 10 characters of UUID without dashes for a short, unique code +UPDATE "seasons" SET "invite_code" = substring(replace(gen_random_uuid()::text, '-', ''), 1, 10) WHERE "invite_code" IS NULL;--> statement-breakpoint + +-- Now make it NOT NULL and add unique constraint +ALTER TABLE "seasons" ALTER COLUMN "invite_code" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "seasons" ADD CONSTRAINT "seasons_invite_code_unique" UNIQUE("invite_code"); \ No newline at end of file diff --git a/drizzle/meta/0010_snapshot.json b/drizzle/meta/0010_snapshot.json new file mode 100644 index 0000000..b1233b5 --- /dev/null +++ b/drizzle/meta/0010_snapshot.json @@ -0,0 +1,947 @@ +{ + "id": "682234f1-8997-43d8-a434-46aead4ef32f", + "prevId": "d7b8574f-85c3-45ec-99cb-a49e2bec7e18", + "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.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 + }, + "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 + }, + "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 c65fbf4..66761c1 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1760392647508, "tag": "0009_fuzzy_korvac", "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1760469105799, + "tag": "0010_complex_the_stranger", + "breakpoints": true } ] } \ No newline at end of file