feat: add icon support for sports with file upload and active seasons display
This commit is contained in:
parent
6814a82b76
commit
c343521706
10 changed files with 1176 additions and 2 deletions
|
|
@ -6,6 +6,10 @@ export type Sport = typeof schema.sports.$inferSelect;
|
||||||
export type NewSport = typeof schema.sports.$inferInsert;
|
export type NewSport = typeof schema.sports.$inferInsert;
|
||||||
export type SportType = "team" | "individual";
|
export type SportType = "team" | "individual";
|
||||||
|
|
||||||
|
export type SportWithActiveSeasons = Sport & {
|
||||||
|
activeSeasons: Array<{ id: string; name: string; year: number }>;
|
||||||
|
};
|
||||||
|
|
||||||
export async function createSport(data: NewSport): Promise<Sport> {
|
export async function createSport(data: NewSport): Promise<Sport> {
|
||||||
const db = database();
|
const db = database();
|
||||||
const [sport] = await db
|
const [sport] = await db
|
||||||
|
|
@ -36,6 +40,32 @@ export async function findAllSports(): Promise<Sport[]> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function findAllSportsWithActiveSeasons(): Promise<SportWithActiveSeasons[]> {
|
||||||
|
const db = database();
|
||||||
|
const sports = await db.query.sports.findMany({
|
||||||
|
orderBy: (sports, { asc }) => [asc(sports.name)],
|
||||||
|
with: {
|
||||||
|
sportsSeasons: {
|
||||||
|
where: (sportsSeasons, { eq, or }) =>
|
||||||
|
or(
|
||||||
|
eq(sportsSeasons.status, "active"),
|
||||||
|
eq(sportsSeasons.status, "upcoming")
|
||||||
|
),
|
||||||
|
orderBy: (sportsSeasons, { desc }) => [desc(sportsSeasons.year)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return sports.map((sport) => ({
|
||||||
|
...sport,
|
||||||
|
activeSeasons: sport.sportsSeasons.map((season) => ({
|
||||||
|
id: season.id,
|
||||||
|
name: season.name,
|
||||||
|
year: season.year,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export async function findSportsByType(type: SportType): Promise<Sport[]> {
|
export async function findSportsByType(type: SportType): Promise<Sport[]> {
|
||||||
const db = database();
|
const db = database();
|
||||||
return await db.query.sports.findMany({
|
return await db.query.sports.findMany({
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,21 @@ export async function findSportsSeasonsBySportId(sportId: string): Promise<Sport
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function findActiveSportsSeasonsBySportId(sportId: string): Promise<SportsSeason[]> {
|
||||||
|
const db = database();
|
||||||
|
return await db.query.sportsSeasons.findMany({
|
||||||
|
where: (sportsSeasons, { eq, and }) =>
|
||||||
|
and(
|
||||||
|
eq(sportsSeasons.sportId, sportId),
|
||||||
|
eq(sportsSeasons.status, "active")
|
||||||
|
),
|
||||||
|
orderBy: (sportsSeasons, { desc }) => [desc(sportsSeasons.year)],
|
||||||
|
with: {
|
||||||
|
sport: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function findSportsSeasonsByYear(year: number): Promise<SportsSeason[]> {
|
export async function findSportsSeasonsByYear(year: number): Promise<SportsSeason[]> {
|
||||||
const db = database();
|
const db = database();
|
||||||
return await db.query.sportsSeasons.findMany({
|
return await db.query.sportsSeasons.findMany({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { Form, Link, redirect } from "react-router";
|
import { Form, Link, redirect } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports.$id";
|
import type { Route } from "./+types/admin.sports.$id";
|
||||||
import { findSportById, updateSport } from "~/models/sport";
|
import { findSportById, updateSport } from "~/models/sport";
|
||||||
|
import { writeFile, mkdir } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
|
|
@ -36,6 +38,8 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
const type = formData.get("type");
|
const type = formData.get("type");
|
||||||
const slug = formData.get("slug");
|
const slug = formData.get("slug");
|
||||||
const description = formData.get("description");
|
const description = formData.get("description");
|
||||||
|
const iconFile = formData.get("iconFile");
|
||||||
|
const keepExistingIcon = formData.get("keepExistingIcon");
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (typeof name !== "string" || !name.trim()) {
|
if (typeof name !== "string" || !name.trim()) {
|
||||||
|
|
@ -55,12 +59,49 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
return { error: "Slug must contain only lowercase letters, numbers, and hyphens" };
|
return { error: "Slug must contain only lowercase letters, numbers, and hyphens" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get existing sport to preserve icon if needed
|
||||||
|
const existingSport = await findSportById(params.id);
|
||||||
|
let iconUrl: string | null | undefined = existingSport?.iconUrl;
|
||||||
|
|
||||||
|
// Handle file upload
|
||||||
|
if (iconFile instanceof File && iconFile.size > 0) {
|
||||||
|
const fileExtension = iconFile.name.split(".").pop()?.toLowerCase();
|
||||||
|
|
||||||
|
// Validate file type
|
||||||
|
if (!fileExtension || !["svg", "png", "jpg", "jpeg"].includes(fileExtension)) {
|
||||||
|
return { error: "Icon must be an SVG, PNG, or JPG file" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create filename using slug
|
||||||
|
const filename = `${slug.trim()}.${fileExtension}`;
|
||||||
|
iconUrl = filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure directory exists
|
||||||
|
const iconsDir = join(process.cwd(), "public", "sports-icons");
|
||||||
|
await mkdir(iconsDir, { recursive: true });
|
||||||
|
|
||||||
|
// Save file
|
||||||
|
const filepath = join(iconsDir, filename);
|
||||||
|
const bytes = await iconFile.arrayBuffer();
|
||||||
|
const buffer = Buffer.from(bytes);
|
||||||
|
await writeFile(filepath, buffer);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving icon file:", error);
|
||||||
|
return { error: "Failed to save icon file" };
|
||||||
|
}
|
||||||
|
} else if (keepExistingIcon !== "true") {
|
||||||
|
// If no file uploaded and not keeping existing, set to null
|
||||||
|
iconUrl = null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateSport(params.id, {
|
await updateSport(params.id, {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
type,
|
type,
|
||||||
slug: slug.trim(),
|
slug: slug.trim(),
|
||||||
description: typeof description === "string" ? description.trim() : null,
|
description: typeof description === "string" ? description.trim() : null,
|
||||||
|
iconUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
return redirect("/admin/sports");
|
return redirect("/admin/sports");
|
||||||
|
|
@ -147,6 +188,36 @@ export default function EditSport({ loaderData, actionData }: Route.ComponentPro
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="iconFile">Icon File (Optional)</Label>
|
||||||
|
{sport.iconUrl && (
|
||||||
|
<div className="mb-2 p-3 bg-muted rounded-md">
|
||||||
|
<p className="text-sm font-medium mb-1">Current icon:</p>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<img
|
||||||
|
src={`/sports-icons/${sport.iconUrl}`}
|
||||||
|
alt={sport.name}
|
||||||
|
className="w-12 h-12 object-contain"
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.style.display = 'none';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<code className="text-xs bg-background px-2 py-1 rounded">{sport.iconUrl}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Input
|
||||||
|
id="iconFile"
|
||||||
|
name="iconFile"
|
||||||
|
type="file"
|
||||||
|
accept=".svg,.png,.jpg,.jpeg"
|
||||||
|
/>
|
||||||
|
<input type="hidden" name="keepExistingIcon" value={sport.iconUrl ? "true" : "false"} />
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Upload a new SVG, PNG, or JPG file to replace the current icon. Leave empty to keep the existing icon.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{actionData?.error && (
|
{actionData?.error && (
|
||||||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||||
{actionData.error}
|
{actionData.error}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { Form, Link, redirect } from "react-router";
|
import { Form, Link, redirect } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports.new";
|
import type { Route } from "./+types/admin.sports.new";
|
||||||
import { createSport } from "~/models/sport";
|
import { createSport } from "~/models/sport";
|
||||||
|
import { writeFile, mkdir } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
|
|
@ -26,6 +28,7 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
const type = formData.get("type");
|
const type = formData.get("type");
|
||||||
const slug = formData.get("slug");
|
const slug = formData.get("slug");
|
||||||
const description = formData.get("description");
|
const description = formData.get("description");
|
||||||
|
const iconFile = formData.get("iconFile");
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (typeof name !== "string" || !name.trim()) {
|
if (typeof name !== "string" || !name.trim()) {
|
||||||
|
|
@ -45,12 +48,44 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
return { error: "Slug must contain only lowercase letters, numbers, and hyphens" };
|
return { error: "Slug must contain only lowercase letters, numbers, and hyphens" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let iconUrl: string | null = null;
|
||||||
|
|
||||||
|
// Handle file upload
|
||||||
|
if (iconFile instanceof File && iconFile.size > 0) {
|
||||||
|
const fileExtension = iconFile.name.split(".").pop()?.toLowerCase();
|
||||||
|
|
||||||
|
// Validate file type
|
||||||
|
if (!fileExtension || !["svg", "png", "jpg", "jpeg"].includes(fileExtension)) {
|
||||||
|
return { error: "Icon must be an SVG, PNG, or JPG file" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create filename using slug
|
||||||
|
const filename = `${slug.trim()}.${fileExtension}`;
|
||||||
|
iconUrl = filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure directory exists
|
||||||
|
const iconsDir = join(process.cwd(), "public", "sports-icons");
|
||||||
|
await mkdir(iconsDir, { recursive: true });
|
||||||
|
|
||||||
|
// Save file
|
||||||
|
const filepath = join(iconsDir, filename);
|
||||||
|
const bytes = await iconFile.arrayBuffer();
|
||||||
|
const buffer = Buffer.from(bytes);
|
||||||
|
await writeFile(filepath, buffer);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving icon file:", error);
|
||||||
|
return { error: "Failed to save icon file" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createSport({
|
await createSport({
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
type,
|
type,
|
||||||
slug: slug.trim(),
|
slug: slug.trim(),
|
||||||
description: typeof description === "string" ? description.trim() : null,
|
description: typeof description === "string" ? description.trim() : null,
|
||||||
|
iconUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
return redirect("/admin/sports");
|
return redirect("/admin/sports");
|
||||||
|
|
@ -132,6 +167,19 @@ export default function NewSport({ actionData }: Route.ComponentProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="iconFile">Icon File (Optional)</Label>
|
||||||
|
<Input
|
||||||
|
id="iconFile"
|
||||||
|
name="iconFile"
|
||||||
|
type="file"
|
||||||
|
accept=".svg,.png,.jpg,.jpeg"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Upload an SVG, PNG, or JPG file. The file will be saved as <code className="bg-muted px-1 py-0.5 rounded">{`{slug}.{ext}`}</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{actionData?.error && (
|
{actionData?.error && (
|
||||||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||||
{actionData.error}
|
{actionData.error}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports";
|
import type { Route } from "./+types/admin.sports";
|
||||||
import { findAllSports } from "~/models/sport";
|
import { findAllSportsWithActiveSeasons } from "~/models/sport";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
@ -21,7 +21,7 @@ import {
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
|
||||||
export async function loader() {
|
export async function loader() {
|
||||||
const sports = await findAllSports();
|
const sports = await findAllSportsWithActiveSeasons();
|
||||||
return { sports };
|
return { sports };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,6 +74,7 @@ export default function AdminSports({ loaderData }: Route.ComponentProps) {
|
||||||
<TableHead>Name</TableHead>
|
<TableHead>Name</TableHead>
|
||||||
<TableHead>Type</TableHead>
|
<TableHead>Type</TableHead>
|
||||||
<TableHead>Slug</TableHead>
|
<TableHead>Slug</TableHead>
|
||||||
|
<TableHead>Current Seasons</TableHead>
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
|
|
@ -89,6 +90,25 @@ export default function AdminSports({ loaderData }: Route.ComponentProps) {
|
||||||
<TableCell className="text-muted-foreground">
|
<TableCell className="text-muted-foreground">
|
||||||
{sport.slug}
|
{sport.slug}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{sport.activeSeasons.length > 0 ? (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{sport.activeSeasons.map((season) => (
|
||||||
|
<Link
|
||||||
|
key={season.id}
|
||||||
|
to={`/admin/sports-seasons/${season.id}`}
|
||||||
|
className="text-sm text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{season.name} ({season.year})
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
No current seasons
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<Button variant="ghost" size="sm" asChild>
|
<Button variant="ghost" size="sm" asChild>
|
||||||
<Link to={`/admin/sports/${sport.id}`}>Edit</Link>
|
<Link to={`/admin/sports/${sport.id}`}>Edit</Link>
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ export const sports = pgTable("sports", {
|
||||||
type: sportTypeEnum("type").notNull(),
|
type: sportTypeEnum("type").notNull(),
|
||||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||||
description: text("description"),
|
description: text("description"),
|
||||||
|
iconUrl: varchar("icon_url", { length: 255 }), // Filename of icon in /public/sports-icons/
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
1
drizzle/0008_salty_trauma.sql
Normal file
1
drizzle/0008_salty_trauma.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE "sports" ADD COLUMN "icon_url" varchar(255);
|
||||||
947
drizzle/meta/0008_snapshot.json
Normal file
947
drizzle/meta/0008_snapshot.json
Normal file
|
|
@ -0,0 +1,947 @@
|
||||||
|
{
|
||||||
|
"id": "a87c9793-9d30-4cf6-b1f5-d3f6322128e2",
|
||||||
|
"prevId": "462e431e-4517-4a02-b4e9-80c82c1b2c9f",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"is_required": {
|
||||||
|
"name": "is_required",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 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
|
||||||
|
},
|
||||||
|
"is_required": {
|
||||||
|
"name": "is_required",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 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
|
||||||
|
},
|
||||||
|
"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": {},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -57,6 +57,13 @@
|
||||||
"when": 1760327817376,
|
"when": 1760327817376,
|
||||||
"tag": "0007_pink_nebula",
|
"tag": "0007_pink_nebula",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 8,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1760375514773,
|
||||||
|
"tag": "0008_salty_trauma",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
34
public/sports-icons/README.md
Normal file
34
public/sports-icons/README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Sports Icons Directory
|
||||||
|
|
||||||
|
This directory contains icon files for sports. Icons are referenced in the database by filename only.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Icons are automatically uploaded through the admin panel when creating or editing sports:
|
||||||
|
1. Go to Admin > Sports > Add/Edit Sport
|
||||||
|
2. Use the "Icon File" upload field to select an SVG, PNG, or JPG file
|
||||||
|
3. The file will be automatically saved as `{slug}.{extension}` in this directory
|
||||||
|
|
||||||
|
You can also manually place icon files here if needed.
|
||||||
|
|
||||||
|
## File Format
|
||||||
|
|
||||||
|
- **Preferred**: SVG files for scalability
|
||||||
|
- **Supported**: PNG files (recommend 512x512px or higher)
|
||||||
|
- **Naming**: Use the sport slug as the filename (e.g., `nfl.svg`, `golf-mens.svg`)
|
||||||
|
|
||||||
|
## Import/Export
|
||||||
|
|
||||||
|
When exporting sports data:
|
||||||
|
- This entire directory should be included in the export
|
||||||
|
- The export script will copy all files from this directory
|
||||||
|
|
||||||
|
When importing sports data:
|
||||||
|
- Extract the `sports-icons` folder to `/public/sports-icons/`
|
||||||
|
- The import script will update database references to match the imported files
|
||||||
|
|
||||||
|
## Access
|
||||||
|
|
||||||
|
Icons are served from `/sports-icons/{filename}` in the application.
|
||||||
|
|
||||||
|
Example: `/sports-icons/nfl.svg`
|
||||||
Loading…
Add table
Reference in a new issue