feat: add invite code system for league seasons with member access control
This commit is contained in:
parent
0999e1c980
commit
8c65ac9590
9 changed files with 1271 additions and 8 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { eq, desc } from "drizzle-orm";
|
import { eq, desc, and } from "drizzle-orm";
|
||||||
import { database } from "~/database/context";
|
import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
|
|
||||||
|
|
@ -88,3 +88,30 @@ export async function findLeaguesWithActiveSeasonsByUserId(
|
||||||
|
|
||||||
return leagues;
|
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<boolean> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,16 @@ export interface SeasonWithSportsSeasons extends Season {
|
||||||
|
|
||||||
export async function createSeason(data: NewSeason): Promise<Season> {
|
export async function createSeason(data: NewSeason): Promise<Season> {
|
||||||
const db = database();
|
const db = database();
|
||||||
|
|
||||||
|
// Generate invite code if not provided
|
||||||
|
const seasonData = {
|
||||||
|
...data,
|
||||||
|
inviteCode: data.inviteCode || generateInviteCode(),
|
||||||
|
};
|
||||||
|
|
||||||
const [season] = await db
|
const [season] = await db
|
||||||
.insert(schema.seasons)
|
.insert(schema.seasons)
|
||||||
.values(data)
|
.values(seasonData)
|
||||||
.returning();
|
.returning();
|
||||||
return season;
|
return season;
|
||||||
}
|
}
|
||||||
|
|
@ -137,3 +144,41 @@ export async function findSeasonWithSportsSeasons(
|
||||||
},
|
},
|
||||||
}) as SeasonWithSportsSeasons | undefined;
|
}) 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<Season | undefined> {
|
||||||
|
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<Season> {
|
||||||
|
const newInviteCode = generateInviteCode();
|
||||||
|
return await updateSeason(seasonId, { inviteCode: newInviteCode });
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { type RouteConfig, index, route } from "@react-router/dev/routes";
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
index("routes/home.tsx"),
|
index("routes/home.tsx"),
|
||||||
|
route("i/:inviteCode", "routes/i.$inviteCode.tsx"),
|
||||||
route("leagues/new", "routes/leagues/new.tsx"),
|
route("leagues/new", "routes/leagues/new.tsx"),
|
||||||
route("leagues/:leagueId", "routes/leagues/$leagueId.tsx"),
|
route("leagues/:leagueId", "routes/leagues/$leagueId.tsx"),
|
||||||
route("leagues/:leagueId/settings", "routes/leagues/$leagueId.settings.tsx"),
|
route("leagues/:leagueId/settings", "routes/leagues/$leagueId.settings.tsx"),
|
||||||
|
|
|
||||||
160
app/routes/i.$inviteCode.tsx
Normal file
160
app/routes/i.$inviteCode.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<div className="container mx-auto py-16 px-4">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-3xl">You're Invited!</CardTitle>
|
||||||
|
<CardDescription className="text-base">
|
||||||
|
{firstCommissioner ? "A commissioner" : "Someone"} has invited you to join{" "}
|
||||||
|
<span className="font-semibold">{league.name}</span>
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="font-semibold text-lg">League Details</h3>
|
||||||
|
<div className="space-y-1 text-sm text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
<span className="font-medium text-foreground">League:</span> {league.name}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-medium text-foreground">Created:</span>{" "}
|
||||||
|
{new Date(league.createdAt).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isAlreadyMember ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
You're already a member of this league!
|
||||||
|
</p>
|
||||||
|
<Button asChild className="w-full">
|
||||||
|
<a href={`/leagues/${league.id}`}>Go to League</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : isLoggedIn ? (
|
||||||
|
<Form method="post" className="space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Click the button below to join this league and claim your team.
|
||||||
|
</p>
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Join League
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
You'll need to sign in or create an account to join this league.
|
||||||
|
</p>
|
||||||
|
<SignInButton mode="modal" forceRedirectUrl={`/i/${loaderData.inviteCode}`}>
|
||||||
|
<Button className="w-full">Sign In to Join</Button>
|
||||||
|
</SignInButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useSearchParams } from "react-router";
|
import { Link, useSearchParams } from "react-router";
|
||||||
import { getAuth } from "@clerk/react-router/server";
|
import { getAuth } from "@clerk/react-router/server";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
@ -7,6 +7,8 @@ import {
|
||||||
findCurrentSeason,
|
findCurrentSeason,
|
||||||
findTeamsBySeasonId,
|
findTeamsBySeasonId,
|
||||||
findCommissionersByLeagueId,
|
findCommissionersByLeagueId,
|
||||||
|
isUserLeagueMember,
|
||||||
|
isCommissioner,
|
||||||
} from "~/models";
|
} from "~/models";
|
||||||
import type { Route } from "./+types/$leagueId";
|
import type { Route } from "./+types/$leagueId";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
|
|
@ -33,14 +35,27 @@ export async function loader(args: Route.LoaderArgs) {
|
||||||
// Fetch current season
|
// Fetch current season
|
||||||
const season = await findCurrentSeason(leagueId);
|
const season = await findCurrentSeason(leagueId);
|
||||||
|
|
||||||
// Fetch teams for current season
|
|
||||||
const teams = season ? await findTeamsBySeasonId(season.id) : [];
|
|
||||||
|
|
||||||
// Fetch commissioners
|
// Fetch commissioners
|
||||||
const commissioners = await findCommissionersByLeagueId(leagueId);
|
const commissioners = await findCommissionersByLeagueId(leagueId);
|
||||||
|
|
||||||
// Check if current user is a commissioner
|
// 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 {
|
return {
|
||||||
league,
|
league,
|
||||||
|
|
@ -55,15 +70,33 @@ export async function loader(args: Route.LoaderArgs) {
|
||||||
export default function LeagueHome({ loaderData }: Route.ComponentProps) {
|
export default function LeagueHome({ loaderData }: Route.ComponentProps) {
|
||||||
const { league, season, teams, commissioners, currentUserId, isUserCommissioner } = loaderData;
|
const { league, season, teams, commissioners, currentUserId, isUserCommissioner } = loaderData;
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchParams.get("updated") === "true") {
|
if (searchParams.get("updated") === "true") {
|
||||||
toast.success("League updated successfully");
|
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({});
|
setSearchParams({});
|
||||||
}
|
}
|
||||||
}, [searchParams, 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 (
|
return (
|
||||||
<div className="container mx-auto py-8 px-4">
|
<div className="container mx-auto py-8 px-4">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
|
|
@ -154,6 +187,38 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{isUserCommissioner && season && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Invite Link</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Share this link to invite people to join your league
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readOnly
|
||||||
|
value={`${typeof window !== "undefined" ? window.location.origin : ""}/i/${season.inviteCode}`}
|
||||||
|
className="flex-1 px-3 py-2 text-sm border rounded-md bg-muted"
|
||||||
|
onClick={(e) => e.currentTarget.select()}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={handleCopyInviteLink}
|
||||||
|
variant={copied ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{copied ? "Copied!" : "Copy"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Anyone with this link can join your league
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Commissioners</CardTitle>
|
<CardTitle>Commissioners</CardTitle>
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ export const seasons = pgTable("seasons", {
|
||||||
templateId: uuid("template_id")
|
templateId: uuid("template_id")
|
||||||
.references(() => seasonTemplates.id, { onDelete: "set null" }),
|
.references(() => seasonTemplates.id, { onDelete: "set null" }),
|
||||||
flexSpots: integer("flex_spots").notNull().default(0),
|
flexSpots: integer("flex_spots").notNull().default(0),
|
||||||
|
inviteCode: varchar("invite_code", { length: 20 }).notNull().unique(),
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
10
drizzle/0010_complex_the_stranger.sql
Normal file
10
drizzle/0010_complex_the_stranger.sql
Normal file
|
|
@ -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");
|
||||||
947
drizzle/meta/0010_snapshot.json
Normal file
947
drizzle/meta/0010_snapshot.json
Normal file
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -71,6 +71,13 @@
|
||||||
"when": 1760392647508,
|
"when": 1760392647508,
|
||||||
"tag": "0009_fuzzy_korvac",
|
"tag": "0009_fuzzy_korvac",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 10,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1760469105799,
|
||||||
|
"tag": "0010_complex_the_stranger",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue