From c9481ff82c53ae65ddf2f02fb4207de90b6324d2 Mon Sep 17 00:00:00 2001 From: Brandon Pham Date: Sun, 9 Aug 2026 17:51:39 -0500 Subject: [PATCH 1/4] feat: own user data in a `users` table instead of Clerk metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clerk was acting as three things at once: the session provider, our user database, and the store for product data (Sleeper link, synced leagues) in `unsafeMetadata`. The last two are the parts that would be expensive to get back if we ever move off it, and the metadata had no Postgres copy at all. Clerk now owns sessions only. - Add a `users` table keyed by the *Clerk* user id, not a fresh uuid. All 18 user-referencing columns across 11 tables already store that string, so keeping it as the PK means a future auth swap is "issue our own JWTs with the same sub" rather than a renumbering of the whole database. - Move sleeperUsername / sleeperUserId / syncedLeagueIds out of `unsafeMetadata` into that table. Nothing writes to Clerk metadata anymore. - Add FKs on all 18 columns, `ON DELETE RESTRICT` — league history outlives an account, and a missed id in a future migration now errors instead of silently orphaning a forum post. Migration 0012 seeds placeholder rows from the data itself before adding the constraints, so it can't fail on an unseeded DB. - Replace `clerkClient.users.getUserList()` — an HTTP call to Clerk on the critical path of five request handlers — with a single SQL read. - Add `GET /api/user/me` as the client's source of truth; AuthGuard blocks on it (which is also what guarantees the row exists) instead of reading Clerk metadata, and `useInvalidateCurrentUser()` replaces `await user.reload()`. - Add scripts/backfill-users.mjs: idempotent Clerk -> Postgres mirror, usable both as the one-time backfill and as a recurring snapshot. Verified against the dev DB: both migrations apply, the backfill moved all 5 users' Sleeper links across, and the FKs reject both an orphaned insert and a delete of a referenced user. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 6 +- PLAYBOOK.md | 26 +- client/src/components/AccountModal.tsx | 13 +- client/src/components/auth/AuthGuard.tsx | 81 +- client/src/hooks/useCurrentUser.ts | 59 + server/drizzle/0011_sticky_shockwave.sql | 15 + server/drizzle/0012_green_valkyrie.sql | 53 + server/drizzle/meta/0011_snapshot.json | 2410 ++++++++++++++++++++ server/drizzle/meta/0012_snapshot.json | 2644 ++++++++++++++++++++++ server/drizzle/meta/_journal.json | 14 + server/package.json | 4 +- server/scripts/backfill-users.mjs | 161 ++ server/src/db/schema.ts | 136 +- server/src/routes/huddleRoutes.ts | 64 +- server/src/routes/userRoutes.ts | 93 +- server/src/services/usersService.ts | 162 ++ 16 files changed, 5799 insertions(+), 142 deletions(-) create mode 100644 client/src/hooks/useCurrentUser.ts create mode 100644 server/drizzle/0011_sticky_shockwave.sql create mode 100644 server/drizzle/0012_green_valkyrie.sql create mode 100644 server/drizzle/meta/0011_snapshot.json create mode 100644 server/drizzle/meta/0012_snapshot.json create mode 100644 server/scripts/backfill-users.mjs create mode 100644 server/src/services/usersService.ts diff --git a/CLAUDE.md b/CLAUDE.md index 671113d..cf16eb1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,8 @@ npm run db:generate --prefix server # generate a migration into server/drizzle/ npm run db:studio --prefix server # Drizzle Studio npm run db:migrate --prefix server # apply pending migrations to the dev DB (local .env) npm run db:migrate:prod --prefix server # apply pending migrations to prod (server/.env.production, gitignored) +npm run db:backfill-users --prefix server # mirror Clerk's user list into the `users` table (idempotent) +npm run db:backfill-users:prod --prefix server # same, against prod ``` There is no test runner configured (`client/src/setupTests.ts` is a leftover). Verify changes by running the app. @@ -55,7 +57,9 @@ Algorithms in `server/src/algorithms/` self-register via `registerAlgorithm()` ( ### Auth -Clerk everywhere. Server: `clerkMiddleware()` globally + `requireAuth` per route (`getAuth(req).userId`). Client: `AuthGuard` gates the app shell and hydrates Clerk `unsafeMetadata` into the Redux `auth` slice. After mutating metadata server-side, call `await user.reload()` client-side or Redux stays stale. +Clerk owns **sessions only**. Server: `clerkMiddleware()` globally + `requireAuth` per route (`getAuth(req).userId`). Client: `AuthGuard` gates the app shell and hydrates the Redux `auth` slice from `GET /api/user/me`. + +User profile data (email/username cache, Sleeper link, synced leagues) lives in our own `users` table, keyed by the Clerk user id — see PLAYBOOK's "Users and auth ownership" for the three rules that keep a future auth migration cheap. Two things that used to be true and no longer are: nothing reads Clerk `unsafeMetadata`, and `await user.reload()` is not how you refresh a profile (use `useInvalidateCurrentUser()`). ### State split diff --git a/PLAYBOOK.md b/PLAYBOOK.md index 9f0d06a..d193b5f 100644 --- a/PLAYBOOK.md +++ b/PLAYBOOK.md @@ -460,13 +460,31 @@ The provider pattern lives in `server/src/providers/`. To add a new platform: ## Auth & user state -User metadata flows: **Clerk `unsafeMetadata` → `AuthGuard` → Redux `auth` slice** +User profile flows: **`users` table → `GET /api/user/me` → `AuthGuard` → Redux `auth` slice** -- `AuthGuard` calls `setUser()` once on load and again whenever Clerk's `user` object updates (e.g. after `user.reload()`) -- After mutating metadata on the server, always call `await user.reload()` on the client to keep Clerk's cache fresh -- The `auth` slice holds: `user` (id, sleeperUsername, sleeperUserId, syncedLeagueIds), `selectedLeagueId`, `selectedYear` +- `AuthGuard` blocks render on `useCurrentUser()` (`client/src/hooks/useCurrentUser.ts`), then calls `setUser()`. It re-runs whenever that query is invalidated. +- After mutating the profile server-side, call `useInvalidateCurrentUser()` on the client. **Do not** use `await user.reload()` — that was the old Clerk-metadata cache-bust and no longer affects anything we read. +- The `auth` slice holds: `user` (id, username, email, sleeperUsername, sleeperUserId, syncedLeagueIds), `selectedLeagueId`, `selectedYear` - `selectedLeagueId` and `selectedYear` are persisted to `localStorage` (`huddle:selection`) by the store subscriber in `client/src/store/index.ts`, so the user's chosen league/season survives a refresh. +### Users and auth ownership + +Clerk owns **sessions only**. Everything else about a user is ours, in the `users` table. + +| Data | Owner | Notes | +|---|---|---| +| Session / JWT / sign-in UI | Clerk | `clerkMiddleware()` + `requireAuth`, `getToken()` client-side | +| `email`, `username` | Clerk, **cached** by us | Refreshed by `usersService.ensureUser` on a 24h TTL | +| `sleeperUsername`, `sleeperUserId`, `syncedLeagueIds` | **Us** | Formerly Clerk `unsafeMetadata`; migrated in `0012` | + +Three rules keep a future auth migration cheap — they exist because the alternative is a rewrite: + +1. **`users.id` is the Clerk user id, and stays that way.** All 18 user-referencing columns across 11 tables store that same string. Swapping providers later means issuing our own JWTs with the same `sub`, not renumbering the database. Never migrate these to fresh uuids. +2. **Never call Clerk for display names.** Use `usersService.getUserSummaries(ids)` — one SQL round-trip. `huddleRoutes` used to make a `clerkClient.users.getUserList()` HTTP call inside five request handlers; that's what this replaced. +3. **Never `DELETE` a `users` row.** Every FK is `ON DELETE RESTRICT` on purpose — forum posts, votes and survey answers are league history that must outlive an account. To delete a user, blank the identity fields and set `is_placeholder`. + +`scripts/backfill-users.mjs` (`npm run db:backfill-users --prefix server`) mirrors Clerk's user list into `users`. It's idempotent and safe to re-run on a schedule — identity fields are always refreshed, but Sleeper fields are only filled when ours are empty, so it can never roll back a user's later edits. Run it after any migration that adds a new user-referencing column, and add that column to the `USER_ID_COLUMNS` list at the top of the script. + --- ## Database changes diff --git a/client/src/components/AccountModal.tsx b/client/src/components/AccountModal.tsx index 8c08427..d5f4121 100644 --- a/client/src/components/AccountModal.tsx +++ b/client/src/components/AccountModal.tsx @@ -26,7 +26,7 @@ import { type ReactNode, } from "react"; import { Link } from "react-router-dom"; -import { UserProfile, useAuth, useUser } from "@clerk/clerk-react"; +import { UserProfile, useAuth } from "@clerk/clerk-react"; import { ChevronRight } from "lucide-react"; import { Plug, X } from "lucide-react"; import axios from "axios"; @@ -36,6 +36,7 @@ import { setSyncedLeagueIds, setSelectedLeague, } from "../store/slices/authSlice"; +import { useInvalidateCurrentUser } from "../hooks/useCurrentUser"; import { Button } from "./ui/button"; interface AccountModalContextValue { @@ -148,9 +149,9 @@ function AccountModal({ onClose, initialTab }: { onClose: () => void; initialTab function IntegrationsPage() { const { getToken } = useAuth(); - const { user } = useUser(); const dispatch = useAppDispatch(); const queryClient = useQueryClient(); + const invalidateCurrentUser = useInvalidateCurrentUser(); const sleeperUsername = useAppSelector( (state) => state.auth.user?.sleeperUsername, ); @@ -179,8 +180,8 @@ function IntegrationsPage() { setErrorMsg(""); try { await patchSleeperUsername(input.trim()); - // Reload Clerk user — AuthGuard's useEffect will re-hydrate Redux with fresh metadata - await user?.reload(); + // Refetch /api/user/me — AuthGuard's useEffect re-hydrates Redux from it. + await invalidateCurrentUser(); setInput(""); setStatus("success"); } catch (err: unknown) { @@ -202,8 +203,8 @@ function IntegrationsPage() { dispatch(setSyncedLeagueIds([])); dispatch(setSelectedLeague(null)); queryClient.removeQueries({ queryKey: ["sleeper-leagues-all"] }); - // Sync Clerk cache so AuthGuard re-hydrates with cleared metadata - await user?.reload(); + // Refetch /api/user/me so AuthGuard re-hydrates with the cleared link. + await invalidateCurrentUser(); setStatus("idle"); } catch (err: unknown) { setStatus("error"); diff --git a/client/src/components/auth/AuthGuard.tsx b/client/src/components/auth/AuthGuard.tsx index 2b19f29..515bb49 100644 --- a/client/src/components/auth/AuthGuard.tsx +++ b/client/src/components/auth/AuthGuard.tsx @@ -4,61 +4,61 @@ * Responsibilities: * 1. Wait for Clerk to finish loading the session (spinner while loading). * 2. Redirect to /sign-in if the user isn't signed in. - * 3. Mirror Clerk's user (including the Sleeper metadata we tuck into - * `unsafeMetadata`) into the Redux `auth` slice so the rest of the - * app can read it synchronously via `useAppSelector`. + * 3. Fetch the user's Huddle profile from `GET /api/user/me` and mirror it + * into the Redux `auth` slice so the rest of the app can read it + * synchronously via `useAppSelector`. * 4. Hold render until Redux is hydrated — otherwise the first paint * would fire queries with `sleeperUserId = null` and waste a * no-op TanStack Query round-trip. * - * Why `unsafeMetadata` and not a separate DB? - * Clerk lets us stash arbitrary JSON on the user record and read it back - * without an extra fetch. We use it for the Sleeper handle and the - * user's list of synced leagueIds — the data is small, user-owned, and - * non-sensitive. Anything bigger lives in our Postgres tables. + * Why our API and not Clerk's `unsafeMetadata`? + * It used to be Clerk metadata — small, user-owned JSON readable without an + * extra fetch. The cost was that the Sleeper link and synced-league list + * were the only product state we couldn't recover without a third-party + * export. They now live in our `users` table. Clerk still owns the session; + * it no longer owns any product data. * - * IMPORTANT: After mutating `unsafeMetadata` on the server, always call - * `await user.reload()` on the client to invalidate Clerk's cache — - * otherwise the next `useUser()` read returns stale data and this guard - * dispatches the old values into Redux. + * This is also the request that *creates* the user's row (see the route's + * comment), so it deliberately blocks render: everything downstream assumes a + * `users` row exists, and the foreign keys enforce it. */ import { type ReactNode, useEffect } from "react"; import { useUser, useAuth, RedirectToSignIn } from "@clerk/clerk-react"; import { useAppDispatch, useAppSelector } from "../../store/hooks"; import { setUser, clearUser } from "../../store/slices/authSlice"; +import { useCurrentUser } from "../../hooks/useCurrentUser"; interface AuthGuardProps { children: ReactNode; } export function AuthGuard({ children }: AuthGuardProps) { - const { isLoaded, isSignedIn, user } = useUser(); + const { isLoaded, isSignedIn } = useUser(); const { isLoaded: authLoaded } = useAuth(); const dispatch = useAppDispatch(); const reduxUser = useAppSelector((state) => state.auth.user); + const { data: profile, isError, error } = useCurrentUser(); - // Keep Redux in lockstep with Clerk's user object. Fires on initial load - // and again any time Clerk publishes an updated `user` (e.g. after the - // app calls `user.reload()` post-metadata write). + // Keep Redux in lockstep with our profile endpoint. Fires on initial load + // and again whenever the query is invalidated (e.g. after linking Sleeper). useEffect(() => { if (!isLoaded || !authLoaded) return; - if (isSignedIn && user) { - const meta = user.unsafeMetadata ?? {}; + if (isSignedIn && profile) { dispatch( setUser({ - id: user.id, - username: user.username, - email: user.primaryEmailAddress?.emailAddress ?? "", - sleeperUsername: (meta.sleeperUsername as string) ?? null, - sleeperUserId: (meta.sleeperUserId as string) ?? null, - syncedLeagueIds: (meta.syncedLeagueIds as string[]) ?? [], + id: profile.id, + username: profile.username, + email: profile.email ?? "", + sleeperUsername: profile.sleeperUsername, + sleeperUserId: profile.sleeperUserId, + syncedLeagueIds: profile.syncedLeagueIds, }), ); - } else { + } else if (!isSignedIn) { dispatch(clearUser()); } - }, [isLoaded, authLoaded, isSignedIn, user, dispatch]); + }, [isLoaded, authLoaded, isSignedIn, profile, dispatch]); if (!isLoaded || !authLoaded) { return ; @@ -66,10 +66,31 @@ export function AuthGuard({ children }: AuthGuardProps) { if (!isSignedIn) return ; - // Hold render until Redux is hydrated — prevents queries firing with null sleeperUserId. - // Without this we'd briefly mount children with `useAppSelector(...)` returning the - // initial state, which would cause every Sleeper hook to fire its `enabled: !!id` guard - // twice (once with null, once with the real id once Redux catches up). + // The profile request failing means we have no user row and no Sleeper link, + // so rendering the app would just produce a wall of broken widgets. Surface + // it instead of spinning forever. + if (isError) { + return ( +
+

Couldn't load your account

+

+ {error instanceof Error ? error.message : "Please try again."} +

+ +
+ ); + } + + // Hold render until Redux is hydrated — prevents queries firing with null + // sleeperUserId. Without this we'd briefly mount children with + // `useAppSelector(...)` returning the initial state, which would cause every + // Sleeper hook to fire its `enabled: !!id` guard twice (once with null, once + // with the real id once Redux catches up). if (!reduxUser) return ; return <>{children}; diff --git a/client/src/hooks/useCurrentUser.ts b/client/src/hooks/useCurrentUser.ts new file mode 100644 index 0000000..3218fa7 --- /dev/null +++ b/client/src/hooks/useCurrentUser.ts @@ -0,0 +1,59 @@ +/** + * The signed-in user's Huddle profile, from our own API. + * + * This replaces reading Clerk's `unsafeMetadata` on the client. Clerk still + * owns the *session* (`useAuth`/`useUser` for tokens and sign-in state), but + * the Sleeper link and synced-league list now come from our `users` table via + * `GET /api/user/me`. + * + * That endpoint is also what creates the user's row, so it must succeed before + * anything else calls the API — AuthGuard blocks render on it for exactly that + * reason. Don't add a competing fetch of this data elsewhere; read the Redux + * `auth.user` mirror instead, or reuse this hook's cache. + * + * Stale time is Infinity: this only changes when *we* change it, so it's + * refreshed by explicit invalidation (see `useInvalidateCurrentUser`) rather + * than by polling. This is the same reasoning as the 24h player-dictionary + * tier in `useSleeper.ts`. + */ +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useAuth } from "@clerk/clerk-react"; +import axios from "axios"; + +export interface CurrentUser { + id: string; + email: string | null; + username: string | null; + sleeperUsername: string | null; + sleeperUserId: string | null; + syncedLeagueIds: string[]; +} + +export const currentUserKey = ["current-user"] as const; + +export function useCurrentUser() { + const { getToken, isSignedIn } = useAuth(); + + return useQuery({ + queryKey: currentUserKey, + enabled: !!isSignedIn, + staleTime: Infinity, + queryFn: async (): Promise => { + const token = await getToken(); + const res = await axios.get<{ user: CurrentUser }>("/api/user/me", { + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }); + return res.data.user; + }, + }); +} + +/** + * Call after any mutation that changes the user's profile. Replaces the old + * `await user.reload()` dance, which existed only to bust Clerk's metadata + * cache. + */ +export function useInvalidateCurrentUser() { + const queryClient = useQueryClient(); + return () => queryClient.invalidateQueries({ queryKey: currentUserKey }); +} diff --git a/server/drizzle/0011_sticky_shockwave.sql b/server/drizzle/0011_sticky_shockwave.sql new file mode 100644 index 0000000..496d33e --- /dev/null +++ b/server/drizzle/0011_sticky_shockwave.sql @@ -0,0 +1,15 @@ +CREATE TABLE "users" ( + "id" text PRIMARY KEY NOT NULL, + "email" text, + "username" text, + "sleeper_username" text, + "sleeper_user_id" text, + "synced_league_ids" text[] DEFAULT '{}'::text[] NOT NULL, + "is_placeholder" boolean DEFAULT false NOT NULL, + "identity_synced_at" timestamp with time zone DEFAULT now() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE INDEX "users_email_idx" ON "users" USING btree ("email");--> statement-breakpoint +CREATE INDEX "users_username_idx" ON "users" USING btree ("username"); \ No newline at end of file diff --git a/server/drizzle/0012_green_valkyrie.sql b/server/drizzle/0012_green_valkyrie.sql new file mode 100644 index 0000000..f050589 --- /dev/null +++ b/server/drizzle/0012_green_valkyrie.sql @@ -0,0 +1,53 @@ +-- Hand-added seed step (the ALTER TABLEs below are Drizzle-generated). +-- +-- Every user id already referenced by our data needs a `users` row before the +-- foreign keys can be added. `db:migrate` applies 0011 and 0012 in the same +-- run, so this cannot rely on scripts/backfill-users.mjs having been run in +-- between — it seeds minimal placeholder rows itself, from the data alone, and +-- is therefore guaranteed to satisfy every constraint that follows. +-- +-- Run `npm run db:backfill-users --prefix server` afterwards to fill these in +-- with real emails/usernames and the Sleeper links from Clerk's metadata; that +-- script clears is_placeholder for every id Clerk still knows about. Whatever +-- stays flagged is a deleted account we're keeping for attribution. +INSERT INTO "users" ("id", "is_placeholder") +SELECT DISTINCT "id", true FROM ( + SELECT "user_id" AS id FROM "huddle_commissioners" + UNION SELECT "added_by" FROM "huddle_commissioners" + UNION SELECT "user_id" FROM "team_claims" + UNION SELECT "decided_by" FROM "team_claims" + UNION SELECT "author_id" FROM "huddle_announcements" + UNION SELECT "marked_by" FROM "huddle_dues_payments" + UNION SELECT "granted_by" FROM "huddle_awards" + UNION SELECT "proposer_id" FROM "side_bets" + UNION SELECT "opponent_id" FROM "side_bets" + UNION SELECT "winner_id" FROM "side_bets" + UNION SELECT "author_id" FROM "huddle_forum_topics" + UNION SELECT "deleted_by" FROM "huddle_forum_topics" + UNION SELECT "author_id" FROM "huddle_forum_replies" + UNION SELECT "deleted_by" FROM "huddle_forum_replies" + UNION SELECT "author_id" FROM "huddle_polls" + UNION SELECT "user_id" FROM "huddle_poll_votes" + UNION SELECT "author_id" FROM "huddle_surveys" + UNION SELECT "user_id" FROM "huddle_survey_responses" +) refs +WHERE "id" IS NOT NULL +ON CONFLICT ("id") DO NOTHING;--> statement-breakpoint +ALTER TABLE "huddle_announcements" ADD CONSTRAINT "huddle_announcements_author_id_users_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_awards" ADD CONSTRAINT "huddle_awards_granted_by_users_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_commissioners" ADD CONSTRAINT "huddle_commissioners_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_commissioners" ADD CONSTRAINT "huddle_commissioners_added_by_users_id_fk" FOREIGN KEY ("added_by") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_dues_payments" ADD CONSTRAINT "huddle_dues_payments_marked_by_users_id_fk" FOREIGN KEY ("marked_by") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_forum_replies" ADD CONSTRAINT "huddle_forum_replies_author_id_users_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_forum_replies" ADD CONSTRAINT "huddle_forum_replies_deleted_by_users_id_fk" FOREIGN KEY ("deleted_by") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_forum_topics" ADD CONSTRAINT "huddle_forum_topics_author_id_users_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_forum_topics" ADD CONSTRAINT "huddle_forum_topics_deleted_by_users_id_fk" FOREIGN KEY ("deleted_by") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_poll_votes" ADD CONSTRAINT "huddle_poll_votes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_polls" ADD CONSTRAINT "huddle_polls_author_id_users_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_survey_responses" ADD CONSTRAINT "huddle_survey_responses_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_surveys" ADD CONSTRAINT "huddle_surveys_author_id_users_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "side_bets" ADD CONSTRAINT "side_bets_proposer_id_users_id_fk" FOREIGN KEY ("proposer_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "side_bets" ADD CONSTRAINT "side_bets_opponent_id_users_id_fk" FOREIGN KEY ("opponent_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "side_bets" ADD CONSTRAINT "side_bets_winner_id_users_id_fk" FOREIGN KEY ("winner_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "team_claims" ADD CONSTRAINT "team_claims_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "team_claims" ADD CONSTRAINT "team_claims_decided_by_users_id_fk" FOREIGN KEY ("decided_by") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action; \ No newline at end of file diff --git a/server/drizzle/meta/0011_snapshot.json b/server/drizzle/meta/0011_snapshot.json new file mode 100644 index 0000000..7e8692b --- /dev/null +++ b/server/drizzle/meta/0011_snapshot.json @@ -0,0 +1,2410 @@ +{ + "id": "4fb314ac-fb58-467a-a0a7-7e4725a7c292", + "prevId": "6344b4e3-ad1b-40bc-9209-1c8c135255fd", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.huddle_active_trophies": { + "name": "huddle_active_trophies", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trophy_type": { + "name": "trophy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_active_trophies_huddle_id_huddles_id_fk": { + "name": "huddle_active_trophies_huddle_id_huddles_id_fk", + "tableFrom": "huddle_active_trophies", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "huddle_active_trophies_huddle_id_trophy_type_pk": { + "name": "huddle_active_trophies_huddle_id_trophy_type_pk", + "columns": [ + "huddle_id", + "trophy_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_announcements": { + "name": "huddle_announcements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_announcements_huddle_idx": { + "name": "huddle_announcements_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_announcements_huddle_id_huddles_id_fk": { + "name": "huddle_announcements_huddle_id_huddles_id_fk", + "tableFrom": "huddle_announcements", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_awards": { + "name": "huddle_awards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "glyph": { + "name": "glyph", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_awards_huddle_idx": { + "name": "huddle_awards_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_awards_huddle_roster_idx": { + "name": "huddle_awards_huddle_roster_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_awards_huddle_id_huddles_id_fk": { + "name": "huddle_awards_huddle_id_huddles_id_fk", + "tableFrom": "huddle_awards", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_commissioners": { + "name": "huddle_commissioners", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_commissioners_user_idx": { + "name": "huddle_commissioners_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_commissioners_huddle_id_huddles_id_fk": { + "name": "huddle_commissioners_huddle_id_huddles_id_fk", + "tableFrom": "huddle_commissioners", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "huddle_commissioners_huddle_id_user_id_pk": { + "name": "huddle_commissioners_huddle_id_user_id_pk", + "columns": [ + "huddle_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_countdown_config": { + "name": "huddle_countdown_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_at": { + "name": "target_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_countdown_config_huddle_id_huddles_id_fk": { + "name": "huddle_countdown_config_huddle_id_huddles_id_fk", + "tableFrom": "huddle_countdown_config", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_dues_config": { + "name": "huddle_dues_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_dues_config_huddle_id_huddles_id_fk": { + "name": "huddle_dues_config_huddle_id_huddles_id_fk", + "tableFrom": "huddle_dues_config", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_dues_payments": { + "name": "huddle_dues_payments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "marked_by": { + "name": "marked_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_dues_payments_huddle_roster_uniq": { + "name": "huddle_dues_payments_huddle_roster_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_dues_payments_huddle_id_huddles_id_fk": { + "name": "huddle_dues_payments_huddle_id_huddles_id_fk", + "tableFrom": "huddle_dues_payments", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_forum_replies": { + "name": "huddle_forum_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_forum_replies_topic_idx": { + "name": "huddle_forum_replies_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk": { + "name": "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "huddle_forum_topics", + "columnsFrom": [ + "topic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_forum_replies_huddle_id_huddles_id_fk": { + "name": "huddle_forum_replies_huddle_id_huddles_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_forum_topics": { + "name": "huddle_forum_topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_forum_topics_huddle_updated_idx": { + "name": "huddle_forum_topics_huddle_updated_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_forum_topics_huddle_id_huddles_id_fk": { + "name": "huddle_forum_topics_huddle_id_huddles_id_fk", + "tableFrom": "huddle_forum_topics", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_payout_entries": { + "name": "huddle_payout_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_payout_entries_huddle_idx": { + "name": "huddle_payout_entries_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_payout_entries_huddle_id_huddles_id_fk": { + "name": "huddle_payout_entries_huddle_id_huddles_id_fk", + "tableFrom": "huddle_payout_entries", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_poll_options": { + "name": "huddle_poll_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_poll_options_poll_idx": { + "name": "huddle_poll_options_poll_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_poll_options_poll_id_huddle_polls_id_fk": { + "name": "huddle_poll_options_poll_id_huddle_polls_id_fk", + "tableFrom": "huddle_poll_options", + "tableTo": "huddle_polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_poll_votes": { + "name": "huddle_poll_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_poll_votes_poll_user_idx": { + "name": "huddle_poll_votes_poll_user_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_poll_votes_option_user_uniq": { + "name": "huddle_poll_votes_option_user_uniq", + "columns": [ + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_poll_votes_poll_id_huddle_polls_id_fk": { + "name": "huddle_poll_votes_poll_id_huddle_polls_id_fk", + "tableFrom": "huddle_poll_votes", + "tableTo": "huddle_polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_poll_votes_option_id_huddle_poll_options_id_fk": { + "name": "huddle_poll_votes_option_id_huddle_poll_options_id_fk", + "tableFrom": "huddle_poll_votes", + "tableTo": "huddle_poll_options", + "columnsFrom": [ + "option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_polls": { + "name": "huddle_polls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_dashboard_poll": { + "name": "is_dashboard_poll", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_multiple": { + "name": "allow_multiple", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_vote_changes": { + "name": "allow_vote_changes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "results_visibility": { + "name": "results_visibility", + "type": "poll_results_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_polls_topic_idx": { + "name": "huddle_polls_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_polls_dashboard_active_uniq": { + "name": "huddle_polls_dashboard_active_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"huddle_polls\".\"is_dashboard_poll\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_polls_huddle_id_huddles_id_fk": { + "name": "huddle_polls_huddle_id_huddles_id_fk", + "tableFrom": "huddle_polls", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_polls_topic_id_huddle_forum_topics_id_fk": { + "name": "huddle_polls_topic_id_huddle_forum_topics_id_fk", + "tableFrom": "huddle_polls", + "tableTo": "huddle_forum_topics", + "columnsFrom": [ + "topic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_answer_options": { + "name": "huddle_survey_answer_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_survey_answer_options_question_idx": { + "name": "huddle_survey_answer_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_survey_answer_options_response_option_uniq": { + "name": "huddle_survey_answer_options_response_option_uniq", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_answer_options_response_id_huddle_survey_responses_id_fk": { + "name": "huddle_survey_answer_options_response_id_huddle_survey_responses_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_responses", + "columnsFrom": [ + "response_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answer_options_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_answer_options_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answer_options_option_id_huddle_survey_options_id_fk": { + "name": "huddle_survey_answer_options_option_id_huddle_survey_options_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_options", + "columnsFrom": [ + "option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_answers": { + "name": "huddle_survey_answers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "text_value": { + "name": "text_value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_survey_answers_response_idx": { + "name": "huddle_survey_answers_response_idx", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_survey_answers_question_idx": { + "name": "huddle_survey_answers_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_answers_response_id_huddle_survey_responses_id_fk": { + "name": "huddle_survey_answers_response_id_huddle_survey_responses_id_fk", + "tableFrom": "huddle_survey_answers", + "tableTo": "huddle_survey_responses", + "columnsFrom": [ + "response_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answers_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_answers_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_answers", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_options": { + "name": "huddle_survey_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_survey_options_question_idx": { + "name": "huddle_survey_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_options_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_options_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_options", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_questions": { + "name": "huddle_survey_questions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "survey_question_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_survey_questions_survey_idx": { + "name": "huddle_survey_questions_survey_idx", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_questions_survey_id_huddle_surveys_id_fk": { + "name": "huddle_survey_questions_survey_id_huddle_surveys_id_fk", + "tableFrom": "huddle_survey_questions", + "tableTo": "huddle_surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_responses": { + "name": "huddle_survey_responses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_survey_responses_survey_user_uniq": { + "name": "huddle_survey_responses_survey_user_uniq", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_responses_survey_id_huddle_surveys_id_fk": { + "name": "huddle_survey_responses_survey_id_huddle_surveys_id_fk", + "tableFrom": "huddle_survey_responses", + "tableTo": "huddle_surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_surveys": { + "name": "huddle_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "results_published": { + "name": "results_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_publish_on_close": { + "name": "auto_publish_on_close", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "anonymity": { + "name": "anonymity", + "type": "survey_anonymity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_surveys_huddle_created_idx": { + "name": "huddle_surveys_huddle_created_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_surveys_huddle_id_huddles_id_fk": { + "name": "huddle_surveys_huddle_id_huddles_id_fk", + "tableFrom": "huddle_surveys", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddles": { + "name": "huddles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_provider": { + "name": "league_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "league_id": { + "name": "league_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code_updated_at": { + "name": "invite_code_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invite_link_token": { + "name": "invite_link_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "invite_link_expires_at": { + "name": "invite_link_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddles_invite_code_uniq": { + "name": "huddles_invite_code_uniq", + "columns": [ + { + "expression": "invite_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddles_invite_link_token_uniq": { + "name": "huddles_invite_link_token_uniq", + "columns": [ + { + "expression": "invite_link_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.side_bets": { + "name": "side_bets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proposer_id": { + "name": "proposer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opponent_id": { + "name": "opponent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposer_roster_id": { + "name": "proposer_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "opponent_roster_id": { + "name": "opponent_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "week": { + "name": "week", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "prize_description": { + "name": "prize_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "side_bet_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "winner_id": { + "name": "winner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settlement_note": { + "name": "settlement_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "side_bets_huddle_idx": { + "name": "side_bets_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "side_bets_proposer_idx": { + "name": "side_bets_proposer_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "proposer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "side_bets_opponent_idx": { + "name": "side_bets_opponent_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "opponent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "side_bets_huddle_id_huddles_id_fk": { + "name": "side_bets_huddle_id_huddles_id_fk", + "tableFrom": "side_bets", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_claims": { + "name": "team_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "claim_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_claims_huddle_roster_approved_uniq": { + "name": "team_claims_huddle_roster_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"team_claims\".\"status\" = 'approved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_huddle_user_approved_uniq": { + "name": "team_claims_huddle_user_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"team_claims\".\"status\" = 'approved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_huddle_idx": { + "name": "team_claims_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_user_idx": { + "name": "team_claims_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_claims_huddle_id_huddles_id_fk": { + "name": "team_claims_huddle_id_huddles_id_fk", + "tableFrom": "team_claims", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_username": { + "name": "sleeper_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_user_id": { + "name": "sleeper_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "synced_league_ids": { + "name": "synced_league_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_placeholder": { + "name": "is_placeholder", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "identity_synced_at": { + "name": "identity_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_username_idx": { + "name": "users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.claim_status": { + "name": "claim_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected" + ] + }, + "public.poll_results_visibility": { + "name": "poll_results_visibility", + "schema": "public", + "values": [ + "always", + "after_vote", + "after_close" + ] + }, + "public.side_bet_status": { + "name": "side_bet_status", + "schema": "public", + "values": [ + "pending", + "accepted", + "rejected", + "cancelled", + "settled" + ] + }, + "public.survey_anonymity": { + "name": "survey_anonymity", + "schema": "public", + "values": [ + "none", + "anonymous_to_league", + "anonymous_to_all" + ] + }, + "public.survey_question_type": { + "name": "survey_question_type", + "schema": "public", + "values": [ + "short_text", + "paragraph", + "multiple_choice", + "checkboxes" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/0012_snapshot.json b/server/drizzle/meta/0012_snapshot.json new file mode 100644 index 0000000..695452b --- /dev/null +++ b/server/drizzle/meta/0012_snapshot.json @@ -0,0 +1,2644 @@ +{ + "id": "48c4474c-9264-4e99-922a-8f1e3ed418a2", + "prevId": "4fb314ac-fb58-467a-a0a7-7e4725a7c292", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.huddle_active_trophies": { + "name": "huddle_active_trophies", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trophy_type": { + "name": "trophy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_active_trophies_huddle_id_huddles_id_fk": { + "name": "huddle_active_trophies_huddle_id_huddles_id_fk", + "tableFrom": "huddle_active_trophies", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "huddle_active_trophies_huddle_id_trophy_type_pk": { + "name": "huddle_active_trophies_huddle_id_trophy_type_pk", + "columns": [ + "huddle_id", + "trophy_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_announcements": { + "name": "huddle_announcements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_announcements_huddle_idx": { + "name": "huddle_announcements_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_announcements_huddle_id_huddles_id_fk": { + "name": "huddle_announcements_huddle_id_huddles_id_fk", + "tableFrom": "huddle_announcements", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_announcements_author_id_users_id_fk": { + "name": "huddle_announcements_author_id_users_id_fk", + "tableFrom": "huddle_announcements", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_awards": { + "name": "huddle_awards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "glyph": { + "name": "glyph", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_awards_huddle_idx": { + "name": "huddle_awards_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_awards_huddle_roster_idx": { + "name": "huddle_awards_huddle_roster_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_awards_huddle_id_huddles_id_fk": { + "name": "huddle_awards_huddle_id_huddles_id_fk", + "tableFrom": "huddle_awards", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_awards_granted_by_users_id_fk": { + "name": "huddle_awards_granted_by_users_id_fk", + "tableFrom": "huddle_awards", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_commissioners": { + "name": "huddle_commissioners", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_commissioners_user_idx": { + "name": "huddle_commissioners_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_commissioners_huddle_id_huddles_id_fk": { + "name": "huddle_commissioners_huddle_id_huddles_id_fk", + "tableFrom": "huddle_commissioners", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_commissioners_user_id_users_id_fk": { + "name": "huddle_commissioners_user_id_users_id_fk", + "tableFrom": "huddle_commissioners", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_commissioners_added_by_users_id_fk": { + "name": "huddle_commissioners_added_by_users_id_fk", + "tableFrom": "huddle_commissioners", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "huddle_commissioners_huddle_id_user_id_pk": { + "name": "huddle_commissioners_huddle_id_user_id_pk", + "columns": [ + "huddle_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_countdown_config": { + "name": "huddle_countdown_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_at": { + "name": "target_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_countdown_config_huddle_id_huddles_id_fk": { + "name": "huddle_countdown_config_huddle_id_huddles_id_fk", + "tableFrom": "huddle_countdown_config", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_dues_config": { + "name": "huddle_dues_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_dues_config_huddle_id_huddles_id_fk": { + "name": "huddle_dues_config_huddle_id_huddles_id_fk", + "tableFrom": "huddle_dues_config", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_dues_payments": { + "name": "huddle_dues_payments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "marked_by": { + "name": "marked_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_dues_payments_huddle_roster_uniq": { + "name": "huddle_dues_payments_huddle_roster_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_dues_payments_huddle_id_huddles_id_fk": { + "name": "huddle_dues_payments_huddle_id_huddles_id_fk", + "tableFrom": "huddle_dues_payments", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_dues_payments_marked_by_users_id_fk": { + "name": "huddle_dues_payments_marked_by_users_id_fk", + "tableFrom": "huddle_dues_payments", + "tableTo": "users", + "columnsFrom": [ + "marked_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_forum_replies": { + "name": "huddle_forum_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_forum_replies_topic_idx": { + "name": "huddle_forum_replies_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk": { + "name": "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "huddle_forum_topics", + "columnsFrom": [ + "topic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_forum_replies_huddle_id_huddles_id_fk": { + "name": "huddle_forum_replies_huddle_id_huddles_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_forum_replies_author_id_users_id_fk": { + "name": "huddle_forum_replies_author_id_users_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_forum_replies_deleted_by_users_id_fk": { + "name": "huddle_forum_replies_deleted_by_users_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_forum_topics": { + "name": "huddle_forum_topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_forum_topics_huddle_updated_idx": { + "name": "huddle_forum_topics_huddle_updated_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_forum_topics_huddle_id_huddles_id_fk": { + "name": "huddle_forum_topics_huddle_id_huddles_id_fk", + "tableFrom": "huddle_forum_topics", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_forum_topics_author_id_users_id_fk": { + "name": "huddle_forum_topics_author_id_users_id_fk", + "tableFrom": "huddle_forum_topics", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_forum_topics_deleted_by_users_id_fk": { + "name": "huddle_forum_topics_deleted_by_users_id_fk", + "tableFrom": "huddle_forum_topics", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_payout_entries": { + "name": "huddle_payout_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_payout_entries_huddle_idx": { + "name": "huddle_payout_entries_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_payout_entries_huddle_id_huddles_id_fk": { + "name": "huddle_payout_entries_huddle_id_huddles_id_fk", + "tableFrom": "huddle_payout_entries", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_poll_options": { + "name": "huddle_poll_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_poll_options_poll_idx": { + "name": "huddle_poll_options_poll_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_poll_options_poll_id_huddle_polls_id_fk": { + "name": "huddle_poll_options_poll_id_huddle_polls_id_fk", + "tableFrom": "huddle_poll_options", + "tableTo": "huddle_polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_poll_votes": { + "name": "huddle_poll_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_poll_votes_poll_user_idx": { + "name": "huddle_poll_votes_poll_user_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_poll_votes_option_user_uniq": { + "name": "huddle_poll_votes_option_user_uniq", + "columns": [ + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_poll_votes_poll_id_huddle_polls_id_fk": { + "name": "huddle_poll_votes_poll_id_huddle_polls_id_fk", + "tableFrom": "huddle_poll_votes", + "tableTo": "huddle_polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_poll_votes_option_id_huddle_poll_options_id_fk": { + "name": "huddle_poll_votes_option_id_huddle_poll_options_id_fk", + "tableFrom": "huddle_poll_votes", + "tableTo": "huddle_poll_options", + "columnsFrom": [ + "option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_poll_votes_user_id_users_id_fk": { + "name": "huddle_poll_votes_user_id_users_id_fk", + "tableFrom": "huddle_poll_votes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_polls": { + "name": "huddle_polls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_dashboard_poll": { + "name": "is_dashboard_poll", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_multiple": { + "name": "allow_multiple", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_vote_changes": { + "name": "allow_vote_changes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "results_visibility": { + "name": "results_visibility", + "type": "poll_results_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_polls_topic_idx": { + "name": "huddle_polls_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_polls_dashboard_active_uniq": { + "name": "huddle_polls_dashboard_active_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"huddle_polls\".\"is_dashboard_poll\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_polls_huddle_id_huddles_id_fk": { + "name": "huddle_polls_huddle_id_huddles_id_fk", + "tableFrom": "huddle_polls", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_polls_author_id_users_id_fk": { + "name": "huddle_polls_author_id_users_id_fk", + "tableFrom": "huddle_polls", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_polls_topic_id_huddle_forum_topics_id_fk": { + "name": "huddle_polls_topic_id_huddle_forum_topics_id_fk", + "tableFrom": "huddle_polls", + "tableTo": "huddle_forum_topics", + "columnsFrom": [ + "topic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_answer_options": { + "name": "huddle_survey_answer_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_survey_answer_options_question_idx": { + "name": "huddle_survey_answer_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_survey_answer_options_response_option_uniq": { + "name": "huddle_survey_answer_options_response_option_uniq", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_answer_options_response_id_huddle_survey_responses_id_fk": { + "name": "huddle_survey_answer_options_response_id_huddle_survey_responses_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_responses", + "columnsFrom": [ + "response_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answer_options_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_answer_options_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answer_options_option_id_huddle_survey_options_id_fk": { + "name": "huddle_survey_answer_options_option_id_huddle_survey_options_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_options", + "columnsFrom": [ + "option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_answers": { + "name": "huddle_survey_answers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "text_value": { + "name": "text_value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_survey_answers_response_idx": { + "name": "huddle_survey_answers_response_idx", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_survey_answers_question_idx": { + "name": "huddle_survey_answers_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_answers_response_id_huddle_survey_responses_id_fk": { + "name": "huddle_survey_answers_response_id_huddle_survey_responses_id_fk", + "tableFrom": "huddle_survey_answers", + "tableTo": "huddle_survey_responses", + "columnsFrom": [ + "response_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answers_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_answers_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_answers", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_options": { + "name": "huddle_survey_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_survey_options_question_idx": { + "name": "huddle_survey_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_options_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_options_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_options", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_questions": { + "name": "huddle_survey_questions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "survey_question_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_survey_questions_survey_idx": { + "name": "huddle_survey_questions_survey_idx", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_questions_survey_id_huddle_surveys_id_fk": { + "name": "huddle_survey_questions_survey_id_huddle_surveys_id_fk", + "tableFrom": "huddle_survey_questions", + "tableTo": "huddle_surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_responses": { + "name": "huddle_survey_responses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_survey_responses_survey_user_uniq": { + "name": "huddle_survey_responses_survey_user_uniq", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_responses_survey_id_huddle_surveys_id_fk": { + "name": "huddle_survey_responses_survey_id_huddle_surveys_id_fk", + "tableFrom": "huddle_survey_responses", + "tableTo": "huddle_surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_responses_user_id_users_id_fk": { + "name": "huddle_survey_responses_user_id_users_id_fk", + "tableFrom": "huddle_survey_responses", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_surveys": { + "name": "huddle_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "results_published": { + "name": "results_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_publish_on_close": { + "name": "auto_publish_on_close", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "anonymity": { + "name": "anonymity", + "type": "survey_anonymity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_surveys_huddle_created_idx": { + "name": "huddle_surveys_huddle_created_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_surveys_huddle_id_huddles_id_fk": { + "name": "huddle_surveys_huddle_id_huddles_id_fk", + "tableFrom": "huddle_surveys", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_surveys_author_id_users_id_fk": { + "name": "huddle_surveys_author_id_users_id_fk", + "tableFrom": "huddle_surveys", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddles": { + "name": "huddles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_provider": { + "name": "league_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "league_id": { + "name": "league_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code_updated_at": { + "name": "invite_code_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invite_link_token": { + "name": "invite_link_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "invite_link_expires_at": { + "name": "invite_link_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddles_invite_code_uniq": { + "name": "huddles_invite_code_uniq", + "columns": [ + { + "expression": "invite_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddles_invite_link_token_uniq": { + "name": "huddles_invite_link_token_uniq", + "columns": [ + { + "expression": "invite_link_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.side_bets": { + "name": "side_bets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proposer_id": { + "name": "proposer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opponent_id": { + "name": "opponent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposer_roster_id": { + "name": "proposer_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "opponent_roster_id": { + "name": "opponent_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "week": { + "name": "week", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "prize_description": { + "name": "prize_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "side_bet_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "winner_id": { + "name": "winner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settlement_note": { + "name": "settlement_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "side_bets_huddle_idx": { + "name": "side_bets_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "side_bets_proposer_idx": { + "name": "side_bets_proposer_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "proposer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "side_bets_opponent_idx": { + "name": "side_bets_opponent_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "opponent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "side_bets_huddle_id_huddles_id_fk": { + "name": "side_bets_huddle_id_huddles_id_fk", + "tableFrom": "side_bets", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "side_bets_proposer_id_users_id_fk": { + "name": "side_bets_proposer_id_users_id_fk", + "tableFrom": "side_bets", + "tableTo": "users", + "columnsFrom": [ + "proposer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "side_bets_opponent_id_users_id_fk": { + "name": "side_bets_opponent_id_users_id_fk", + "tableFrom": "side_bets", + "tableTo": "users", + "columnsFrom": [ + "opponent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "side_bets_winner_id_users_id_fk": { + "name": "side_bets_winner_id_users_id_fk", + "tableFrom": "side_bets", + "tableTo": "users", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_claims": { + "name": "team_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "claim_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_claims_huddle_roster_approved_uniq": { + "name": "team_claims_huddle_roster_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"team_claims\".\"status\" = 'approved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_huddle_user_approved_uniq": { + "name": "team_claims_huddle_user_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"team_claims\".\"status\" = 'approved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_huddle_idx": { + "name": "team_claims_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_user_idx": { + "name": "team_claims_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_claims_huddle_id_huddles_id_fk": { + "name": "team_claims_huddle_id_huddles_id_fk", + "tableFrom": "team_claims", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_claims_user_id_users_id_fk": { + "name": "team_claims_user_id_users_id_fk", + "tableFrom": "team_claims", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "team_claims_decided_by_users_id_fk": { + "name": "team_claims_decided_by_users_id_fk", + "tableFrom": "team_claims", + "tableTo": "users", + "columnsFrom": [ + "decided_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_username": { + "name": "sleeper_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_user_id": { + "name": "sleeper_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "synced_league_ids": { + "name": "synced_league_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_placeholder": { + "name": "is_placeholder", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "identity_synced_at": { + "name": "identity_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_username_idx": { + "name": "users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.claim_status": { + "name": "claim_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected" + ] + }, + "public.poll_results_visibility": { + "name": "poll_results_visibility", + "schema": "public", + "values": [ + "always", + "after_vote", + "after_close" + ] + }, + "public.side_bet_status": { + "name": "side_bet_status", + "schema": "public", + "values": [ + "pending", + "accepted", + "rejected", + "cancelled", + "settled" + ] + }, + "public.survey_anonymity": { + "name": "survey_anonymity", + "schema": "public", + "values": [ + "none", + "anonymous_to_league", + "anonymous_to_all" + ] + }, + "public.survey_question_type": { + "name": "survey_question_type", + "schema": "public", + "values": [ + "short_text", + "paragraph", + "multiple_choice", + "checkboxes" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/_journal.json b/server/drizzle/meta/_journal.json index 6033dc1..a23a422 100644 --- a/server/drizzle/meta/_journal.json +++ b/server/drizzle/meta/_journal.json @@ -78,6 +78,20 @@ "when": 1785791787209, "tag": "0010_romantic_captain_america", "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1786314864383, + "tag": "0011_sticky_shockwave", + "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1786315059916, + "tag": "0012_green_valkyrie", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/package.json b/server/package.json index bd90671..dbe9922 100644 --- a/server/package.json +++ b/server/package.json @@ -10,7 +10,9 @@ "db:push": "dotenv -e ../.env -- drizzle-kit push", "db:studio": "dotenv -e ../.env -- drizzle-kit studio", "db:migrate": "dotenv -e ../.env -- node scripts/migrate.mjs", - "db:migrate:prod": "dotenv -e .env.production -- node scripts/migrate.mjs" + "db:migrate:prod": "dotenv -e .env.production -- node scripts/migrate.mjs", + "db:backfill-users": "dotenv -e ../.env -- node scripts/backfill-users.mjs", + "db:backfill-users:prod": "dotenv -e .env.production -e ../.env -- node scripts/backfill-users.mjs" }, "dependencies": { "@clerk/express": "^1.7.0", diff --git a/server/scripts/backfill-users.mjs b/server/scripts/backfill-users.mjs new file mode 100644 index 0000000..0248b84 --- /dev/null +++ b/server/scripts/backfill-users.mjs @@ -0,0 +1,161 @@ +/** + * Mirrors Clerk's user list into our own `users` table. + * + * This serves two purposes, and is safe to run repeatedly for both: + * + * 1. **One-time backfill.** Before the foreign keys in migration 0012 can be + * applied, every user id already referenced by our data needs a `users` + * row. That includes ids belonging to *deleted* Clerk accounts, which get + * a placeholder row so attribution ("posted by") survives. + * + * 2. **Recurring snapshot.** Run it on a schedule and a Clerk outage — or a + * Clerk exit — never costs us the identity data. Nothing here is + * destructive. + * + * Re-run safety: identity fields (email, username) are always refreshed from + * Clerk, because Clerk owns them. Sleeper fields and syncedLeagueIds are only + * written when our row doesn't already have a value — after the cutover *we* + * own those, and Clerk's `unsafeMetadata` copy is frozen at migration time. + * Overwriting from it would silently roll back a user's later edits. + * + * Usage: + * npm run db:backfill-users --prefix server (dev DB, from ../.env) + * npm run db:backfill-users:prod --prefix server (prod DB, from .env.production) + */ +import { neon } from "@neondatabase/serverless"; +import { createClerkClient } from "@clerk/express"; + +const url = process.env.DATABASE_URL; +if (!url) throw new Error("Missing DATABASE_URL"); +const secretKey = process.env.CLERK_SECRET_KEY; +if (!secretKey) throw new Error("Missing CLERK_SECRET_KEY"); + +const sql = neon(url); +const clerk = createClerkClient({ secretKey }); + +/** + * Every column in the schema that holds a user id. Keep this in sync with the + * foreign keys in schema.ts — if you add a new user-referencing column, it + * belongs here too, or the next FK migration will fail on orphaned rows. + */ +const USER_ID_COLUMNS = [ + ["huddle_commissioners", "user_id"], + ["huddle_commissioners", "added_by"], + ["team_claims", "user_id"], + ["team_claims", "decided_by"], + ["huddle_announcements", "author_id"], + ["huddle_dues_payments", "marked_by"], + ["huddle_awards", "granted_by"], + ["side_bets", "proposer_id"], + ["side_bets", "opponent_id"], + ["side_bets", "winner_id"], + ["huddle_forum_topics", "author_id"], + ["huddle_forum_topics", "deleted_by"], + ["huddle_forum_replies", "author_id"], + ["huddle_forum_replies", "deleted_by"], + ["huddle_polls", "author_id"], + ["huddle_poll_votes", "user_id"], + ["huddle_surveys", "author_id"], + ["huddle_survey_responses", "user_id"], +]; + +const PAGE_SIZE = 500; + +async function fetchAllClerkUsers() { + const all = []; + for (let offset = 0; ; offset += PAGE_SIZE) { + const page = await clerk.users.getUserList({ limit: PAGE_SIZE, offset }); + all.push(...page.data); + if (page.data.length < PAGE_SIZE) break; + } + return all; +} + +async function upsertFromClerk(u) { + const meta = u.unsafeMetadata ?? {}; + const email = + u.primaryEmailAddress?.emailAddress ?? + u.emailAddresses?.[0]?.emailAddress ?? + null; + const syncedLeagueIds = Array.isArray(meta.syncedLeagueIds) + ? meta.syncedLeagueIds.filter((id) => typeof id === "string") + : []; + + await sql.query( + `insert into users + (id, email, username, sleeper_username, sleeper_user_id, + synced_league_ids, is_placeholder, identity_synced_at) + values ($1, $2, $3, $4, $5, $6, false, now()) + on conflict (id) do update set + email = excluded.email, + username = excluded.username, + -- ours wins: only fill what we don't already have + sleeper_username = coalesce(users.sleeper_username, excluded.sleeper_username), + sleeper_user_id = coalesce(users.sleeper_user_id, excluded.sleeper_user_id), + synced_league_ids = case + when coalesce(array_length(users.synced_league_ids, 1), 0) = 0 + then excluded.synced_league_ids + else users.synced_league_ids + end, + is_placeholder = false, + identity_synced_at = now(), + updated_at = now()`, + [ + u.id, + email, + u.username ?? null, + typeof meta.sleeperUsername === "string" ? meta.sleeperUsername : null, + typeof meta.sleeperUserId === "string" ? meta.sleeperUserId : null, + syncedLeagueIds, + ], + ); +} + +/** User ids that appear somewhere in our data but have no `users` row. */ +async function findOrphanIds() { + const union = USER_ID_COLUMNS.map( + ([table, col]) => + `select distinct "${col}" as id from "${table}" where "${col}" is not null`, + ).join(" union "); + + const rows = await sql.query( + `select id from (${union}) refs + where not exists (select 1 from users u where u.id = refs.id)`, + ); + return rows.map((r) => r.id); +} + +console.log("Fetching users from Clerk…"); +const clerkUsers = await fetchAllClerkUsers(); +console.log(` ${clerkUsers.length} Clerk user(s).`); + +let mirrored = 0; +for (const u of clerkUsers) { + await upsertFromClerk(u); + mirrored++; +} +console.log(` Mirrored ${mirrored} user(s) into "users".`); + +console.log("Scanning for user ids referenced by data but absent from Clerk…"); +const orphans = await findOrphanIds(); + +if (orphans.length === 0) { + console.log(" None — every referenced user id has a row."); +} else { + for (const id of orphans) { + // DO NOTHING, not DO UPDATE: if a row already exists we must never + // downgrade a real user to a placeholder. + await sql.query( + `insert into users (id, is_placeholder) values ($1, true) + on conflict (id) do nothing`, + [id], + ); + } + console.log( + ` Created ${orphans.length} placeholder row(s) for deleted/unknown accounts:`, + ); + for (const id of orphans) console.log(` ${id}`); +} + +const [{ n }] = await sql.query(`select count(*)::int as n from users`); +console.log(`\nDone. "users" now holds ${n} row(s).`); diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index 6bbb1e9..d0d2b72 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -18,6 +18,68 @@ export const claimStatus = pgEnum("claim_status", [ "rejected", ]); +// ── Users ───────────────────────────────────────────────────────────────────── +// Our own mirror of the identity provider's user record, and the home for the +// product data that used to live in Clerk's `unsafeMetadata`. +// +// `id` is deliberately the *Clerk* user id (`user_2ab...`), not a fresh uuid. +// Every other table stores that same string in its `user_id`/`author_id`/etc. +// column, so keeping it as the primary key means swapping auth providers later +// is "issue our own JWTs with the same `sub`" rather than a rewrite of 18 +// columns across 11 tables. Do not renumber these — see PLAYBOOK, "Users and +// auth ownership". +// +// Identity fields (email, username) are a cache of the provider's copy, +// refreshed by usersService.ensureUser on a TTL. Sleeper fields and +// syncedLeagueIds are *owned* here — Clerk no longer holds them. +// +// All 18 references to this table are `ON DELETE RESTRICT`, deliberately: a +// user's forum posts, votes and survey answers are league history and must +// outlive their account. To "delete" a user, blank the identity fields and set +// isPlaceholder — never DELETE the row, or Postgres will (correctly) refuse. + +export const users = pgTable( + "users", + { + /** Clerk user id. Also the join key used by every other table. */ + id: text("id").primaryKey(), + /** Mirrored from Clerk. Nullable because Clerk doesn't guarantee either. */ + email: text("email"), + username: text("username"), + /** Sleeper account link — migrated out of Clerk `unsafeMetadata`. */ + sleeperUsername: text("sleeper_username"), + sleeperUserId: text("sleeper_user_id"), + /** League ids the user has opted into syncing. */ + syncedLeagueIds: text("synced_league_ids") + .array() + .notNull() + .default(sql`'{}'::text[]`), + /** + * True for rows created by the backfill for a user id that appears in our + * data but no longer exists in Clerk (deleted account). Keeps the foreign + * keys satisfiable without inventing fake identity data. + */ + isPlaceholder: boolean("is_placeholder").notNull().default(false), + /** Last time email/username were refreshed from Clerk. */ + identitySyncedAt: timestamp("identity_synced_at", { withTimezone: true }) + .defaultNow() + .notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (t) => ({ + byEmail: index("users_email_idx").on(t.email), + byUsername: index("users_username_idx").on(t.username), + }), +); + +export type User = typeof users.$inferSelect; +export type NewUser = typeof users.$inferInsert; + export const huddles = pgTable( "huddles", { @@ -54,11 +116,15 @@ export const huddleCommissioners = pgTable( huddleId: uuid("huddle_id") .notNull() .references(() => huddles.id, { onDelete: "cascade" }), - userId: text("user_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), addedAt: timestamp("added_at", { withTimezone: true }) .defaultNow() .notNull(), - addedBy: text("added_by").notNull(), + addedBy: text("added_by") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), }, (t) => ({ pk: primaryKey({ columns: [t.huddleId, t.userId] }), @@ -73,7 +139,9 @@ export const teamClaims = pgTable( huddleId: uuid("huddle_id") .notNull() .references(() => huddles.id, { onDelete: "cascade" }), - userId: text("user_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), rosterId: integer("roster_id").notNull(), status: claimStatus("status").notNull().default("pending"), message: text("message"), @@ -81,7 +149,9 @@ export const teamClaims = pgTable( .defaultNow() .notNull(), decidedAt: timestamp("decided_at", { withTimezone: true }), - decidedBy: text("decided_by"), + decidedBy: text("decided_by").references(() => users.id, { + onDelete: "restrict", + }), }, (t) => ({ uniqApprovedRoster: uniqueIndex("team_claims_huddle_roster_approved_uniq") @@ -104,7 +174,9 @@ export const huddleAnnouncements = pgTable( huddleId: uuid("huddle_id") .notNull() .references(() => huddles.id, { onDelete: "cascade" }), - authorId: text("author_id").notNull(), + authorId: text("author_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), title: text("title").notNull(), body: text("body").notNull(), createdAt: timestamp("created_at", { withTimezone: true }) @@ -142,7 +214,9 @@ export const huddleDuesPayments = pgTable( .references(() => huddles.id, { onDelete: "cascade" }), rosterId: integer("roster_id").notNull(), paidAt: timestamp("paid_at", { withTimezone: true }), - markedBy: text("marked_by").notNull(), + markedBy: text("marked_by") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), note: text("note"), updatedAt: timestamp("updated_at", { withTimezone: true }) .defaultNow() @@ -180,7 +254,9 @@ export const huddleAwards = pgTable( color: text("color").notNull(), title: text("title").notNull(), description: text("description"), - grantedBy: text("granted_by").notNull(), + grantedBy: text("granted_by") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), season: text("season"), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() @@ -269,8 +345,12 @@ export const sideBets = pgTable( huddleId: uuid("huddle_id") .notNull() .references(() => huddles.id, { onDelete: "cascade" }), - proposerId: text("proposer_id").notNull(), - opponentId: text("opponent_id").notNull(), + proposerId: text("proposer_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), + opponentId: text("opponent_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), proposerRosterId: integer("proposer_roster_id"), opponentRosterId: integer("opponent_roster_id"), week: integer("week").notNull(), @@ -280,8 +360,10 @@ export const sideBets = pgTable( /** Free-text prize description when the wager isn't cash (e.g. "loser buys dinner"). Null for cash bets. */ prizeDescription: text("prize_description"), status: sideBetStatus("status").notNull().default("pending"), - /** Clerk userId of the winner. Null until settled. */ - winnerId: text("winner_id"), + /** Winner. Null until settled. */ + winnerId: text("winner_id").references(() => users.id, { + onDelete: "restrict", + }), settlementNote: text("settlement_note"), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() @@ -332,7 +414,9 @@ export const huddleForumTopics = pgTable( huddleId: uuid("huddle_id") .notNull() .references(() => huddles.id, { onDelete: "cascade" }), - authorId: text("author_id").notNull(), + authorId: text("author_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), title: text("title").notNull(), body: text("body").notNull(), /** Bumped whenever a reply is posted, for "recent activity" sort. */ @@ -345,7 +429,9 @@ export const huddleForumTopics = pgTable( .defaultNow() .notNull(), deletedAt: timestamp("deleted_at", { withTimezone: true }), - deletedBy: text("deleted_by"), + deletedBy: text("deleted_by").references(() => users.id, { + onDelete: "restrict", + }), }, (t) => ({ byHuddleActivity: index("huddle_forum_topics_huddle_updated_idx").on( @@ -366,13 +452,17 @@ export const huddleForumReplies = pgTable( huddleId: uuid("huddle_id") .notNull() .references(() => huddles.id, { onDelete: "cascade" }), - authorId: text("author_id").notNull(), + authorId: text("author_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), body: text("body").notNull(), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), deletedAt: timestamp("deleted_at", { withTimezone: true }), - deletedBy: text("deleted_by"), + deletedBy: text("deleted_by").references(() => users.id, { + onDelete: "restrict", + }), }, (t) => ({ byTopic: index("huddle_forum_replies_topic_idx").on(t.topicId, t.createdAt), @@ -403,7 +493,9 @@ export const huddlePolls = pgTable( huddleId: uuid("huddle_id") .notNull() .references(() => huddles.id, { onDelete: "cascade" }), - authorId: text("author_id").notNull(), + authorId: text("author_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), topicId: uuid("topic_id").references(() => huddleForumTopics.id, { onDelete: "cascade" }), isDashboardPoll: boolean("is_dashboard_poll").notNull().default(false), question: text("question").notNull(), @@ -450,7 +542,9 @@ export const huddlePollVotes = pgTable( optionId: uuid("option_id") .notNull() .references(() => huddlePollOptions.id, { onDelete: "cascade" }), - userId: text("user_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), @@ -505,7 +599,9 @@ export const huddleSurveys = pgTable( huddleId: uuid("huddle_id") .notNull() .references(() => huddles.id, { onDelete: "cascade" }), - authorId: text("author_id").notNull(), + authorId: text("author_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), title: text("title").notNull(), description: text("description"), /** Responses can no longer be submitted or edited once this passes. */ @@ -564,7 +660,9 @@ export const huddleSurveyResponses = pgTable( surveyId: uuid("survey_id") .notNull() .references(() => huddleSurveys.id, { onDelete: "cascade" }), - userId: text("user_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "restrict" }), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), diff --git a/server/src/routes/huddleRoutes.ts b/server/src/routes/huddleRoutes.ts index e243d9a..9304f41 100644 --- a/server/src/routes/huddleRoutes.ts +++ b/server/src/routes/huddleRoutes.ts @@ -1,5 +1,5 @@ import { type Express, type Request, type Response } from "express"; -import { getAuth, createClerkClient } from "@clerk/express"; +import { getAuth } from "@clerk/express"; import { requireAuth } from "../middleware/requireAuth.js"; import { HuddlesServiceError, @@ -44,41 +44,16 @@ import { import { listPayouts, setPayouts } from "../services/payoutsService.js"; import { getActiveTrophies, setTrophyEnabled } from "../services/trophyControlService.js"; import { getCountdownConfig, setCountdownConfig } from "../services/countdownService.js"; +import { + displayHandle, + ensureUser, + getUserSummaries, +} from "../services/usersService.js"; -const clerkSecretKey = process.env["CLERK_SECRET_KEY"]; -if (!clerkSecretKey) { - throw new Error("Missing required environment variable: CLERK_SECRET_KEY"); -} -const clerkClient = createClerkClient({ secretKey: clerkSecretKey }); - -interface ClerkUserSummary { - id: string; - username: string | null; - email: string | null; -} - -async function fetchUserSummaries( - userIds: string[], -): Promise> { - const unique = [...new Set(userIds.filter(Boolean))]; - if (unique.length === 0) return new Map(); - const result = new Map(); - const list = await clerkClient.users.getUserList({ - userId: unique, - limit: unique.length, - }); - for (const u of list.data) { - result.set(u.id, { - id: u.id, - username: u.username, - email: - u.primaryEmailAddress?.emailAddress ?? - u.emailAddresses[0]?.emailAddress ?? - null, - }); - } - return result; -} +// Display names come from our own `users` table (one SQL round-trip), not from +// Clerk's API. These used to be `clerkClient.users.getUserList()` calls made +// inside the request handler — an HTTP hop to a third party on the critical +// path of rendering a huddle. See services/usersService.ts. function handleError(err: unknown, res: Response): void { if (err instanceof HuddlesServiceError) { @@ -145,17 +120,12 @@ function serializeClaim(c: { } export function initHuddleRoutes(app: Express) { - // POST /api/huddles — name is auto-generated from the user's Clerk display name + // POST /api/huddles — name is auto-generated from the user's display name app.post("/api/huddles", requireAuth, async (req: Request, res: Response) => { try { const { userId } = getAuth(req); - const clerkUser = await clerkClient.users.getUser(userId!); - const handle = - clerkUser.username ?? - clerkUser.firstName ?? - clerkUser.emailAddresses[0]?.emailAddress?.split("@")[0] ?? - "Your"; - const name = `${handle}'s Huddle`; + const user = await ensureUser(userId!); + const name = `${displayHandle(user)}'s Huddle`; const huddle = await createHuddle({ name, commissionerUserId: userId! }); res.status(201).json({ huddle: serializeHuddle(huddle, true) }); } catch (err) { @@ -273,7 +243,7 @@ export function initHuddleRoutes(app: Express) { ...commissioners.map((c) => c.userId), ...claims.map((c) => c.userId), ]; - const userMap = await fetchUserSummaries(allUserIds); + const userMap = await getUserSummaries(allUserIds); const myClaim = claims.find((c) => c.userId === userId) ?? null; res.json({ @@ -361,7 +331,7 @@ export function initHuddleRoutes(app: Express) { return; } const claims = await listClaimsForHuddle(huddleId); - const userMap = await fetchUserSummaries(claims.map((c) => c.userId)); + const userMap = await getUserSummaries(claims.map((c) => c.userId)); res.json({ claims: claims.map((c) => ({ ...serializeClaim(c), @@ -450,7 +420,7 @@ export function initHuddleRoutes(app: Express) { async (req: Request, res: Response) => { try { const commissioners = await listCommissioners(req.params.id!); - const userMap = await fetchUserSummaries( + const userMap = await getUserSummaries( commissioners.map((c) => c.userId), ); res.json({ @@ -487,7 +457,7 @@ export function initHuddleRoutes(app: Express) { newUserId, actingUserId: userId!, }); - const userMap = await fetchUserSummaries([row.userId]); + const userMap = await getUserSummaries([row.userId]); res.status(201).json({ commissioner: { userId: row.userId, diff --git a/server/src/routes/userRoutes.ts b/server/src/routes/userRoutes.ts index 925ac6b..474b5e1 100644 --- a/server/src/routes/userRoutes.ts +++ b/server/src/routes/userRoutes.ts @@ -1,16 +1,52 @@ +/** + * User routes — the current user's own profile. + * + * `sleeperUsername` / `sleeperUserId` / `syncedLeagueIds` used to live in + * Clerk's `unsafeMetadata`. They now live in our `users` table; Clerk is only + * consulted for identity (email, username), and only through + * usersService.ensureUser's TTL cache. Nothing here writes to Clerk anymore. + */ import { type Express, type Request, type Response } from "express"; -import { getAuth, createClerkClient } from "@clerk/express"; +import { getAuth } from "@clerk/express"; import { requireAuth } from "../middleware/requireAuth.js"; import { getProvider } from "../providers/registry.js"; +import { + ensureUser, + setSleeperLink, + setSyncedLeagues, +} from "../services/usersService.js"; +import type { User } from "../db/schema.js"; -const clerkSecretKey = process.env["CLERK_SECRET_KEY"]; -if (!clerkSecretKey) { - throw new Error("Missing required environment variable: CLERK_SECRET_KEY"); +function serializeUser(u: User) { + return { + id: u.id, + email: u.email, + username: u.username, + sleeperUsername: u.sleeperUsername, + sleeperUserId: u.sleeperUserId, + syncedLeagueIds: u.syncedLeagueIds, + }; } -const clerkClient = createClerkClient({ secretKey: clerkSecretKey }); - export function initUserRoutes(app: Express) { + /** + * GET /api/user/me — the client's source of truth for the signed-in user. + * + * Also the only place a `users` row gets created. AuthGuard calls this on + * every app load before any other request fires, which is what lets the + * foreign keys on every `user_id` column assume a row already exists. + */ + app.get("/api/user/me", requireAuth, async (req: Request, res: Response) => { + const { userId } = getAuth(req); + try { + const user = await ensureUser(userId!); + res.json({ user: serializeUser(user) }); + } catch (err) { + console.error("GET /api/user/me failed:", err); + res.status(503).json({ error: "Failed to load user" }); + } + }); + // PATCH /api/user/sleeper-username — link or clear Sleeper username app.patch( "/api/user/sleeper-username", @@ -57,26 +93,21 @@ export function initUserRoutes(app: Express) { } const { userId } = getAuth(req); - if (!userId) { - res.status(401).json({ error: "Unauthorized" }); - return; - } try { - await clerkClient.users.updateUserMetadata(userId, { - unsafeMetadata: { - sleeperUsername: username, - sleeperUserId, - // Clear synced leagues when unlinking so re-linking starts fresh - ...(isUnlink ? { syncedLeagueIds: [] } : {}), - }, + await ensureUser(userId!); + const updated = await setSleeperLink(userId!, { + sleeperUsername: username, + sleeperUserId, }); - } catch { - res.status(503).json({ error: "Failed to update user metadata" }); - return; + res.json({ + sleeperUsername: updated.sleeperUsername, + sleeperUserId: updated.sleeperUserId, + }); + } catch (err) { + console.error("PATCH /api/user/sleeper-username failed:", err); + res.status(503).json({ error: "Failed to update Sleeper link" }); } - - res.json({ sleeperUsername: username, sleeperUserId }); }, ); @@ -98,21 +129,15 @@ export function initUserRoutes(app: Express) { } const { userId } = getAuth(req); - if (!userId) { - res.status(401).json({ error: "Unauthorized" }); - return; - } try { - await clerkClient.users.updateUserMetadata(userId, { - unsafeMetadata: { syncedLeagueIds }, - }); - } catch { - res.status(503).json({ error: "Failed to update user metadata" }); - return; + await ensureUser(userId!); + const updated = await setSyncedLeagues(userId!, syncedLeagueIds); + res.json({ syncedLeagueIds: updated.syncedLeagueIds }); + } catch (err) { + console.error("PATCH /api/user/synced-leagues failed:", err); + res.status(503).json({ error: "Failed to update synced leagues" }); } - - res.json({ syncedLeagueIds }); }, ); } diff --git a/server/src/services/usersService.ts b/server/src/services/usersService.ts new file mode 100644 index 0000000..98b82ed --- /dev/null +++ b/server/src/services/usersService.ts @@ -0,0 +1,162 @@ +/** + * usersService — the `users` table is our own copy of who's who. + * + * Two kinds of data live on a user row: + * + * 1. **Identity** (email, username) — a *cache* of Clerk's copy. Refreshed + * lazily by `ensureUser` when it's older than IDENTITY_TTL_MS. Nothing + * else in the app is allowed to call Clerk for this; if you need a + * display name for a set of user ids, use `getUserSummaries`, which is a + * single SQL round-trip instead of an HTTP call to Clerk. + * + * 2. **Product data** (sleeperUsername, sleeperUserId, syncedLeagueIds) — + * *owned* here. This used to live in Clerk's `unsafeMetadata`, which made + * it the one piece of product state we couldn't recover without an export + * from a third party. It is now plain Postgres. + * + * Row lifecycle: `ensureUser` is called by `GET /api/user/me`, which AuthGuard + * hits on every app load before any other request goes out. That's what + * guarantees a row exists for the caller, which in turn is what makes the + * foreign keys on every `user_id` column safe. + */ +import { eq, inArray } from "drizzle-orm"; +import { createClerkClient } from "@clerk/express"; +import { db } from "../db/client.js"; +import { users, type User } from "../db/schema.js"; + +const clerkSecretKey = process.env["CLERK_SECRET_KEY"]; +if (!clerkSecretKey) { + throw new Error("Missing required environment variable: CLERK_SECRET_KEY"); +} + +export const clerkClient = createClerkClient({ secretKey: clerkSecretKey }); + +/** How stale a cached email/username may get before we re-read it from Clerk. */ +const IDENTITY_TTL_MS = 24 * 60 * 60 * 1000; + +export interface UserSummary { + id: string; + username: string | null; + email: string | null; +} + +function toSummary(u: Pick): UserSummary { + return { id: u.id, username: u.username, email: u.email }; +} + +/** + * Display info for a set of user ids, as a Map for O(1) lookup while + * serializing. Ids with no row are simply absent — callers already fall back + * to `{ id, username: null, email: null }`. + */ +export async function getUserSummaries( + userIds: string[], +): Promise> { + const unique = [...new Set(userIds.filter(Boolean))]; + if (unique.length === 0) return new Map(); + + const rows = await db + .select({ + id: users.id, + username: users.username, + email: users.email, + }) + .from(users) + .where(inArray(users.id, unique)); + + return new Map(rows.map((r) => [r.id, toSummary(r)])); +} + +export async function getUser(userId: string): Promise { + const [row] = await db + .select() + .from(users) + .where(eq(users.id, userId)) + .limit(1); + return row ?? null; +} + +/** + * Guarantees a `users` row for `userId` and returns it, refreshing the cached + * identity fields from Clerk if the row is new or its cache has expired. + * + * If Clerk is unreachable but we already have a row, the stale row is returned + * rather than failing the request — identity is a cache, and a day-old username + * is better than a 503. A missing row with Clerk down does throw, because we + * have nothing to serve. + */ +export async function ensureUser(userId: string): Promise { + const existing = await getUser(userId); + + const isFresh = + existing !== null && + Date.now() - existing.identitySyncedAt.getTime() < IDENTITY_TTL_MS; + if (isFresh) return existing; + + let email: string | null = null; + let username: string | null = null; + try { + const clerkUser = await clerkClient.users.getUser(userId); + username = clerkUser.username; + email = + clerkUser.primaryEmailAddress?.emailAddress ?? + clerkUser.emailAddresses[0]?.emailAddress ?? + null; + } catch (err) { + if (existing) return existing; + throw err; + } + + const [row] = await db + .insert(users) + .values({ id: userId, email, username }) + .onConflictDoUpdate({ + target: users.id, + set: { + email, + username, + // A real sign-in proves the account exists again. + isPlaceholder: false, + identitySyncedAt: new Date(), + updatedAt: new Date(), + }, + }) + .returning(); + + return row!; +} + +/** Best-effort display handle, used for auto-naming a new huddle. */ +export function displayHandle(user: User): string { + return user.username ?? user.email?.split("@")[0] ?? "Your"; +} + +export async function setSleeperLink( + userId: string, + opts: { sleeperUsername: string | null; sleeperUserId: string | null }, +): Promise { + const [row] = await db + .update(users) + .set({ + sleeperUsername: opts.sleeperUsername, + sleeperUserId: opts.sleeperUserId, + // Unlinking clears the synced leagues so re-linking starts fresh. + ...(opts.sleeperUsername === null ? { syncedLeagueIds: [] } : {}), + updatedAt: new Date(), + }) + .where(eq(users.id, userId)) + .returning(); + return row!; +} + +export async function setSyncedLeagues( + userId: string, + syncedLeagueIds: string[], +): Promise { + const [row] = await db + .update(users) + .set({ syncedLeagueIds, updatedAt: new Date() }) + .where(eq(users.id, userId)) + .returning(); + return row!; +} From 692a3f0afb10c2becbbd641f3d40a94927017232 Mon Sep 17 00:00:00 2001 From: Brandon Pham Date: Sun, 9 Aug 2026 18:19:02 -0500 Subject: [PATCH 2/4] refactor(db): make the huddle_ prefix mean something, before there are users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prefix looked like it meant "scoped to a huddle" but was wrong in both directions: team_claims and side_bets carried huddle_id without it, while the seven poll/survey child tables carried it without a huddle_id. ~80% consistent is worse than no convention — it invites an inference that doesn't hold. Settles on: no prefix = global, huddle_ = lives inside one huddle, directly or transitively. That makes `users` (and future auth tables) the meaningful exception rather than an eighteenth arbitrary case. - 0013: rename team_claims -> huddle_team_claims, side_bets -> huddle_side_bets (real ALTER TABLE ... RENAME, resolved through drizzle-kit's TTY prompt, so rows are preserved rather than dropped and recreated). - 0013: huddle_active_trophies.enabled from integer 1/0 to boolean — the schema uses boolean() in 8 other places. Drizzle's bare SET DATA TYPE would have failed; hand-edited to DROP DEFAULT / SET DATA TYPE ... USING / SET DEFAULT. - 0014: rename the leftover primary keys. Postgres keeps constraint names across a table rename and Drizzle doesn't track them, so groups_pkey had survived since 0001 and 0013 had just created two more of the same. Every PK now matches its table. - Drop the 1/0 translation in trophyControlService; the API already spoke boolean, only storage didn't. - Point backfill-users.mjs at the renamed tables. Verified on the dev DB: 3 team-claim rows survived the rename, enabled is boolean defaulting true, all 23 PKs match their table, 18 FKs intact, and the services reading these tables plus a live trophy toggle all still work. Co-Authored-By: Claude Opus 5 --- PLAYBOOK.md | 12 +- server/drizzle/0013_hot_cannonball.sql | 48 + server/drizzle/0014_rename_legacy_pkeys.sql | 10 + server/drizzle/meta/0013_snapshot.json | 2644 +++++++++++++++++++ server/drizzle/meta/0014_snapshot.json | 2644 +++++++++++++++++++ server/drizzle/meta/_journal.json | 14 + server/scripts/backfill-users.mjs | 10 +- server/src/db/schema.ts | 32 +- server/src/services/huddlesService.ts | 90 +- server/src/services/sideBetsService.ts | 36 +- server/src/services/trophyControlService.ts | 6 +- 11 files changed, 5458 insertions(+), 88 deletions(-) create mode 100644 server/drizzle/0013_hot_cannonball.sql create mode 100644 server/drizzle/0014_rename_legacy_pkeys.sql create mode 100644 server/drizzle/meta/0013_snapshot.json create mode 100644 server/drizzle/meta/0014_snapshot.json diff --git a/PLAYBOOK.md b/PLAYBOOK.md index d193b5f..cff075f 100644 --- a/PLAYBOOK.md +++ b/PLAYBOOK.md @@ -520,9 +520,19 @@ This replaces the old `psql -f ...` workflow — no `psql` install required, and | Renaming a column | Drizzle sees drop + add and generates `DROP`/`ADD`, silently discarding data. Resolve as a rename, or hand-edit to `ALTER ... RENAME`. | | Adding `NOT NULL` | Fails on a populated table unless you give it a default. | | Removing an enum value | Postgres can add values but not drop them; requires recreating the type. | +| Changing a column's type | Drizzle emits a bare `SET DATA TYPE`, which Postgres rejects whenever no implicit cast exists (e.g. `integer` → `boolean` in `0013`). Hand-edit to `DROP DEFAULT` → `SET DATA TYPE ... USING (...)` → `SET DEFAULT`. | +| Renaming a table | Needs a TTY — `drizzle-kit generate` prompts "created or renamed?", and answering "create" silently produces a `DROP`/`CREATE` that discards the data. Constraint names don't follow the rename; see "Table naming". | | Deploys | Never auto-migrate on boot — Vercel serverless would race on every cold start. Migrations are always run deliberately. | -Legacy artifact: the primary key on `huddles` is still named `groups_pkey`, because `0001` renamed the table but Postgres keeps constraint names. Harmless; rename if you're ever touching that table. +### Table naming + +> **No prefix = global. `huddle_` = lives inside one huddle**, directly or transitively. + +`users` (and any future `sessions`/`accounts`) are global. Everything else is huddle-scoped and prefixed — including tables like `huddle_poll_options` and `huddle_survey_answers`, which reach a huddle through their parent rather than carrying a `huddle_id` of their own. + +Two tables were renamed into this rule in `0013` (`team_claims` → `huddle_team_claims`, `side_bets` → `huddle_side_bets`), which is why the prefix is now worth trusting: before that it was ~80% consistent, which is worse than no convention, because it invited an inference that was wrong in both directions. + +**Postgres keeps constraint names across `ALTER TABLE ... RENAME TO`,** and Drizzle doesn't track them — so a table rename silently leaves `oldname_pkey` behind and it will never appear in a generated diff. `0014` cleaned up the three that had accumulated (`groups_pkey` from the original `groups` → `huddles` rename in `0001`, plus the two from `0013`). If you rename a table, add an `ALTER INDEX ... RENAME` alongside it, via `npx drizzle-kit generate --custom`. --- diff --git a/server/drizzle/0013_hot_cannonball.sql b/server/drizzle/0013_hot_cannonball.sql new file mode 100644 index 0000000..d359bcc --- /dev/null +++ b/server/drizzle/0013_hot_cannonball.sql @@ -0,0 +1,48 @@ +ALTER TABLE "side_bets" RENAME TO "huddle_side_bets";--> statement-breakpoint +ALTER TABLE "team_claims" RENAME TO "huddle_team_claims";--> statement-breakpoint +ALTER TABLE "huddle_side_bets" DROP CONSTRAINT "side_bets_huddle_id_huddles_id_fk"; +--> statement-breakpoint +ALTER TABLE "huddle_side_bets" DROP CONSTRAINT "side_bets_proposer_id_users_id_fk"; +--> statement-breakpoint +ALTER TABLE "huddle_side_bets" DROP CONSTRAINT "side_bets_opponent_id_users_id_fk"; +--> statement-breakpoint +ALTER TABLE "huddle_side_bets" DROP CONSTRAINT "side_bets_winner_id_users_id_fk"; +--> statement-breakpoint +ALTER TABLE "huddle_team_claims" DROP CONSTRAINT "team_claims_huddle_id_huddles_id_fk"; +--> statement-breakpoint +ALTER TABLE "huddle_team_claims" DROP CONSTRAINT "team_claims_user_id_users_id_fk"; +--> statement-breakpoint +ALTER TABLE "huddle_team_claims" DROP CONSTRAINT "team_claims_decided_by_users_id_fk"; +--> statement-breakpoint +DROP INDEX "side_bets_huddle_idx";--> statement-breakpoint +DROP INDEX "side_bets_proposer_idx";--> statement-breakpoint +DROP INDEX "side_bets_opponent_idx";--> statement-breakpoint +DROP INDEX "team_claims_huddle_roster_approved_uniq";--> statement-breakpoint +DROP INDEX "team_claims_huddle_user_approved_uniq";--> statement-breakpoint +DROP INDEX "team_claims_huddle_idx";--> statement-breakpoint +DROP INDEX "team_claims_user_idx";--> statement-breakpoint +-- Hand-edited: Drizzle emitted a bare `SET DATA TYPE boolean`, which Postgres +-- rejects — integer has no implicit cast to boolean, and the existing +-- `DEFAULT 1` can't be cast either. Drop the default, convert with an explicit +-- USING, then restore it. (PLAYBOOK, "Sharp edges".) +ALTER TABLE "huddle_active_trophies" ALTER COLUMN "enabled" DROP DEFAULT;--> statement-breakpoint +ALTER TABLE "huddle_active_trophies" ALTER COLUMN "enabled" SET DATA TYPE boolean USING ("enabled" <> 0);--> statement-breakpoint +ALTER TABLE "huddle_active_trophies" ALTER COLUMN "enabled" SET DEFAULT true;--> statement-breakpoint +-- Hand-added: legacy constraint name left behind when 0001 renamed `groups` +-- to `huddles`. Postgres keeps constraint names across a table rename, and +-- Drizzle doesn't track them, so this never appears in a generated diff. +ALTER INDEX IF EXISTS "groups_pkey" RENAME TO "huddles_pkey";--> statement-breakpoint +ALTER TABLE "huddle_side_bets" ADD CONSTRAINT "huddle_side_bets_huddle_id_huddles_id_fk" FOREIGN KEY ("huddle_id") REFERENCES "public"."huddles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_side_bets" ADD CONSTRAINT "huddle_side_bets_proposer_id_users_id_fk" FOREIGN KEY ("proposer_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_side_bets" ADD CONSTRAINT "huddle_side_bets_opponent_id_users_id_fk" FOREIGN KEY ("opponent_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_side_bets" ADD CONSTRAINT "huddle_side_bets_winner_id_users_id_fk" FOREIGN KEY ("winner_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_team_claims" ADD CONSTRAINT "huddle_team_claims_huddle_id_huddles_id_fk" FOREIGN KEY ("huddle_id") REFERENCES "public"."huddles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_team_claims" ADD CONSTRAINT "huddle_team_claims_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "huddle_team_claims" ADD CONSTRAINT "huddle_team_claims_decided_by_users_id_fk" FOREIGN KEY ("decided_by") REFERENCES "public"."users"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "huddle_side_bets_huddle_idx" ON "huddle_side_bets" USING btree ("huddle_id");--> statement-breakpoint +CREATE INDEX "huddle_side_bets_proposer_idx" ON "huddle_side_bets" USING btree ("huddle_id","proposer_id");--> statement-breakpoint +CREATE INDEX "huddle_side_bets_opponent_idx" ON "huddle_side_bets" USING btree ("huddle_id","opponent_id");--> statement-breakpoint +CREATE UNIQUE INDEX "huddle_team_claims_huddle_roster_approved_uniq" ON "huddle_team_claims" USING btree ("huddle_id","roster_id") WHERE "huddle_team_claims"."status" = 'approved';--> statement-breakpoint +CREATE UNIQUE INDEX "huddle_team_claims_huddle_user_approved_uniq" ON "huddle_team_claims" USING btree ("huddle_id","user_id") WHERE "huddle_team_claims"."status" = 'approved';--> statement-breakpoint +CREATE INDEX "huddle_team_claims_huddle_idx" ON "huddle_team_claims" USING btree ("huddle_id");--> statement-breakpoint +CREATE INDEX "huddle_team_claims_user_idx" ON "huddle_team_claims" USING btree ("user_id"); \ No newline at end of file diff --git a/server/drizzle/0014_rename_legacy_pkeys.sql b/server/drizzle/0014_rename_legacy_pkeys.sql new file mode 100644 index 0000000..10eae54 --- /dev/null +++ b/server/drizzle/0014_rename_legacy_pkeys.sql @@ -0,0 +1,10 @@ +-- Custom migration (`drizzle-kit generate --custom`), so the snapshot is +-- written correctly even though Drizzle sees no schema change here. +-- +-- Postgres keeps constraint names across `ALTER TABLE ... RENAME TO`, so 0013's +-- table renames left the old names behind on the primary keys — the same wart +-- `groups_pkey` was on `huddles`. Drizzle doesn't track PK constraint names, so +-- these will never show up in a generated diff; they have to be renamed by hand +-- or not at all. +ALTER INDEX IF EXISTS "team_claims_pkey" RENAME TO "huddle_team_claims_pkey";--> statement-breakpoint +ALTER INDEX IF EXISTS "side_bets_pkey" RENAME TO "huddle_side_bets_pkey"; diff --git a/server/drizzle/meta/0013_snapshot.json b/server/drizzle/meta/0013_snapshot.json new file mode 100644 index 0000000..95be831 --- /dev/null +++ b/server/drizzle/meta/0013_snapshot.json @@ -0,0 +1,2644 @@ +{ + "id": "63c4d38b-4b6d-49ad-a885-3cc750ee2e2a", + "prevId": "48c4474c-9264-4e99-922a-8f1e3ed418a2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.huddle_active_trophies": { + "name": "huddle_active_trophies", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trophy_type": { + "name": "trophy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_active_trophies_huddle_id_huddles_id_fk": { + "name": "huddle_active_trophies_huddle_id_huddles_id_fk", + "tableFrom": "huddle_active_trophies", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "huddle_active_trophies_huddle_id_trophy_type_pk": { + "name": "huddle_active_trophies_huddle_id_trophy_type_pk", + "columns": [ + "huddle_id", + "trophy_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_announcements": { + "name": "huddle_announcements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_announcements_huddle_idx": { + "name": "huddle_announcements_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_announcements_huddle_id_huddles_id_fk": { + "name": "huddle_announcements_huddle_id_huddles_id_fk", + "tableFrom": "huddle_announcements", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_announcements_author_id_users_id_fk": { + "name": "huddle_announcements_author_id_users_id_fk", + "tableFrom": "huddle_announcements", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_awards": { + "name": "huddle_awards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "glyph": { + "name": "glyph", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_awards_huddle_idx": { + "name": "huddle_awards_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_awards_huddle_roster_idx": { + "name": "huddle_awards_huddle_roster_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_awards_huddle_id_huddles_id_fk": { + "name": "huddle_awards_huddle_id_huddles_id_fk", + "tableFrom": "huddle_awards", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_awards_granted_by_users_id_fk": { + "name": "huddle_awards_granted_by_users_id_fk", + "tableFrom": "huddle_awards", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_commissioners": { + "name": "huddle_commissioners", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_commissioners_user_idx": { + "name": "huddle_commissioners_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_commissioners_huddle_id_huddles_id_fk": { + "name": "huddle_commissioners_huddle_id_huddles_id_fk", + "tableFrom": "huddle_commissioners", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_commissioners_user_id_users_id_fk": { + "name": "huddle_commissioners_user_id_users_id_fk", + "tableFrom": "huddle_commissioners", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_commissioners_added_by_users_id_fk": { + "name": "huddle_commissioners_added_by_users_id_fk", + "tableFrom": "huddle_commissioners", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "huddle_commissioners_huddle_id_user_id_pk": { + "name": "huddle_commissioners_huddle_id_user_id_pk", + "columns": [ + "huddle_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_countdown_config": { + "name": "huddle_countdown_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_at": { + "name": "target_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_countdown_config_huddle_id_huddles_id_fk": { + "name": "huddle_countdown_config_huddle_id_huddles_id_fk", + "tableFrom": "huddle_countdown_config", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_dues_config": { + "name": "huddle_dues_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_dues_config_huddle_id_huddles_id_fk": { + "name": "huddle_dues_config_huddle_id_huddles_id_fk", + "tableFrom": "huddle_dues_config", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_dues_payments": { + "name": "huddle_dues_payments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "marked_by": { + "name": "marked_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_dues_payments_huddle_roster_uniq": { + "name": "huddle_dues_payments_huddle_roster_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_dues_payments_huddle_id_huddles_id_fk": { + "name": "huddle_dues_payments_huddle_id_huddles_id_fk", + "tableFrom": "huddle_dues_payments", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_dues_payments_marked_by_users_id_fk": { + "name": "huddle_dues_payments_marked_by_users_id_fk", + "tableFrom": "huddle_dues_payments", + "tableTo": "users", + "columnsFrom": [ + "marked_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_forum_replies": { + "name": "huddle_forum_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_forum_replies_topic_idx": { + "name": "huddle_forum_replies_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk": { + "name": "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "huddle_forum_topics", + "columnsFrom": [ + "topic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_forum_replies_huddle_id_huddles_id_fk": { + "name": "huddle_forum_replies_huddle_id_huddles_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_forum_replies_author_id_users_id_fk": { + "name": "huddle_forum_replies_author_id_users_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_forum_replies_deleted_by_users_id_fk": { + "name": "huddle_forum_replies_deleted_by_users_id_fk", + "tableFrom": "huddle_forum_replies", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_forum_topics": { + "name": "huddle_forum_topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_forum_topics_huddle_updated_idx": { + "name": "huddle_forum_topics_huddle_updated_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_forum_topics_huddle_id_huddles_id_fk": { + "name": "huddle_forum_topics_huddle_id_huddles_id_fk", + "tableFrom": "huddle_forum_topics", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_forum_topics_author_id_users_id_fk": { + "name": "huddle_forum_topics_author_id_users_id_fk", + "tableFrom": "huddle_forum_topics", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_forum_topics_deleted_by_users_id_fk": { + "name": "huddle_forum_topics_deleted_by_users_id_fk", + "tableFrom": "huddle_forum_topics", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_payout_entries": { + "name": "huddle_payout_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_payout_entries_huddle_idx": { + "name": "huddle_payout_entries_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_payout_entries_huddle_id_huddles_id_fk": { + "name": "huddle_payout_entries_huddle_id_huddles_id_fk", + "tableFrom": "huddle_payout_entries", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_poll_options": { + "name": "huddle_poll_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_poll_options_poll_idx": { + "name": "huddle_poll_options_poll_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_poll_options_poll_id_huddle_polls_id_fk": { + "name": "huddle_poll_options_poll_id_huddle_polls_id_fk", + "tableFrom": "huddle_poll_options", + "tableTo": "huddle_polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_poll_votes": { + "name": "huddle_poll_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_poll_votes_poll_user_idx": { + "name": "huddle_poll_votes_poll_user_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_poll_votes_option_user_uniq": { + "name": "huddle_poll_votes_option_user_uniq", + "columns": [ + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_poll_votes_poll_id_huddle_polls_id_fk": { + "name": "huddle_poll_votes_poll_id_huddle_polls_id_fk", + "tableFrom": "huddle_poll_votes", + "tableTo": "huddle_polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_poll_votes_option_id_huddle_poll_options_id_fk": { + "name": "huddle_poll_votes_option_id_huddle_poll_options_id_fk", + "tableFrom": "huddle_poll_votes", + "tableTo": "huddle_poll_options", + "columnsFrom": [ + "option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_poll_votes_user_id_users_id_fk": { + "name": "huddle_poll_votes_user_id_users_id_fk", + "tableFrom": "huddle_poll_votes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_polls": { + "name": "huddle_polls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_dashboard_poll": { + "name": "is_dashboard_poll", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_multiple": { + "name": "allow_multiple", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_vote_changes": { + "name": "allow_vote_changes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "results_visibility": { + "name": "results_visibility", + "type": "poll_results_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_polls_topic_idx": { + "name": "huddle_polls_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_polls_dashboard_active_uniq": { + "name": "huddle_polls_dashboard_active_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"huddle_polls\".\"is_dashboard_poll\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_polls_huddle_id_huddles_id_fk": { + "name": "huddle_polls_huddle_id_huddles_id_fk", + "tableFrom": "huddle_polls", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_polls_author_id_users_id_fk": { + "name": "huddle_polls_author_id_users_id_fk", + "tableFrom": "huddle_polls", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_polls_topic_id_huddle_forum_topics_id_fk": { + "name": "huddle_polls_topic_id_huddle_forum_topics_id_fk", + "tableFrom": "huddle_polls", + "tableTo": "huddle_forum_topics", + "columnsFrom": [ + "topic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_side_bets": { + "name": "huddle_side_bets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proposer_id": { + "name": "proposer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opponent_id": { + "name": "opponent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposer_roster_id": { + "name": "proposer_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "opponent_roster_id": { + "name": "opponent_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "week": { + "name": "week", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "prize_description": { + "name": "prize_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "side_bet_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "winner_id": { + "name": "winner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settlement_note": { + "name": "settlement_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_side_bets_huddle_idx": { + "name": "huddle_side_bets_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_side_bets_proposer_idx": { + "name": "huddle_side_bets_proposer_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "proposer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_side_bets_opponent_idx": { + "name": "huddle_side_bets_opponent_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "opponent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_side_bets_huddle_id_huddles_id_fk": { + "name": "huddle_side_bets_huddle_id_huddles_id_fk", + "tableFrom": "huddle_side_bets", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_side_bets_proposer_id_users_id_fk": { + "name": "huddle_side_bets_proposer_id_users_id_fk", + "tableFrom": "huddle_side_bets", + "tableTo": "users", + "columnsFrom": [ + "proposer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_side_bets_opponent_id_users_id_fk": { + "name": "huddle_side_bets_opponent_id_users_id_fk", + "tableFrom": "huddle_side_bets", + "tableTo": "users", + "columnsFrom": [ + "opponent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_side_bets_winner_id_users_id_fk": { + "name": "huddle_side_bets_winner_id_users_id_fk", + "tableFrom": "huddle_side_bets", + "tableTo": "users", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_answer_options": { + "name": "huddle_survey_answer_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_survey_answer_options_question_idx": { + "name": "huddle_survey_answer_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_survey_answer_options_response_option_uniq": { + "name": "huddle_survey_answer_options_response_option_uniq", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_answer_options_response_id_huddle_survey_responses_id_fk": { + "name": "huddle_survey_answer_options_response_id_huddle_survey_responses_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_responses", + "columnsFrom": [ + "response_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answer_options_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_answer_options_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answer_options_option_id_huddle_survey_options_id_fk": { + "name": "huddle_survey_answer_options_option_id_huddle_survey_options_id_fk", + "tableFrom": "huddle_survey_answer_options", + "tableTo": "huddle_survey_options", + "columnsFrom": [ + "option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_answers": { + "name": "huddle_survey_answers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "text_value": { + "name": "text_value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_survey_answers_response_idx": { + "name": "huddle_survey_answers_response_idx", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_survey_answers_question_idx": { + "name": "huddle_survey_answers_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_answers_response_id_huddle_survey_responses_id_fk": { + "name": "huddle_survey_answers_response_id_huddle_survey_responses_id_fk", + "tableFrom": "huddle_survey_answers", + "tableTo": "huddle_survey_responses", + "columnsFrom": [ + "response_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_answers_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_answers_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_answers", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_options": { + "name": "huddle_survey_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_survey_options_question_idx": { + "name": "huddle_survey_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_options_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_options_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_options", + "tableTo": "huddle_survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_questions": { + "name": "huddle_survey_questions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "survey_question_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_survey_questions_survey_idx": { + "name": "huddle_survey_questions_survey_idx", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_questions_survey_id_huddle_surveys_id_fk": { + "name": "huddle_survey_questions_survey_id_huddle_surveys_id_fk", + "tableFrom": "huddle_survey_questions", + "tableTo": "huddle_surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_responses": { + "name": "huddle_survey_responses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_survey_responses_survey_user_uniq": { + "name": "huddle_survey_responses_survey_user_uniq", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_survey_responses_survey_id_huddle_surveys_id_fk": { + "name": "huddle_survey_responses_survey_id_huddle_surveys_id_fk", + "tableFrom": "huddle_survey_responses", + "tableTo": "huddle_surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_survey_responses_user_id_users_id_fk": { + "name": "huddle_survey_responses_user_id_users_id_fk", + "tableFrom": "huddle_survey_responses", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_surveys": { + "name": "huddle_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "results_published": { + "name": "results_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_publish_on_close": { + "name": "auto_publish_on_close", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "anonymity": { + "name": "anonymity", + "type": "survey_anonymity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_surveys_huddle_created_idx": { + "name": "huddle_surveys_huddle_created_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_surveys_huddle_id_huddles_id_fk": { + "name": "huddle_surveys_huddle_id_huddles_id_fk", + "tableFrom": "huddle_surveys", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_surveys_author_id_users_id_fk": { + "name": "huddle_surveys_author_id_users_id_fk", + "tableFrom": "huddle_surveys", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_team_claims": { + "name": "huddle_team_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "claim_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_team_claims_huddle_roster_approved_uniq": { + "name": "huddle_team_claims_huddle_roster_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"huddle_team_claims\".\"status\" = 'approved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_team_claims_huddle_user_approved_uniq": { + "name": "huddle_team_claims_huddle_user_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"huddle_team_claims\".\"status\" = 'approved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_team_claims_huddle_idx": { + "name": "huddle_team_claims_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddle_team_claims_user_idx": { + "name": "huddle_team_claims_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "huddle_team_claims_huddle_id_huddles_id_fk": { + "name": "huddle_team_claims_huddle_id_huddles_id_fk", + "tableFrom": "huddle_team_claims", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "huddle_team_claims_user_id_users_id_fk": { + "name": "huddle_team_claims_user_id_users_id_fk", + "tableFrom": "huddle_team_claims", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "huddle_team_claims_decided_by_users_id_fk": { + "name": "huddle_team_claims_decided_by_users_id_fk", + "tableFrom": "huddle_team_claims", + "tableTo": "users", + "columnsFrom": [ + "decided_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddles": { + "name": "huddles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_provider": { + "name": "league_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "league_id": { + "name": "league_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code_updated_at": { + "name": "invite_code_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invite_link_token": { + "name": "invite_link_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "invite_link_expires_at": { + "name": "invite_link_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddles_invite_code_uniq": { + "name": "huddles_invite_code_uniq", + "columns": [ + { + "expression": "invite_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddles_invite_link_token_uniq": { + "name": "huddles_invite_link_token_uniq", + "columns": [ + { + "expression": "invite_link_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_username": { + "name": "sleeper_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_user_id": { + "name": "sleeper_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "synced_league_ids": { + "name": "synced_league_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_placeholder": { + "name": "is_placeholder", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "identity_synced_at": { + "name": "identity_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_username_idx": { + "name": "users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.claim_status": { + "name": "claim_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected" + ] + }, + "public.poll_results_visibility": { + "name": "poll_results_visibility", + "schema": "public", + "values": [ + "always", + "after_vote", + "after_close" + ] + }, + "public.side_bet_status": { + "name": "side_bet_status", + "schema": "public", + "values": [ + "pending", + "accepted", + "rejected", + "cancelled", + "settled" + ] + }, + "public.survey_anonymity": { + "name": "survey_anonymity", + "schema": "public", + "values": [ + "none", + "anonymous_to_league", + "anonymous_to_all" + ] + }, + "public.survey_question_type": { + "name": "survey_question_type", + "schema": "public", + "values": [ + "short_text", + "paragraph", + "multiple_choice", + "checkboxes" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/0014_snapshot.json b/server/drizzle/meta/0014_snapshot.json new file mode 100644 index 0000000..b62dbe0 --- /dev/null +++ b/server/drizzle/meta/0014_snapshot.json @@ -0,0 +1,2644 @@ +{ + "id": "f9994cad-9a04-4600-976e-240953d6631c", + "prevId": "63c4d38b-4b6d-49ad-a885-3cc750ee2e2a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.huddle_active_trophies": { + "name": "huddle_active_trophies", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trophy_type": { + "name": "trophy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_active_trophies_huddle_id_huddles_id_fk": { + "name": "huddle_active_trophies_huddle_id_huddles_id_fk", + "tableFrom": "huddle_active_trophies", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "huddle_active_trophies_huddle_id_trophy_type_pk": { + "name": "huddle_active_trophies_huddle_id_trophy_type_pk", + "columns": [ + "huddle_id", + "trophy_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_announcements": { + "name": "huddle_announcements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_announcements_huddle_idx": { + "name": "huddle_announcements_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_announcements_huddle_id_huddles_id_fk": { + "name": "huddle_announcements_huddle_id_huddles_id_fk", + "tableFrom": "huddle_announcements", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_announcements_author_id_users_id_fk": { + "name": "huddle_announcements_author_id_users_id_fk", + "tableFrom": "huddle_announcements", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_awards": { + "name": "huddle_awards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "glyph": { + "name": "glyph", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_awards_huddle_idx": { + "name": "huddle_awards_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddle_awards_huddle_roster_idx": { + "name": "huddle_awards_huddle_roster_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_awards_huddle_id_huddles_id_fk": { + "name": "huddle_awards_huddle_id_huddles_id_fk", + "tableFrom": "huddle_awards", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_awards_granted_by_users_id_fk": { + "name": "huddle_awards_granted_by_users_id_fk", + "tableFrom": "huddle_awards", + "columnsFrom": [ + "granted_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_commissioners": { + "name": "huddle_commissioners", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_commissioners_user_idx": { + "name": "huddle_commissioners_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_commissioners_huddle_id_huddles_id_fk": { + "name": "huddle_commissioners_huddle_id_huddles_id_fk", + "tableFrom": "huddle_commissioners", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_commissioners_user_id_users_id_fk": { + "name": "huddle_commissioners_user_id_users_id_fk", + "tableFrom": "huddle_commissioners", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "huddle_commissioners_added_by_users_id_fk": { + "name": "huddle_commissioners_added_by_users_id_fk", + "tableFrom": "huddle_commissioners", + "columnsFrom": [ + "added_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": { + "huddle_commissioners_huddle_id_user_id_pk": { + "name": "huddle_commissioners_huddle_id_user_id_pk", + "columns": [ + "huddle_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_countdown_config": { + "name": "huddle_countdown_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_at": { + "name": "target_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_countdown_config_huddle_id_huddles_id_fk": { + "name": "huddle_countdown_config_huddle_id_huddles_id_fk", + "tableFrom": "huddle_countdown_config", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_dues_config": { + "name": "huddle_dues_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "huddle_dues_config_huddle_id_huddles_id_fk": { + "name": "huddle_dues_config_huddle_id_huddles_id_fk", + "tableFrom": "huddle_dues_config", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_dues_payments": { + "name": "huddle_dues_payments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "marked_by": { + "name": "marked_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_dues_payments_huddle_roster_uniq": { + "name": "huddle_dues_payments_huddle_roster_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_dues_payments_huddle_id_huddles_id_fk": { + "name": "huddle_dues_payments_huddle_id_huddles_id_fk", + "tableFrom": "huddle_dues_payments", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_dues_payments_marked_by_users_id_fk": { + "name": "huddle_dues_payments_marked_by_users_id_fk", + "tableFrom": "huddle_dues_payments", + "columnsFrom": [ + "marked_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_forum_replies": { + "name": "huddle_forum_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_forum_replies_topic_idx": { + "name": "huddle_forum_replies_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk": { + "name": "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk", + "tableFrom": "huddle_forum_replies", + "columnsFrom": [ + "topic_id" + ], + "tableTo": "huddle_forum_topics", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_forum_replies_huddle_id_huddles_id_fk": { + "name": "huddle_forum_replies_huddle_id_huddles_id_fk", + "tableFrom": "huddle_forum_replies", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_forum_replies_author_id_users_id_fk": { + "name": "huddle_forum_replies_author_id_users_id_fk", + "tableFrom": "huddle_forum_replies", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "huddle_forum_replies_deleted_by_users_id_fk": { + "name": "huddle_forum_replies_deleted_by_users_id_fk", + "tableFrom": "huddle_forum_replies", + "columnsFrom": [ + "deleted_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_forum_topics": { + "name": "huddle_forum_topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_forum_topics_huddle_updated_idx": { + "name": "huddle_forum_topics_huddle_updated_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_forum_topics_huddle_id_huddles_id_fk": { + "name": "huddle_forum_topics_huddle_id_huddles_id_fk", + "tableFrom": "huddle_forum_topics", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_forum_topics_author_id_users_id_fk": { + "name": "huddle_forum_topics_author_id_users_id_fk", + "tableFrom": "huddle_forum_topics", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "huddle_forum_topics_deleted_by_users_id_fk": { + "name": "huddle_forum_topics_deleted_by_users_id_fk", + "tableFrom": "huddle_forum_topics", + "columnsFrom": [ + "deleted_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_payout_entries": { + "name": "huddle_payout_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_payout_entries_huddle_idx": { + "name": "huddle_payout_entries_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_payout_entries_huddle_id_huddles_id_fk": { + "name": "huddle_payout_entries_huddle_id_huddles_id_fk", + "tableFrom": "huddle_payout_entries", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_poll_options": { + "name": "huddle_poll_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_poll_options_poll_idx": { + "name": "huddle_poll_options_poll_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_poll_options_poll_id_huddle_polls_id_fk": { + "name": "huddle_poll_options_poll_id_huddle_polls_id_fk", + "tableFrom": "huddle_poll_options", + "columnsFrom": [ + "poll_id" + ], + "tableTo": "huddle_polls", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_poll_votes": { + "name": "huddle_poll_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_poll_votes_poll_user_idx": { + "name": "huddle_poll_votes_poll_user_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddle_poll_votes_option_user_uniq": { + "name": "huddle_poll_votes_option_user_uniq", + "columns": [ + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_poll_votes_poll_id_huddle_polls_id_fk": { + "name": "huddle_poll_votes_poll_id_huddle_polls_id_fk", + "tableFrom": "huddle_poll_votes", + "columnsFrom": [ + "poll_id" + ], + "tableTo": "huddle_polls", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_poll_votes_option_id_huddle_poll_options_id_fk": { + "name": "huddle_poll_votes_option_id_huddle_poll_options_id_fk", + "tableFrom": "huddle_poll_votes", + "columnsFrom": [ + "option_id" + ], + "tableTo": "huddle_poll_options", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_poll_votes_user_id_users_id_fk": { + "name": "huddle_poll_votes_user_id_users_id_fk", + "tableFrom": "huddle_poll_votes", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_polls": { + "name": "huddle_polls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_dashboard_poll": { + "name": "is_dashboard_poll", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_multiple": { + "name": "allow_multiple", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_vote_changes": { + "name": "allow_vote_changes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "results_visibility": { + "name": "results_visibility", + "type": "poll_results_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_polls_topic_idx": { + "name": "huddle_polls_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddle_polls_dashboard_active_uniq": { + "name": "huddle_polls_dashboard_active_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"huddle_polls\".\"is_dashboard_poll\" = true", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_polls_huddle_id_huddles_id_fk": { + "name": "huddle_polls_huddle_id_huddles_id_fk", + "tableFrom": "huddle_polls", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_polls_author_id_users_id_fk": { + "name": "huddle_polls_author_id_users_id_fk", + "tableFrom": "huddle_polls", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "huddle_polls_topic_id_huddle_forum_topics_id_fk": { + "name": "huddle_polls_topic_id_huddle_forum_topics_id_fk", + "tableFrom": "huddle_polls", + "columnsFrom": [ + "topic_id" + ], + "tableTo": "huddle_forum_topics", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_side_bets": { + "name": "huddle_side_bets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proposer_id": { + "name": "proposer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opponent_id": { + "name": "opponent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposer_roster_id": { + "name": "proposer_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "opponent_roster_id": { + "name": "opponent_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "week": { + "name": "week", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "prize_description": { + "name": "prize_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "side_bet_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "winner_id": { + "name": "winner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settlement_note": { + "name": "settlement_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_side_bets_huddle_idx": { + "name": "huddle_side_bets_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddle_side_bets_proposer_idx": { + "name": "huddle_side_bets_proposer_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "proposer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddle_side_bets_opponent_idx": { + "name": "huddle_side_bets_opponent_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "opponent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_side_bets_huddle_id_huddles_id_fk": { + "name": "huddle_side_bets_huddle_id_huddles_id_fk", + "tableFrom": "huddle_side_bets", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_side_bets_proposer_id_users_id_fk": { + "name": "huddle_side_bets_proposer_id_users_id_fk", + "tableFrom": "huddle_side_bets", + "columnsFrom": [ + "proposer_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "huddle_side_bets_opponent_id_users_id_fk": { + "name": "huddle_side_bets_opponent_id_users_id_fk", + "tableFrom": "huddle_side_bets", + "columnsFrom": [ + "opponent_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "huddle_side_bets_winner_id_users_id_fk": { + "name": "huddle_side_bets_winner_id_users_id_fk", + "tableFrom": "huddle_side_bets", + "columnsFrom": [ + "winner_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_answer_options": { + "name": "huddle_survey_answer_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_survey_answer_options_question_idx": { + "name": "huddle_survey_answer_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddle_survey_answer_options_response_option_uniq": { + "name": "huddle_survey_answer_options_response_option_uniq", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_survey_answer_options_response_id_huddle_survey_responses_id_fk": { + "name": "huddle_survey_answer_options_response_id_huddle_survey_responses_id_fk", + "tableFrom": "huddle_survey_answer_options", + "columnsFrom": [ + "response_id" + ], + "tableTo": "huddle_survey_responses", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_survey_answer_options_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_answer_options_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_answer_options", + "columnsFrom": [ + "question_id" + ], + "tableTo": "huddle_survey_questions", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_survey_answer_options_option_id_huddle_survey_options_id_fk": { + "name": "huddle_survey_answer_options_option_id_huddle_survey_options_id_fk", + "tableFrom": "huddle_survey_answer_options", + "columnsFrom": [ + "option_id" + ], + "tableTo": "huddle_survey_options", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_answers": { + "name": "huddle_survey_answers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "text_value": { + "name": "text_value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "huddle_survey_answers_response_idx": { + "name": "huddle_survey_answers_response_idx", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddle_survey_answers_question_idx": { + "name": "huddle_survey_answers_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_survey_answers_response_id_huddle_survey_responses_id_fk": { + "name": "huddle_survey_answers_response_id_huddle_survey_responses_id_fk", + "tableFrom": "huddle_survey_answers", + "columnsFrom": [ + "response_id" + ], + "tableTo": "huddle_survey_responses", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_survey_answers_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_answers_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_answers", + "columnsFrom": [ + "question_id" + ], + "tableTo": "huddle_survey_questions", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_options": { + "name": "huddle_survey_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_survey_options_question_idx": { + "name": "huddle_survey_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_survey_options_question_id_huddle_survey_questions_id_fk": { + "name": "huddle_survey_options_question_id_huddle_survey_questions_id_fk", + "tableFrom": "huddle_survey_options", + "columnsFrom": [ + "question_id" + ], + "tableTo": "huddle_survey_questions", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_questions": { + "name": "huddle_survey_questions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "survey_question_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "huddle_survey_questions_survey_idx": { + "name": "huddle_survey_questions_survey_idx", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_survey_questions_survey_id_huddle_surveys_id_fk": { + "name": "huddle_survey_questions_survey_id_huddle_surveys_id_fk", + "tableFrom": "huddle_survey_questions", + "columnsFrom": [ + "survey_id" + ], + "tableTo": "huddle_surveys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_survey_responses": { + "name": "huddle_survey_responses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_survey_responses_survey_user_uniq": { + "name": "huddle_survey_responses_survey_user_uniq", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_survey_responses_survey_id_huddle_surveys_id_fk": { + "name": "huddle_survey_responses_survey_id_huddle_surveys_id_fk", + "tableFrom": "huddle_survey_responses", + "columnsFrom": [ + "survey_id" + ], + "tableTo": "huddle_surveys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_survey_responses_user_id_users_id_fk": { + "name": "huddle_survey_responses_user_id_users_id_fk", + "tableFrom": "huddle_survey_responses", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_surveys": { + "name": "huddle_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "results_published": { + "name": "results_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_publish_on_close": { + "name": "auto_publish_on_close", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "anonymity": { + "name": "anonymity", + "type": "survey_anonymity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddle_surveys_huddle_created_idx": { + "name": "huddle_surveys_huddle_created_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_surveys_huddle_id_huddles_id_fk": { + "name": "huddle_surveys_huddle_id_huddles_id_fk", + "tableFrom": "huddle_surveys", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_surveys_author_id_users_id_fk": { + "name": "huddle_surveys_author_id_users_id_fk", + "tableFrom": "huddle_surveys", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddle_team_claims": { + "name": "huddle_team_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "claim_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "huddle_team_claims_huddle_roster_approved_uniq": { + "name": "huddle_team_claims_huddle_roster_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"huddle_team_claims\".\"status\" = 'approved'", + "concurrently": false + }, + "huddle_team_claims_huddle_user_approved_uniq": { + "name": "huddle_team_claims_huddle_user_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"huddle_team_claims\".\"status\" = 'approved'", + "concurrently": false + }, + "huddle_team_claims_huddle_idx": { + "name": "huddle_team_claims_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddle_team_claims_user_idx": { + "name": "huddle_team_claims_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "huddle_team_claims_huddle_id_huddles_id_fk": { + "name": "huddle_team_claims_huddle_id_huddles_id_fk", + "tableFrom": "huddle_team_claims", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "huddle_team_claims_user_id_users_id_fk": { + "name": "huddle_team_claims_user_id_users_id_fk", + "tableFrom": "huddle_team_claims", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "huddle_team_claims_decided_by_users_id_fk": { + "name": "huddle_team_claims_decided_by_users_id_fk", + "tableFrom": "huddle_team_claims", + "columnsFrom": [ + "decided_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddles": { + "name": "huddles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_provider": { + "name": "league_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "league_id": { + "name": "league_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code_updated_at": { + "name": "invite_code_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invite_link_token": { + "name": "invite_link_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "invite_link_expires_at": { + "name": "invite_link_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddles_invite_code_uniq": { + "name": "huddles_invite_code_uniq", + "columns": [ + { + "expression": "invite_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddles_invite_link_token_uniq": { + "name": "huddles_invite_link_token_uniq", + "columns": [ + { + "expression": "invite_link_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_username": { + "name": "sleeper_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_user_id": { + "name": "sleeper_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "synced_league_ids": { + "name": "synced_league_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_placeholder": { + "name": "is_placeholder", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "identity_synced_at": { + "name": "identity_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "users_username_idx": { + "name": "users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.claim_status": { + "name": "claim_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected" + ] + }, + "public.poll_results_visibility": { + "name": "poll_results_visibility", + "schema": "public", + "values": [ + "always", + "after_vote", + "after_close" + ] + }, + "public.side_bet_status": { + "name": "side_bet_status", + "schema": "public", + "values": [ + "pending", + "accepted", + "rejected", + "cancelled", + "settled" + ] + }, + "public.survey_anonymity": { + "name": "survey_anonymity", + "schema": "public", + "values": [ + "none", + "anonymous_to_league", + "anonymous_to_all" + ] + }, + "public.survey_question_type": { + "name": "survey_question_type", + "schema": "public", + "values": [ + "short_text", + "paragraph", + "multiple_choice", + "checkboxes" + ] + } + }, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/_journal.json b/server/drizzle/meta/_journal.json index a23a422..05bed97 100644 --- a/server/drizzle/meta/_journal.json +++ b/server/drizzle/meta/_journal.json @@ -92,6 +92,20 @@ "when": 1786315059916, "tag": "0012_green_valkyrie", "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1786317198957, + "tag": "0013_hot_cannonball", + "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1786317326520, + "tag": "0014_rename_legacy_pkeys", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/scripts/backfill-users.mjs b/server/scripts/backfill-users.mjs index 0248b84..05a4d1e 100644 --- a/server/scripts/backfill-users.mjs +++ b/server/scripts/backfill-users.mjs @@ -41,14 +41,14 @@ const clerk = createClerkClient({ secretKey }); const USER_ID_COLUMNS = [ ["huddle_commissioners", "user_id"], ["huddle_commissioners", "added_by"], - ["team_claims", "user_id"], - ["team_claims", "decided_by"], + ["huddle_team_claims", "user_id"], + ["huddle_team_claims", "decided_by"], ["huddle_announcements", "author_id"], ["huddle_dues_payments", "marked_by"], ["huddle_awards", "granted_by"], - ["side_bets", "proposer_id"], - ["side_bets", "opponent_id"], - ["side_bets", "winner_id"], + ["huddle_side_bets", "proposer_id"], + ["huddle_side_bets", "opponent_id"], + ["huddle_side_bets", "winner_id"], ["huddle_forum_topics", "author_id"], ["huddle_forum_topics", "deleted_by"], ["huddle_forum_replies", "author_id"], diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index d0d2b72..5e5251e 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -132,8 +132,8 @@ export const huddleCommissioners = pgTable( }), ); -export const teamClaims = pgTable( - "team_claims", +export const huddleTeamClaims = pgTable( + "huddle_team_claims", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -154,14 +154,14 @@ export const teamClaims = pgTable( }), }, (t) => ({ - uniqApprovedRoster: uniqueIndex("team_claims_huddle_roster_approved_uniq") + uniqApprovedRoster: uniqueIndex("huddle_team_claims_huddle_roster_approved_uniq") .on(t.huddleId, t.rosterId) .where(sql`${t.status} = 'approved'`), - uniqApprovedUser: uniqueIndex("team_claims_huddle_user_approved_uniq") + uniqApprovedUser: uniqueIndex("huddle_team_claims_huddle_user_approved_uniq") .on(t.huddleId, t.userId) .where(sql`${t.status} = 'approved'`), - byHuddle: index("team_claims_huddle_idx").on(t.huddleId), - byUser: index("team_claims_user_idx").on(t.userId), + byHuddle: index("huddle_team_claims_huddle_idx").on(t.huddleId), + byUser: index("huddle_team_claims_user_idx").on(t.userId), }), ); @@ -233,8 +233,8 @@ export const huddleDuesPayments = pgTable( export type Huddle = typeof huddles.$inferSelect; export type NewHuddle = typeof huddles.$inferInsert; export type HuddleCommissioner = typeof huddleCommissioners.$inferSelect; -export type TeamClaim = typeof teamClaims.$inferSelect; -export type NewTeamClaim = typeof teamClaims.$inferInsert; +export type TeamClaim = typeof huddleTeamClaims.$inferSelect; +export type NewTeamClaim = typeof huddleTeamClaims.$inferInsert; export type HuddleAnnouncement = typeof huddleAnnouncements.$inferSelect; export type NewHuddleAnnouncement = typeof huddleAnnouncements.$inferInsert; export type HuddleDuesConfig = typeof huddleDuesConfig.$inferSelect; @@ -314,7 +314,7 @@ export const huddleActiveTrophies = pgTable( .references(() => huddles.id, { onDelete: "cascade" }), /** Built-in trophy type key, e.g. "champion", "high_score". */ trophyType: text("trophy_type").notNull(), - enabled: integer("enabled").notNull().default(1), // 1 = on, 0 = off + enabled: boolean("enabled").notNull().default(true), updatedAt: timestamp("updated_at", { withTimezone: true }) .defaultNow() .notNull(), @@ -338,8 +338,8 @@ export const sideBetStatus = pgEnum("side_bet_status", [ "settled", // winner determined ]); -export const sideBets = pgTable( - "side_bets", +export const huddleSideBets = pgTable( + "huddle_side_bets", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -373,14 +373,14 @@ export const sideBets = pgTable( .notNull(), }, (t) => ({ - byHuddle: index("side_bets_huddle_idx").on(t.huddleId), - byProposer: index("side_bets_proposer_idx").on(t.huddleId, t.proposerId), - byOpponent: index("side_bets_opponent_idx").on(t.huddleId, t.opponentId), + byHuddle: index("huddle_side_bets_huddle_idx").on(t.huddleId), + byProposer: index("huddle_side_bets_proposer_idx").on(t.huddleId, t.proposerId), + byOpponent: index("huddle_side_bets_opponent_idx").on(t.huddleId, t.opponentId), }), ); -export type SideBet = typeof sideBets.$inferSelect; -export type NewSideBet = typeof sideBets.$inferInsert; +export type SideBet = typeof huddleSideBets.$inferSelect; +export type NewSideBet = typeof huddleSideBets.$inferInsert; // ── Countdown widget ───────────────────────────────────────────────────────── // Commissioner-configured countdown shown on the dashboard, e.g. counting down diff --git a/server/src/services/huddlesService.ts b/server/src/services/huddlesService.ts index ddec00a..16aa5ef 100644 --- a/server/src/services/huddlesService.ts +++ b/server/src/services/huddlesService.ts @@ -5,7 +5,7 @@ import { huddles, huddleCommissioners, huddleAnnouncements, - teamClaims, + huddleTeamClaims, type Huddle, type HuddleCommissioner, type HuddleAnnouncement, @@ -98,12 +98,12 @@ export async function isCommissioner( export async function hasApprovedClaim(huddleId: string, userId: string): Promise { const rows = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.huddleId, huddleId), - eq(teamClaims.userId, userId), - eq(teamClaims.status, "approved"), + eq(huddleTeamClaims.huddleId, huddleId), + eq(huddleTeamClaims.userId, userId), + eq(huddleTeamClaims.status, "approved"), ), ) .limit(1); @@ -165,12 +165,12 @@ export async function listHuddlesForUser( .from(huddleCommissioners) .where(eq(huddleCommissioners.userId, userId)), db - .select({ huddleId: teamClaims.huddleId, status: teamClaims.status }) - .from(teamClaims) + .select({ huddleId: huddleTeamClaims.huddleId, status: huddleTeamClaims.status }) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.userId, userId), - inArray(teamClaims.status, ["approved", "pending"]), + eq(huddleTeamClaims.userId, userId), + inArray(huddleTeamClaims.status, ["approved", "pending"]), ), ), ]); @@ -255,7 +255,7 @@ export async function getHuddleByInviteCode( export async function listClaimsForHuddle( huddleId: string, ): Promise { - return db.select().from(teamClaims).where(eq(teamClaims.huddleId, huddleId)); + return db.select().from(huddleTeamClaims).where(eq(huddleTeamClaims.huddleId, huddleId)); } // ---- Claims ---- @@ -288,12 +288,12 @@ export async function submitClaim(opts: { const existingApproved = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.huddleId, opts.huddleId), - eq(teamClaims.userId, opts.userId), - eq(teamClaims.status, "approved"), + eq(huddleTeamClaims.huddleId, opts.huddleId), + eq(huddleTeamClaims.userId, opts.userId), + eq(huddleTeamClaims.status, "approved"), ), ) .limit(1); @@ -302,12 +302,12 @@ export async function submitClaim(opts: { const existingPending = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.huddleId, opts.huddleId), - eq(teamClaims.userId, opts.userId), - eq(teamClaims.status, "pending"), + eq(huddleTeamClaims.huddleId, opts.huddleId), + eq(huddleTeamClaims.userId, opts.userId), + eq(huddleTeamClaims.status, "pending"), ), ) .limit(1); @@ -316,19 +316,19 @@ export async function submitClaim(opts: { const rosterTaken = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.huddleId, opts.huddleId), - eq(teamClaims.rosterId, opts.rosterId as number), - eq(teamClaims.status, "approved"), + eq(huddleTeamClaims.huddleId, opts.huddleId), + eq(huddleTeamClaims.rosterId, opts.rosterId as number), + eq(huddleTeamClaims.status, "approved"), ), ) .limit(1); if (rosterTaken.length > 0) fail(409, "That team has already been claimed"); const [created] = await db - .insert(teamClaims) + .insert(huddleTeamClaims) .values({ huddleId: opts.huddleId, userId: opts.userId, @@ -352,11 +352,11 @@ export async function decideClaim(opts: { const rows = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.id, opts.claimId), - eq(teamClaims.huddleId, opts.huddleId), + eq(huddleTeamClaims.id, opts.claimId), + eq(huddleTeamClaims.huddleId, opts.huddleId), ), ) .limit(1); @@ -367,12 +367,12 @@ export async function decideClaim(opts: { if (opts.decision === "approved") { const rosterTaken = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.huddleId, opts.huddleId), - eq(teamClaims.rosterId, claim!.rosterId), - eq(teamClaims.status, "approved"), + eq(huddleTeamClaims.huddleId, opts.huddleId), + eq(huddleTeamClaims.rosterId, claim!.rosterId), + eq(huddleTeamClaims.status, "approved"), ), ) .limit(1); @@ -380,13 +380,13 @@ export async function decideClaim(opts: { } const [updated] = await db - .update(teamClaims) + .update(huddleTeamClaims) .set({ status: opts.decision, decidedAt: new Date(), decidedBy: opts.decidedBy, }) - .where(eq(teamClaims.id, opts.claimId)) + .where(eq(huddleTeamClaims.id, opts.claimId)) .returning(); if (!updated) fail(500, "Failed to update claim"); return updated!; @@ -405,12 +405,12 @@ export async function addCommissioner(opts: { // Check the new commissioner has an approved claim const hasClaim = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.huddleId, opts.huddleId), - eq(teamClaims.userId, opts.newUserId), - eq(teamClaims.status, "approved"), + eq(huddleTeamClaims.huddleId, opts.huddleId), + eq(huddleTeamClaims.userId, opts.newUserId), + eq(huddleTeamClaims.status, "approved"), ), ) .limit(1); @@ -657,19 +657,19 @@ export async function unclaimTeam(opts: { }): Promise { const rows = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.id, opts.claimId), - eq(teamClaims.huddleId, opts.huddleId), - eq(teamClaims.userId, opts.userId), + eq(huddleTeamClaims.id, opts.claimId), + eq(huddleTeamClaims.huddleId, opts.huddleId), + eq(huddleTeamClaims.userId, opts.userId), ), ) .limit(1); if (!rows[0]) fail(404, "Claim not found"); if (rows[0].status === "rejected") fail(409, "Claim has already been rejected"); - await db.delete(teamClaims).where(eq(teamClaims.id, opts.claimId)); + await db.delete(huddleTeamClaims).where(eq(huddleTeamClaims.id, opts.claimId)); } // Commissioner force-remove: remove any claim regardless of owner @@ -682,11 +682,11 @@ export async function forceRemoveClaim(opts: { fail(403, "Only a commissioner can remove team assignments"); const rows = await db .select() - .from(teamClaims) + .from(huddleTeamClaims) .where( and( - eq(teamClaims.id, opts.claimId), - eq(teamClaims.huddleId, opts.huddleId), + eq(huddleTeamClaims.id, opts.claimId), + eq(huddleTeamClaims.huddleId, opts.huddleId), ), ) .limit(1); @@ -700,5 +700,5 @@ export async function forceRemoveClaim(opts: { "You can't unlink yourself while you're the only commissioner — assign another commissioner first", ); } - await db.delete(teamClaims).where(eq(teamClaims.id, opts.claimId)); + await db.delete(huddleTeamClaims).where(eq(huddleTeamClaims.id, opts.claimId)); } diff --git a/server/src/services/sideBetsService.ts b/server/src/services/sideBetsService.ts index f79d6d2..0b227ec 100644 --- a/server/src/services/sideBetsService.ts +++ b/server/src/services/sideBetsService.ts @@ -7,7 +7,7 @@ */ import { and, eq, or } from "drizzle-orm"; import { db } from "../db/client.js"; -import { sideBets, type SideBet } from "../db/schema.js"; +import { huddleSideBets, type SideBet } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner } from "./huddlesService.js"; const fail = (status: number, message: string): never => { @@ -21,13 +21,13 @@ export async function listBets( huddleId: string, week?: number, ): Promise { - const conditions = [eq(sideBets.huddleId, huddleId)]; - if (week !== undefined) conditions.push(eq(sideBets.week, week)); + const conditions = [eq(huddleSideBets.huddleId, huddleId)]; + if (week !== undefined) conditions.push(eq(huddleSideBets.week, week)); return db .select() - .from(sideBets) + .from(huddleSideBets) .where(and(...conditions)) - .orderBy(sideBets.createdAt); + .orderBy(huddleSideBets.createdAt); } // ── Mutations ────────────────────────────────────────────────────────────────── @@ -54,7 +54,7 @@ export async function proposeBet(opts: { fail(400, "a bet cannot have both a cash amount and a prize description"); const [row] = await db - .insert(sideBets) + .insert(huddleSideBets) .values({ huddleId: opts.huddleId, proposerId: opts.proposerId, @@ -79,8 +79,8 @@ export async function respondToBet(opts: { }): Promise { const [bet] = await db .select() - .from(sideBets) - .where(and(eq(sideBets.id, opts.betId), eq(sideBets.huddleId, opts.huddleId))) + .from(huddleSideBets) + .where(and(eq(huddleSideBets.id, opts.betId), eq(huddleSideBets.huddleId, opts.huddleId))) .limit(1); if (!bet) fail(404, "Bet not found"); @@ -88,14 +88,14 @@ export async function respondToBet(opts: { if (bet.status !== "pending") fail(409, `Bet is already ${bet.status}`); if (opts.response === "rejected") { - await db.delete(sideBets).where(eq(sideBets.id, opts.betId)); + await db.delete(huddleSideBets).where(eq(huddleSideBets.id, opts.betId)); return { ...bet, status: "rejected" }; } const [updated] = await db - .update(sideBets) + .update(huddleSideBets) .set({ status: "accepted", updatedAt: new Date() }) - .where(eq(sideBets.id, opts.betId)) + .where(eq(huddleSideBets.id, opts.betId)) .returning(); return updated!; } @@ -107,8 +107,8 @@ export async function cancelBet(opts: { }): Promise { const [bet] = await db .select() - .from(sideBets) - .where(and(eq(sideBets.id, opts.betId), eq(sideBets.huddleId, opts.huddleId))) + .from(huddleSideBets) + .where(and(eq(huddleSideBets.id, opts.betId), eq(huddleSideBets.huddleId, opts.huddleId))) .limit(1); if (!bet) fail(404, "Bet not found"); @@ -122,7 +122,7 @@ export async function cancelBet(opts: { if (bet.status === "pending" && bet.proposerId !== opts.userId && !commish) fail(403, "Only the proposer (or a commissioner) can cancel a pending bet"); - await db.delete(sideBets).where(eq(sideBets.id, opts.betId)); + await db.delete(huddleSideBets).where(eq(huddleSideBets.id, opts.betId)); return { ...bet, status: "cancelled" }; } @@ -135,8 +135,8 @@ export async function settleBet(opts: { }): Promise { const [bet] = await db .select() - .from(sideBets) - .where(and(eq(sideBets.id, opts.betId), eq(sideBets.huddleId, opts.huddleId))) + .from(huddleSideBets) + .where(and(eq(huddleSideBets.id, opts.betId), eq(huddleSideBets.huddleId, opts.huddleId))) .limit(1); if (!bet) fail(404, "Bet not found"); @@ -150,14 +150,14 @@ export async function settleBet(opts: { fail(400, "winnerId must be one of the two parties"); const [updated] = await db - .update(sideBets) + .update(huddleSideBets) .set({ status: "settled", winnerId: opts.winnerId, settlementNote: opts.settlementNote ?? null, updatedAt: new Date(), }) - .where(eq(sideBets.id, opts.betId)) + .where(eq(huddleSideBets.id, opts.betId)) .returning(); return updated!; } diff --git a/server/src/services/trophyControlService.ts b/server/src/services/trophyControlService.ts index 9ab7a7a..a26981f 100644 --- a/server/src/services/trophyControlService.ts +++ b/server/src/services/trophyControlService.ts @@ -44,7 +44,7 @@ export async function getActiveTrophies( } // Apply saved overrides for (const row of rows) { - result[row.trophyType] = row.enabled === 1; + result[row.trophyType] = row.enabled; } return result; } @@ -69,11 +69,11 @@ export async function setTrophyEnabled( .values({ huddleId, trophyType, - enabled: enabled ? 1 : 0, + enabled, updatedAt: new Date(), }) .onConflictDoUpdate({ target: [huddleActiveTrophies.huddleId, huddleActiveTrophies.trophyType], - set: { enabled: enabled ? 1 : 0, updatedAt: new Date() }, + set: { enabled, updatedAt: new Date() }, }); } From 6120cd066488b45a42697368adab13aa12b06155 Mon Sep 17 00:00:00 2001 From: Brandon Pham Date: Sun, 9 Aug 2026 18:29:53 -0500 Subject: [PATCH 3/4] refactor(db): drop the redundant huddle_ prefix from table names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverses the direction of 692a3f0, which made the prefix consistent when the right call was to remove it. Every table in this database belongs to Huddle, so the prefix carried no information — it just made `\dt` and autocomplete on `hud` return almost the entire schema. 21 tables renamed (huddle_commissioners -> commissioners, huddle_polls -> polls, ...). `huddles` and `users` are unchanged. team_claims and side_bets end up back at their original names, so 0013's renames were round-tripped; the rest of 0013 (the boolean conversion, the groups_pkey fix) still stands. The structural distinction the prefix pretended to encode isn't in the names and doesn't need to be: `users` is global, everything else is reachable from a huddle, directly or through a parent. Mechanics worth knowing (now in PLAYBOOK): - Postgres renames the table and nothing else, and Drizzle doesn't track constraint or index names, so they'd rot invisibly. 0015 renames 115 objects, generated from pg_constraint/pg_index against the live schema rather than by hand. - drizzle-kit can't be driven through 21 "created or renamed?" prompts reliably, so it was answered "create" — the snapshot records end state and is correct either way — and its destructive DROP/CREATE output replaced. - 0016 fixes a constraint name that had been silently truncated at Postgres's 63-byte limit when first created, losing its `_fk` suffix; 0015 carried the truncation forward. Caught by diffing the snapshot against pg_constraint, which is a stronger check than "no schema changes" from db:generate. Verified on the dev DB: row counts unchanged across the rename (3 claims, 3 huddles, 3 commissioners, 5 users), no table/index/constraint still carries the prefix, all 18 FKs to users intact, zero drift across all 77 FK and index names, and every service that reads these tables plus a live trophy toggle still works. Co-Authored-By: Claude Opus 5 --- PLAYBOOK.md | 15 +- server/drizzle/0015_drop_huddle_prefix.sql | 133 + server/drizzle/0016_fix_truncated_fk_name.sql | 12 + server/drizzle/meta/0015_snapshot.json | 2644 +++++++++++++++++ server/drizzle/meta/0016_snapshot.json | 2644 +++++++++++++++++ server/drizzle/meta/_journal.json | 14 + server/scripts/backfill-users.mjs | 36 +- server/src/db/schema.ts | 222 +- server/src/services/awardsService.ts | 36 +- server/src/services/countdownService.ts | 10 +- server/src/services/duesService.ts | 20 +- server/src/services/forumService.ts | 66 +- server/src/services/huddlesService.ts | 148 +- server/src/services/payoutsService.ts | 14 +- server/src/services/pollService.ts | 72 +- server/src/services/sideBetsService.ts | 36 +- server/src/services/surveyService.ts | 158 +- server/src/services/trophyControlService.ts | 10 +- 18 files changed, 5871 insertions(+), 419 deletions(-) create mode 100644 server/drizzle/0015_drop_huddle_prefix.sql create mode 100644 server/drizzle/0016_fix_truncated_fk_name.sql create mode 100644 server/drizzle/meta/0015_snapshot.json create mode 100644 server/drizzle/meta/0016_snapshot.json diff --git a/PLAYBOOK.md b/PLAYBOOK.md index cff075f..a059610 100644 --- a/PLAYBOOK.md +++ b/PLAYBOOK.md @@ -521,18 +521,23 @@ This replaces the old `psql -f ...` workflow — no `psql` install required, and | Adding `NOT NULL` | Fails on a populated table unless you give it a default. | | Removing an enum value | Postgres can add values but not drop them; requires recreating the type. | | Changing a column's type | Drizzle emits a bare `SET DATA TYPE`, which Postgres rejects whenever no implicit cast exists (e.g. `integer` → `boolean` in `0013`). Hand-edit to `DROP DEFAULT` → `SET DATA TYPE ... USING (...)` → `SET DEFAULT`. | -| Renaming a table | Needs a TTY — `drizzle-kit generate` prompts "created or renamed?", and answering "create" silently produces a `DROP`/`CREATE` that discards the data. Constraint names don't follow the rename; see "Table naming". | +| Renaming a table | Needs a TTY — `drizzle-kit generate` prompts "created or renamed?" per table, and answering "create" produces a `DROP`/`CREATE` that discards the data. For a bulk rename it's safer to answer "create" deliberately (the *snapshot* is correct either way, since it records end state) and then replace the generated SQL with `ALTER TABLE ... RENAME`. Constraint and index names don't follow the rename; see "Table naming". | | Deploys | Never auto-migrate on boot — Vercel serverless would race on every cold start. Migrations are always run deliberately. | ### Table naming -> **No prefix = global. `huddle_` = lives inside one huddle**, directly or transitively. +> **Tables are named for what they hold. No product-name prefix.** -`users` (and any future `sessions`/`accounts`) are global. Everything else is huddle-scoped and prefixed — including tables like `huddle_poll_options` and `huddle_survey_answers`, which reach a huddle through their parent rather than carrying a `huddle_id` of their own. +`commissioners`, `polls`, `survey_responses` — not `huddle_commissioners`, `huddle_polls`. The `huddle_` prefix that 19 tables used to carry said nothing: every table in this database belongs to Huddle. It cost real ergonomics — `\dt` and editor autocomplete on `hud` returned almost the whole schema — and it implied a scoping rule it didn't keep (`team_claims` and `side_bets` carried `huddle_id` without the prefix; the poll/survey child tables carried the prefix without a `huddle_id`). `0015` removed it from all 21 remaining tables. -Two tables were renamed into this rule in `0013` (`team_claims` → `huddle_team_claims`, `side_bets` → `huddle_side_bets`), which is why the prefix is now worth trusting: before that it was ~80% consistent, which is worse than no convention, because it invited an inference that was wrong in both directions. +The one structural distinction worth knowing isn't in the names: **`users` is global; everything else is reachable from a huddle**, directly via `huddle_id` or transitively through a parent (`poll_options` → `polls` → huddle). If you add auth tables later (`sessions`, `accounts`), they join `users` on the global side. -**Postgres keeps constraint names across `ALTER TABLE ... RENAME TO`,** and Drizzle doesn't track them — so a table rename silently leaves `oldname_pkey` behind and it will never appear in a generated diff. `0014` cleaned up the three that had accumulated (`groups_pkey` from the original `groups` → `huddles` rename in `0001`, plus the two from `0013`). If you rename a table, add an `ALTER INDEX ... RENAME` alongside it, via `npx drizzle-kit generate --custom`. +**Renaming a table is a three-part job, and Postgres only does the first part.** `ALTER TABLE ... RENAME TO` leaves every constraint and index still carrying the old name, and Drizzle doesn't track those names, so they will never show up in a generated diff — they just rot until something collides. `0015` renamed 115 objects for 21 tables. Generate the statements from the live schema (`pg_constraint` + `pg_index`) rather than by hand. + +Two traps that cost a follow-up migration (`0016`) the first time: + +- **The 63-byte identifier limit.** Postgres silently truncates longer constraint names, so `huddle_survey_answer_options_option_id_huddle_survey_options_id_fk` lost its `_fk` suffix on creation. Renaming carried the truncation forward, leaving a name Drizzle's snapshot didn't expect. +- **Verify against the snapshot afterwards.** `db:generate` reporting "no schema changes" only proves `schema.ts` matches the snapshot — not that the *database* does. Diff the snapshot's `foreignKeys`/`indexes` names against `pg_constraint`/`pg_indexes` directly; that's what caught the truncation. --- diff --git a/server/drizzle/0015_drop_huddle_prefix.sql b/server/drizzle/0015_drop_huddle_prefix.sql new file mode 100644 index 0000000..727a9a8 --- /dev/null +++ b/server/drizzle/0015_drop_huddle_prefix.sql @@ -0,0 +1,133 @@ +-- Hand-written, replacing Drizzle's generated SQL for this step. +-- +-- Drizzle prompts "created or renamed?" once per table and cannot be answered +-- reliably from a script for 21 tables, so it was answered "create" — which +-- produces a correct *snapshot* (the snapshot records the end state, not the +-- operations) alongside a DROP/CREATE migration that would discard every row. +-- That SQL was replaced with the renames below, generated from the live schema +-- so no index or constraint is missed. +-- +-- Naming rule this establishes: tables are named for what they hold. The +-- `huddle_` prefix said nothing — every table in the database belongs to +-- Huddle — while making `\dt` and autocomplete useless. See PLAYBOOK, +-- "Table naming". +-- +-- Postgres does not rename constraints or indexes when a table is renamed, so +-- each one is renamed explicitly; otherwise the next `db:generate` diffs +-- against names that no longer match the snapshot. + +ALTER TABLE "huddle_active_trophies" RENAME TO "active_trophies";--> statement-breakpoint +ALTER TABLE "active_trophies" RENAME CONSTRAINT "huddle_active_trophies_huddle_id_huddles_id_fk" TO "active_trophies_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "active_trophies" RENAME CONSTRAINT "huddle_active_trophies_huddle_id_trophy_type_pk" TO "active_trophies_huddle_id_trophy_type_pk";--> statement-breakpoint +ALTER TABLE "huddle_announcements" RENAME TO "announcements";--> statement-breakpoint +ALTER TABLE "announcements" RENAME CONSTRAINT "huddle_announcements_author_id_users_id_fk" TO "announcements_author_id_users_id_fk";--> statement-breakpoint +ALTER TABLE "announcements" RENAME CONSTRAINT "huddle_announcements_huddle_id_huddles_id_fk" TO "announcements_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "announcements" RENAME CONSTRAINT "huddle_announcements_pkey" TO "announcements_pkey";--> statement-breakpoint +ALTER INDEX "huddle_announcements_huddle_idx" RENAME TO "announcements_huddle_idx";--> statement-breakpoint +ALTER TABLE "huddle_awards" RENAME TO "awards";--> statement-breakpoint +ALTER TABLE "awards" RENAME CONSTRAINT "huddle_awards_granted_by_users_id_fk" TO "awards_granted_by_users_id_fk";--> statement-breakpoint +ALTER TABLE "awards" RENAME CONSTRAINT "huddle_awards_huddle_id_huddles_id_fk" TO "awards_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "awards" RENAME CONSTRAINT "huddle_awards_pkey" TO "awards_pkey";--> statement-breakpoint +ALTER INDEX "huddle_awards_huddle_idx" RENAME TO "awards_huddle_idx";--> statement-breakpoint +ALTER INDEX "huddle_awards_huddle_roster_idx" RENAME TO "awards_huddle_roster_idx";--> statement-breakpoint +ALTER TABLE "huddle_commissioners" RENAME TO "commissioners";--> statement-breakpoint +ALTER TABLE "commissioners" RENAME CONSTRAINT "huddle_commissioners_added_by_users_id_fk" TO "commissioners_added_by_users_id_fk";--> statement-breakpoint +ALTER TABLE "commissioners" RENAME CONSTRAINT "huddle_commissioners_huddle_id_huddles_id_fk" TO "commissioners_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "commissioners" RENAME CONSTRAINT "huddle_commissioners_huddle_id_user_id_pk" TO "commissioners_huddle_id_user_id_pk";--> statement-breakpoint +ALTER TABLE "commissioners" RENAME CONSTRAINT "huddle_commissioners_user_id_users_id_fk" TO "commissioners_user_id_users_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_commissioners_user_idx" RENAME TO "commissioners_user_idx";--> statement-breakpoint +ALTER TABLE "huddle_countdown_config" RENAME TO "countdown_config";--> statement-breakpoint +ALTER TABLE "countdown_config" RENAME CONSTRAINT "huddle_countdown_config_huddle_id_huddles_id_fk" TO "countdown_config_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "countdown_config" RENAME CONSTRAINT "huddle_countdown_config_pkey" TO "countdown_config_pkey";--> statement-breakpoint +ALTER TABLE "huddle_dues_config" RENAME TO "dues_config";--> statement-breakpoint +ALTER TABLE "dues_config" RENAME CONSTRAINT "huddle_dues_config_huddle_id_huddles_id_fk" TO "dues_config_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "dues_config" RENAME CONSTRAINT "huddle_dues_config_pkey" TO "dues_config_pkey";--> statement-breakpoint +ALTER TABLE "huddle_dues_payments" RENAME TO "dues_payments";--> statement-breakpoint +ALTER TABLE "dues_payments" RENAME CONSTRAINT "huddle_dues_payments_huddle_id_huddles_id_fk" TO "dues_payments_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "dues_payments" RENAME CONSTRAINT "huddle_dues_payments_marked_by_users_id_fk" TO "dues_payments_marked_by_users_id_fk";--> statement-breakpoint +ALTER TABLE "dues_payments" RENAME CONSTRAINT "huddle_dues_payments_pkey" TO "dues_payments_pkey";--> statement-breakpoint +ALTER INDEX "huddle_dues_payments_huddle_roster_uniq" RENAME TO "dues_payments_huddle_roster_uniq";--> statement-breakpoint +ALTER TABLE "huddle_forum_replies" RENAME TO "forum_replies";--> statement-breakpoint +ALTER TABLE "forum_replies" RENAME CONSTRAINT "huddle_forum_replies_author_id_users_id_fk" TO "forum_replies_author_id_users_id_fk";--> statement-breakpoint +ALTER TABLE "forum_replies" RENAME CONSTRAINT "huddle_forum_replies_deleted_by_users_id_fk" TO "forum_replies_deleted_by_users_id_fk";--> statement-breakpoint +ALTER TABLE "forum_replies" RENAME CONSTRAINT "huddle_forum_replies_huddle_id_huddles_id_fk" TO "forum_replies_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "forum_replies" RENAME CONSTRAINT "huddle_forum_replies_pkey" TO "forum_replies_pkey";--> statement-breakpoint +ALTER TABLE "forum_replies" RENAME CONSTRAINT "huddle_forum_replies_topic_id_huddle_forum_topics_id_fk" TO "forum_replies_topic_id_forum_topics_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_forum_replies_topic_idx" RENAME TO "forum_replies_topic_idx";--> statement-breakpoint +ALTER TABLE "huddle_forum_topics" RENAME TO "forum_topics";--> statement-breakpoint +ALTER TABLE "forum_topics" RENAME CONSTRAINT "huddle_forum_topics_author_id_users_id_fk" TO "forum_topics_author_id_users_id_fk";--> statement-breakpoint +ALTER TABLE "forum_topics" RENAME CONSTRAINT "huddle_forum_topics_deleted_by_users_id_fk" TO "forum_topics_deleted_by_users_id_fk";--> statement-breakpoint +ALTER TABLE "forum_topics" RENAME CONSTRAINT "huddle_forum_topics_huddle_id_huddles_id_fk" TO "forum_topics_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "forum_topics" RENAME CONSTRAINT "huddle_forum_topics_pkey" TO "forum_topics_pkey";--> statement-breakpoint +ALTER INDEX "huddle_forum_topics_huddle_updated_idx" RENAME TO "forum_topics_huddle_updated_idx";--> statement-breakpoint +ALTER TABLE "huddle_payout_entries" RENAME TO "payout_entries";--> statement-breakpoint +ALTER TABLE "payout_entries" RENAME CONSTRAINT "huddle_payout_entries_huddle_id_huddles_id_fk" TO "payout_entries_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "payout_entries" RENAME CONSTRAINT "huddle_payout_entries_pkey" TO "payout_entries_pkey";--> statement-breakpoint +ALTER INDEX "huddle_payout_entries_huddle_idx" RENAME TO "payout_entries_huddle_idx";--> statement-breakpoint +ALTER TABLE "huddle_poll_options" RENAME TO "poll_options";--> statement-breakpoint +ALTER TABLE "poll_options" RENAME CONSTRAINT "huddle_poll_options_pkey" TO "poll_options_pkey";--> statement-breakpoint +ALTER TABLE "poll_options" RENAME CONSTRAINT "huddle_poll_options_poll_id_huddle_polls_id_fk" TO "poll_options_poll_id_polls_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_poll_options_poll_idx" RENAME TO "poll_options_poll_idx";--> statement-breakpoint +ALTER TABLE "huddle_poll_votes" RENAME TO "poll_votes";--> statement-breakpoint +ALTER TABLE "poll_votes" RENAME CONSTRAINT "huddle_poll_votes_option_id_huddle_poll_options_id_fk" TO "poll_votes_option_id_poll_options_id_fk";--> statement-breakpoint +ALTER TABLE "poll_votes" RENAME CONSTRAINT "huddle_poll_votes_pkey" TO "poll_votes_pkey";--> statement-breakpoint +ALTER TABLE "poll_votes" RENAME CONSTRAINT "huddle_poll_votes_poll_id_huddle_polls_id_fk" TO "poll_votes_poll_id_polls_id_fk";--> statement-breakpoint +ALTER TABLE "poll_votes" RENAME CONSTRAINT "huddle_poll_votes_user_id_users_id_fk" TO "poll_votes_user_id_users_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_poll_votes_option_user_uniq" RENAME TO "poll_votes_option_user_uniq";--> statement-breakpoint +ALTER INDEX "huddle_poll_votes_poll_user_idx" RENAME TO "poll_votes_poll_user_idx";--> statement-breakpoint +ALTER TABLE "huddle_polls" RENAME TO "polls";--> statement-breakpoint +ALTER TABLE "polls" RENAME CONSTRAINT "huddle_polls_author_id_users_id_fk" TO "polls_author_id_users_id_fk";--> statement-breakpoint +ALTER TABLE "polls" RENAME CONSTRAINT "huddle_polls_huddle_id_huddles_id_fk" TO "polls_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "polls" RENAME CONSTRAINT "huddle_polls_pkey" TO "polls_pkey";--> statement-breakpoint +ALTER TABLE "polls" RENAME CONSTRAINT "huddle_polls_topic_id_huddle_forum_topics_id_fk" TO "polls_topic_id_forum_topics_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_polls_dashboard_active_uniq" RENAME TO "polls_dashboard_active_uniq";--> statement-breakpoint +ALTER INDEX "huddle_polls_topic_idx" RENAME TO "polls_topic_idx";--> statement-breakpoint +ALTER TABLE "huddle_side_bets" RENAME TO "side_bets";--> statement-breakpoint +ALTER TABLE "side_bets" RENAME CONSTRAINT "huddle_side_bets_huddle_id_huddles_id_fk" TO "side_bets_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "side_bets" RENAME CONSTRAINT "huddle_side_bets_opponent_id_users_id_fk" TO "side_bets_opponent_id_users_id_fk";--> statement-breakpoint +ALTER TABLE "side_bets" RENAME CONSTRAINT "huddle_side_bets_pkey" TO "side_bets_pkey";--> statement-breakpoint +ALTER TABLE "side_bets" RENAME CONSTRAINT "huddle_side_bets_proposer_id_users_id_fk" TO "side_bets_proposer_id_users_id_fk";--> statement-breakpoint +ALTER TABLE "side_bets" RENAME CONSTRAINT "huddle_side_bets_winner_id_users_id_fk" TO "side_bets_winner_id_users_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_side_bets_huddle_idx" RENAME TO "side_bets_huddle_idx";--> statement-breakpoint +ALTER INDEX "huddle_side_bets_opponent_idx" RENAME TO "side_bets_opponent_idx";--> statement-breakpoint +ALTER INDEX "huddle_side_bets_proposer_idx" RENAME TO "side_bets_proposer_idx";--> statement-breakpoint +ALTER TABLE "huddle_survey_answer_options" RENAME TO "survey_answer_options";--> statement-breakpoint +ALTER TABLE "survey_answer_options" RENAME CONSTRAINT "huddle_survey_answer_options_option_id_huddle_survey_options_id" TO "survey_answer_options_option_id_survey_options_id";--> statement-breakpoint +ALTER TABLE "survey_answer_options" RENAME CONSTRAINT "huddle_survey_answer_options_pkey" TO "survey_answer_options_pkey";--> statement-breakpoint +ALTER TABLE "survey_answer_options" RENAME CONSTRAINT "huddle_survey_answer_options_question_id_huddle_survey_question" TO "survey_answer_options_question_id_survey_questions_id_fk";--> statement-breakpoint +ALTER TABLE "survey_answer_options" RENAME CONSTRAINT "huddle_survey_answer_options_response_id_huddle_survey_response" TO "survey_answer_options_response_id_survey_responses_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_survey_answer_options_question_idx" RENAME TO "survey_answer_options_question_idx";--> statement-breakpoint +ALTER INDEX "huddle_survey_answer_options_response_option_uniq" RENAME TO "survey_answer_options_response_option_uniq";--> statement-breakpoint +ALTER TABLE "huddle_survey_answers" RENAME TO "survey_answers";--> statement-breakpoint +ALTER TABLE "survey_answers" RENAME CONSTRAINT "huddle_survey_answers_pkey" TO "survey_answers_pkey";--> statement-breakpoint +ALTER TABLE "survey_answers" RENAME CONSTRAINT "huddle_survey_answers_question_id_huddle_survey_questions_id_fk" TO "survey_answers_question_id_survey_questions_id_fk";--> statement-breakpoint +ALTER TABLE "survey_answers" RENAME CONSTRAINT "huddle_survey_answers_response_id_huddle_survey_responses_id_fk" TO "survey_answers_response_id_survey_responses_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_survey_answers_question_idx" RENAME TO "survey_answers_question_idx";--> statement-breakpoint +ALTER INDEX "huddle_survey_answers_response_idx" RENAME TO "survey_answers_response_idx";--> statement-breakpoint +ALTER TABLE "huddle_survey_options" RENAME TO "survey_options";--> statement-breakpoint +ALTER TABLE "survey_options" RENAME CONSTRAINT "huddle_survey_options_pkey" TO "survey_options_pkey";--> statement-breakpoint +ALTER TABLE "survey_options" RENAME CONSTRAINT "huddle_survey_options_question_id_huddle_survey_questions_id_fk" TO "survey_options_question_id_survey_questions_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_survey_options_question_idx" RENAME TO "survey_options_question_idx";--> statement-breakpoint +ALTER TABLE "huddle_survey_questions" RENAME TO "survey_questions";--> statement-breakpoint +ALTER TABLE "survey_questions" RENAME CONSTRAINT "huddle_survey_questions_pkey" TO "survey_questions_pkey";--> statement-breakpoint +ALTER TABLE "survey_questions" RENAME CONSTRAINT "huddle_survey_questions_survey_id_huddle_surveys_id_fk" TO "survey_questions_survey_id_surveys_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_survey_questions_survey_idx" RENAME TO "survey_questions_survey_idx";--> statement-breakpoint +ALTER TABLE "huddle_survey_responses" RENAME TO "survey_responses";--> statement-breakpoint +ALTER TABLE "survey_responses" RENAME CONSTRAINT "huddle_survey_responses_pkey" TO "survey_responses_pkey";--> statement-breakpoint +ALTER TABLE "survey_responses" RENAME CONSTRAINT "huddle_survey_responses_survey_id_huddle_surveys_id_fk" TO "survey_responses_survey_id_surveys_id_fk";--> statement-breakpoint +ALTER TABLE "survey_responses" RENAME CONSTRAINT "huddle_survey_responses_user_id_users_id_fk" TO "survey_responses_user_id_users_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_survey_responses_survey_user_uniq" RENAME TO "survey_responses_survey_user_uniq";--> statement-breakpoint +ALTER TABLE "huddle_surveys" RENAME TO "surveys";--> statement-breakpoint +ALTER TABLE "surveys" RENAME CONSTRAINT "huddle_surveys_author_id_users_id_fk" TO "surveys_author_id_users_id_fk";--> statement-breakpoint +ALTER TABLE "surveys" RENAME CONSTRAINT "huddle_surveys_huddle_id_huddles_id_fk" TO "surveys_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "surveys" RENAME CONSTRAINT "huddle_surveys_pkey" TO "surveys_pkey";--> statement-breakpoint +ALTER INDEX "huddle_surveys_huddle_created_idx" RENAME TO "surveys_huddle_created_idx";--> statement-breakpoint +ALTER TABLE "huddle_team_claims" RENAME TO "team_claims";--> statement-breakpoint +ALTER TABLE "team_claims" RENAME CONSTRAINT "huddle_team_claims_decided_by_users_id_fk" TO "team_claims_decided_by_users_id_fk";--> statement-breakpoint +ALTER TABLE "team_claims" RENAME CONSTRAINT "huddle_team_claims_huddle_id_huddles_id_fk" TO "team_claims_huddle_id_huddles_id_fk";--> statement-breakpoint +ALTER TABLE "team_claims" RENAME CONSTRAINT "huddle_team_claims_pkey" TO "team_claims_pkey";--> statement-breakpoint +ALTER TABLE "team_claims" RENAME CONSTRAINT "huddle_team_claims_user_id_users_id_fk" TO "team_claims_user_id_users_id_fk";--> statement-breakpoint +ALTER INDEX "huddle_team_claims_huddle_idx" RENAME TO "team_claims_huddle_idx";--> statement-breakpoint +ALTER INDEX "huddle_team_claims_huddle_roster_approved_uniq" RENAME TO "team_claims_huddle_roster_approved_uniq";--> statement-breakpoint +ALTER INDEX "huddle_team_claims_huddle_user_approved_uniq" RENAME TO "team_claims_huddle_user_approved_uniq";--> statement-breakpoint +ALTER INDEX "huddle_team_claims_user_idx" RENAME TO "team_claims_user_idx"; diff --git a/server/drizzle/0016_fix_truncated_fk_name.sql b/server/drizzle/0016_fix_truncated_fk_name.sql new file mode 100644 index 0000000..e209cf0 --- /dev/null +++ b/server/drizzle/0016_fix_truncated_fk_name.sql @@ -0,0 +1,12 @@ +-- Custom migration, so the snapshot is written even though schema.ts is unchanged. +-- +-- This constraint was created before 0015 as +-- `huddle_survey_answer_options_option_id_huddle_survey_options_id_fk`, which is +-- 66 bytes — over Postgres's 63-byte identifier limit, so it was silently +-- truncated, losing its `_fk` suffix. 0015 renamed the truncated name and so +-- carried the truncation forward. Now that the `huddle_` prefixes are gone the +-- full name fits (52 bytes), and it has to match what Drizzle's snapshot +-- expects or every future `db:generate` will diff against it. +ALTER TABLE "survey_answer_options" + RENAME CONSTRAINT "survey_answer_options_option_id_survey_options_id" + TO "survey_answer_options_option_id_survey_options_id_fk"; diff --git a/server/drizzle/meta/0015_snapshot.json b/server/drizzle/meta/0015_snapshot.json new file mode 100644 index 0000000..9a8e0bf --- /dev/null +++ b/server/drizzle/meta/0015_snapshot.json @@ -0,0 +1,2644 @@ +{ + "id": "a0ea729f-abdb-42b6-8bfd-4ed4c0d8b1a9", + "prevId": "f9994cad-9a04-4600-976e-240953d6631c", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.active_trophies": { + "name": "active_trophies", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trophy_type": { + "name": "trophy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "active_trophies_huddle_id_huddles_id_fk": { + "name": "active_trophies_huddle_id_huddles_id_fk", + "tableFrom": "active_trophies", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "active_trophies_huddle_id_trophy_type_pk": { + "name": "active_trophies_huddle_id_trophy_type_pk", + "columns": [ + "huddle_id", + "trophy_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.announcements": { + "name": "announcements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "announcements_huddle_idx": { + "name": "announcements_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "announcements_huddle_id_huddles_id_fk": { + "name": "announcements_huddle_id_huddles_id_fk", + "tableFrom": "announcements", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "announcements_author_id_users_id_fk": { + "name": "announcements_author_id_users_id_fk", + "tableFrom": "announcements", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.awards": { + "name": "awards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "glyph": { + "name": "glyph", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "awards_huddle_idx": { + "name": "awards_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "awards_huddle_roster_idx": { + "name": "awards_huddle_roster_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "awards_huddle_id_huddles_id_fk": { + "name": "awards_huddle_id_huddles_id_fk", + "tableFrom": "awards", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "awards_granted_by_users_id_fk": { + "name": "awards_granted_by_users_id_fk", + "tableFrom": "awards", + "tableTo": "users", + "columnsFrom": [ + "granted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.commissioners": { + "name": "commissioners", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "commissioners_user_idx": { + "name": "commissioners_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "commissioners_huddle_id_huddles_id_fk": { + "name": "commissioners_huddle_id_huddles_id_fk", + "tableFrom": "commissioners", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "commissioners_user_id_users_id_fk": { + "name": "commissioners_user_id_users_id_fk", + "tableFrom": "commissioners", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "commissioners_added_by_users_id_fk": { + "name": "commissioners_added_by_users_id_fk", + "tableFrom": "commissioners", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "commissioners_huddle_id_user_id_pk": { + "name": "commissioners_huddle_id_user_id_pk", + "columns": [ + "huddle_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.countdown_config": { + "name": "countdown_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_at": { + "name": "target_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "countdown_config_huddle_id_huddles_id_fk": { + "name": "countdown_config_huddle_id_huddles_id_fk", + "tableFrom": "countdown_config", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dues_config": { + "name": "dues_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dues_config_huddle_id_huddles_id_fk": { + "name": "dues_config_huddle_id_huddles_id_fk", + "tableFrom": "dues_config", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dues_payments": { + "name": "dues_payments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "marked_by": { + "name": "marked_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dues_payments_huddle_roster_uniq": { + "name": "dues_payments_huddle_roster_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dues_payments_huddle_id_huddles_id_fk": { + "name": "dues_payments_huddle_id_huddles_id_fk", + "tableFrom": "dues_payments", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dues_payments_marked_by_users_id_fk": { + "name": "dues_payments_marked_by_users_id_fk", + "tableFrom": "dues_payments", + "tableTo": "users", + "columnsFrom": [ + "marked_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.forum_replies": { + "name": "forum_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "forum_replies_topic_idx": { + "name": "forum_replies_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "forum_replies_topic_id_forum_topics_id_fk": { + "name": "forum_replies_topic_id_forum_topics_id_fk", + "tableFrom": "forum_replies", + "tableTo": "forum_topics", + "columnsFrom": [ + "topic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "forum_replies_huddle_id_huddles_id_fk": { + "name": "forum_replies_huddle_id_huddles_id_fk", + "tableFrom": "forum_replies", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "forum_replies_author_id_users_id_fk": { + "name": "forum_replies_author_id_users_id_fk", + "tableFrom": "forum_replies", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "forum_replies_deleted_by_users_id_fk": { + "name": "forum_replies_deleted_by_users_id_fk", + "tableFrom": "forum_replies", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.forum_topics": { + "name": "forum_topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "forum_topics_huddle_updated_idx": { + "name": "forum_topics_huddle_updated_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "forum_topics_huddle_id_huddles_id_fk": { + "name": "forum_topics_huddle_id_huddles_id_fk", + "tableFrom": "forum_topics", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "forum_topics_author_id_users_id_fk": { + "name": "forum_topics_author_id_users_id_fk", + "tableFrom": "forum_topics", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "forum_topics_deleted_by_users_id_fk": { + "name": "forum_topics_deleted_by_users_id_fk", + "tableFrom": "forum_topics", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddles": { + "name": "huddles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_provider": { + "name": "league_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "league_id": { + "name": "league_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code_updated_at": { + "name": "invite_code_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invite_link_token": { + "name": "invite_link_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "invite_link_expires_at": { + "name": "invite_link_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddles_invite_code_uniq": { + "name": "huddles_invite_code_uniq", + "columns": [ + { + "expression": "invite_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "huddles_invite_link_token_uniq": { + "name": "huddles_invite_link_token_uniq", + "columns": [ + { + "expression": "invite_link_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payout_entries": { + "name": "payout_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payout_entries_huddle_idx": { + "name": "payout_entries_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "payout_entries_huddle_id_huddles_id_fk": { + "name": "payout_entries_huddle_id_huddles_id_fk", + "tableFrom": "payout_entries", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.poll_options": { + "name": "poll_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "poll_options_poll_idx": { + "name": "poll_options_poll_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "poll_options_poll_id_polls_id_fk": { + "name": "poll_options_poll_id_polls_id_fk", + "tableFrom": "poll_options", + "tableTo": "polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.poll_votes": { + "name": "poll_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "poll_votes_poll_user_idx": { + "name": "poll_votes_poll_user_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "poll_votes_option_user_uniq": { + "name": "poll_votes_option_user_uniq", + "columns": [ + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "poll_votes_poll_id_polls_id_fk": { + "name": "poll_votes_poll_id_polls_id_fk", + "tableFrom": "poll_votes", + "tableTo": "polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "poll_votes_option_id_poll_options_id_fk": { + "name": "poll_votes_option_id_poll_options_id_fk", + "tableFrom": "poll_votes", + "tableTo": "poll_options", + "columnsFrom": [ + "option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "poll_votes_user_id_users_id_fk": { + "name": "poll_votes_user_id_users_id_fk", + "tableFrom": "poll_votes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.polls": { + "name": "polls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_dashboard_poll": { + "name": "is_dashboard_poll", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_multiple": { + "name": "allow_multiple", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_vote_changes": { + "name": "allow_vote_changes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "results_visibility": { + "name": "results_visibility", + "type": "poll_results_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "polls_topic_idx": { + "name": "polls_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "polls_dashboard_active_uniq": { + "name": "polls_dashboard_active_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"polls\".\"is_dashboard_poll\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "polls_huddle_id_huddles_id_fk": { + "name": "polls_huddle_id_huddles_id_fk", + "tableFrom": "polls", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "polls_author_id_users_id_fk": { + "name": "polls_author_id_users_id_fk", + "tableFrom": "polls", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "polls_topic_id_forum_topics_id_fk": { + "name": "polls_topic_id_forum_topics_id_fk", + "tableFrom": "polls", + "tableTo": "forum_topics", + "columnsFrom": [ + "topic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.side_bets": { + "name": "side_bets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proposer_id": { + "name": "proposer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opponent_id": { + "name": "opponent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposer_roster_id": { + "name": "proposer_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "opponent_roster_id": { + "name": "opponent_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "week": { + "name": "week", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "prize_description": { + "name": "prize_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "side_bet_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "winner_id": { + "name": "winner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settlement_note": { + "name": "settlement_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "side_bets_huddle_idx": { + "name": "side_bets_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "side_bets_proposer_idx": { + "name": "side_bets_proposer_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "proposer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "side_bets_opponent_idx": { + "name": "side_bets_opponent_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "opponent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "side_bets_huddle_id_huddles_id_fk": { + "name": "side_bets_huddle_id_huddles_id_fk", + "tableFrom": "side_bets", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "side_bets_proposer_id_users_id_fk": { + "name": "side_bets_proposer_id_users_id_fk", + "tableFrom": "side_bets", + "tableTo": "users", + "columnsFrom": [ + "proposer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "side_bets_opponent_id_users_id_fk": { + "name": "side_bets_opponent_id_users_id_fk", + "tableFrom": "side_bets", + "tableTo": "users", + "columnsFrom": [ + "opponent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "side_bets_winner_id_users_id_fk": { + "name": "side_bets_winner_id_users_id_fk", + "tableFrom": "side_bets", + "tableTo": "users", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_answer_options": { + "name": "survey_answer_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "survey_answer_options_question_idx": { + "name": "survey_answer_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "survey_answer_options_response_option_uniq": { + "name": "survey_answer_options_response_option_uniq", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "survey_answer_options_response_id_survey_responses_id_fk": { + "name": "survey_answer_options_response_id_survey_responses_id_fk", + "tableFrom": "survey_answer_options", + "tableTo": "survey_responses", + "columnsFrom": [ + "response_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "survey_answer_options_question_id_survey_questions_id_fk": { + "name": "survey_answer_options_question_id_survey_questions_id_fk", + "tableFrom": "survey_answer_options", + "tableTo": "survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "survey_answer_options_option_id_survey_options_id_fk": { + "name": "survey_answer_options_option_id_survey_options_id_fk", + "tableFrom": "survey_answer_options", + "tableTo": "survey_options", + "columnsFrom": [ + "option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_answers": { + "name": "survey_answers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "text_value": { + "name": "text_value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "survey_answers_response_idx": { + "name": "survey_answers_response_idx", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "survey_answers_question_idx": { + "name": "survey_answers_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "survey_answers_response_id_survey_responses_id_fk": { + "name": "survey_answers_response_id_survey_responses_id_fk", + "tableFrom": "survey_answers", + "tableTo": "survey_responses", + "columnsFrom": [ + "response_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "survey_answers_question_id_survey_questions_id_fk": { + "name": "survey_answers_question_id_survey_questions_id_fk", + "tableFrom": "survey_answers", + "tableTo": "survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_options": { + "name": "survey_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "survey_options_question_idx": { + "name": "survey_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "survey_options_question_id_survey_questions_id_fk": { + "name": "survey_options_question_id_survey_questions_id_fk", + "tableFrom": "survey_options", + "tableTo": "survey_questions", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_questions": { + "name": "survey_questions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "survey_question_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "survey_questions_survey_idx": { + "name": "survey_questions_survey_idx", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "survey_questions_survey_id_surveys_id_fk": { + "name": "survey_questions_survey_id_surveys_id_fk", + "tableFrom": "survey_questions", + "tableTo": "surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_responses": { + "name": "survey_responses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "survey_responses_survey_user_uniq": { + "name": "survey_responses_survey_user_uniq", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "survey_responses_survey_id_surveys_id_fk": { + "name": "survey_responses_survey_id_surveys_id_fk", + "tableFrom": "survey_responses", + "tableTo": "surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "survey_responses_user_id_users_id_fk": { + "name": "survey_responses_user_id_users_id_fk", + "tableFrom": "survey_responses", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.surveys": { + "name": "surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "results_published": { + "name": "results_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_publish_on_close": { + "name": "auto_publish_on_close", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "anonymity": { + "name": "anonymity", + "type": "survey_anonymity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "surveys_huddle_created_idx": { + "name": "surveys_huddle_created_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "surveys_huddle_id_huddles_id_fk": { + "name": "surveys_huddle_id_huddles_id_fk", + "tableFrom": "surveys", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "surveys_author_id_users_id_fk": { + "name": "surveys_author_id_users_id_fk", + "tableFrom": "surveys", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_claims": { + "name": "team_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "claim_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_claims_huddle_roster_approved_uniq": { + "name": "team_claims_huddle_roster_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"team_claims\".\"status\" = 'approved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_huddle_user_approved_uniq": { + "name": "team_claims_huddle_user_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"team_claims\".\"status\" = 'approved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_huddle_idx": { + "name": "team_claims_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_claims_user_idx": { + "name": "team_claims_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_claims_huddle_id_huddles_id_fk": { + "name": "team_claims_huddle_id_huddles_id_fk", + "tableFrom": "team_claims", + "tableTo": "huddles", + "columnsFrom": [ + "huddle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_claims_user_id_users_id_fk": { + "name": "team_claims_user_id_users_id_fk", + "tableFrom": "team_claims", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "team_claims_decided_by_users_id_fk": { + "name": "team_claims_decided_by_users_id_fk", + "tableFrom": "team_claims", + "tableTo": "users", + "columnsFrom": [ + "decided_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_username": { + "name": "sleeper_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_user_id": { + "name": "sleeper_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "synced_league_ids": { + "name": "synced_league_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_placeholder": { + "name": "is_placeholder", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "identity_synced_at": { + "name": "identity_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_username_idx": { + "name": "users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.claim_status": { + "name": "claim_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected" + ] + }, + "public.poll_results_visibility": { + "name": "poll_results_visibility", + "schema": "public", + "values": [ + "always", + "after_vote", + "after_close" + ] + }, + "public.side_bet_status": { + "name": "side_bet_status", + "schema": "public", + "values": [ + "pending", + "accepted", + "rejected", + "cancelled", + "settled" + ] + }, + "public.survey_anonymity": { + "name": "survey_anonymity", + "schema": "public", + "values": [ + "none", + "anonymous_to_league", + "anonymous_to_all" + ] + }, + "public.survey_question_type": { + "name": "survey_question_type", + "schema": "public", + "values": [ + "short_text", + "paragraph", + "multiple_choice", + "checkboxes" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/0016_snapshot.json b/server/drizzle/meta/0016_snapshot.json new file mode 100644 index 0000000..2ffc141 --- /dev/null +++ b/server/drizzle/meta/0016_snapshot.json @@ -0,0 +1,2644 @@ +{ + "id": "3c16b521-2e1c-4bbc-ad6a-c63e04b2e2b5", + "prevId": "a0ea729f-abdb-42b6-8bfd-4ed4c0d8b1a9", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.active_trophies": { + "name": "active_trophies", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trophy_type": { + "name": "trophy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "active_trophies_huddle_id_huddles_id_fk": { + "name": "active_trophies_huddle_id_huddles_id_fk", + "tableFrom": "active_trophies", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "active_trophies_huddle_id_trophy_type_pk": { + "name": "active_trophies_huddle_id_trophy_type_pk", + "columns": [ + "huddle_id", + "trophy_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.announcements": { + "name": "announcements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "announcements_huddle_idx": { + "name": "announcements_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "announcements_huddle_id_huddles_id_fk": { + "name": "announcements_huddle_id_huddles_id_fk", + "tableFrom": "announcements", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "announcements_author_id_users_id_fk": { + "name": "announcements_author_id_users_id_fk", + "tableFrom": "announcements", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.awards": { + "name": "awards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "glyph": { + "name": "glyph", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "awards_huddle_idx": { + "name": "awards_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "awards_huddle_roster_idx": { + "name": "awards_huddle_roster_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "awards_huddle_id_huddles_id_fk": { + "name": "awards_huddle_id_huddles_id_fk", + "tableFrom": "awards", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "awards_granted_by_users_id_fk": { + "name": "awards_granted_by_users_id_fk", + "tableFrom": "awards", + "columnsFrom": [ + "granted_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.commissioners": { + "name": "commissioners", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "commissioners_user_idx": { + "name": "commissioners_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "commissioners_huddle_id_huddles_id_fk": { + "name": "commissioners_huddle_id_huddles_id_fk", + "tableFrom": "commissioners", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "commissioners_user_id_users_id_fk": { + "name": "commissioners_user_id_users_id_fk", + "tableFrom": "commissioners", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "commissioners_added_by_users_id_fk": { + "name": "commissioners_added_by_users_id_fk", + "tableFrom": "commissioners", + "columnsFrom": [ + "added_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": { + "commissioners_huddle_id_user_id_pk": { + "name": "commissioners_huddle_id_user_id_pk", + "columns": [ + "huddle_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.countdown_config": { + "name": "countdown_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_at": { + "name": "target_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "countdown_config_huddle_id_huddles_id_fk": { + "name": "countdown_config_huddle_id_huddles_id_fk", + "tableFrom": "countdown_config", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dues_config": { + "name": "dues_config", + "schema": "", + "columns": { + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "dues_config_huddle_id_huddles_id_fk": { + "name": "dues_config_huddle_id_huddles_id_fk", + "tableFrom": "dues_config", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dues_payments": { + "name": "dues_payments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "marked_by": { + "name": "marked_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dues_payments_huddle_roster_uniq": { + "name": "dues_payments_huddle_roster_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "dues_payments_huddle_id_huddles_id_fk": { + "name": "dues_payments_huddle_id_huddles_id_fk", + "tableFrom": "dues_payments", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "dues_payments_marked_by_users_id_fk": { + "name": "dues_payments_marked_by_users_id_fk", + "tableFrom": "dues_payments", + "columnsFrom": [ + "marked_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.forum_replies": { + "name": "forum_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "forum_replies_topic_idx": { + "name": "forum_replies_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "forum_replies_topic_id_forum_topics_id_fk": { + "name": "forum_replies_topic_id_forum_topics_id_fk", + "tableFrom": "forum_replies", + "columnsFrom": [ + "topic_id" + ], + "tableTo": "forum_topics", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "forum_replies_huddle_id_huddles_id_fk": { + "name": "forum_replies_huddle_id_huddles_id_fk", + "tableFrom": "forum_replies", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "forum_replies_author_id_users_id_fk": { + "name": "forum_replies_author_id_users_id_fk", + "tableFrom": "forum_replies", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "forum_replies_deleted_by_users_id_fk": { + "name": "forum_replies_deleted_by_users_id_fk", + "tableFrom": "forum_replies", + "columnsFrom": [ + "deleted_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.forum_topics": { + "name": "forum_topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "forum_topics_huddle_updated_idx": { + "name": "forum_topics_huddle_updated_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "forum_topics_huddle_id_huddles_id_fk": { + "name": "forum_topics_huddle_id_huddles_id_fk", + "tableFrom": "forum_topics", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "forum_topics_author_id_users_id_fk": { + "name": "forum_topics_author_id_users_id_fk", + "tableFrom": "forum_topics", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "forum_topics_deleted_by_users_id_fk": { + "name": "forum_topics_deleted_by_users_id_fk", + "tableFrom": "forum_topics", + "columnsFrom": [ + "deleted_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.huddles": { + "name": "huddles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_provider": { + "name": "league_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "league_id": { + "name": "league_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code_updated_at": { + "name": "invite_code_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invite_link_token": { + "name": "invite_link_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "invite_link_expires_at": { + "name": "invite_link_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "huddles_invite_code_uniq": { + "name": "huddles_invite_code_uniq", + "columns": [ + { + "expression": "invite_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "huddles_invite_link_token_uniq": { + "name": "huddles_invite_link_token_uniq", + "columns": [ + { + "expression": "invite_link_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payout_entries": { + "name": "payout_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payout_entries_huddle_idx": { + "name": "payout_entries_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "payout_entries_huddle_id_huddles_id_fk": { + "name": "payout_entries_huddle_id_huddles_id_fk", + "tableFrom": "payout_entries", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.poll_options": { + "name": "poll_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "poll_options_poll_idx": { + "name": "poll_options_poll_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "poll_options_poll_id_polls_id_fk": { + "name": "poll_options_poll_id_polls_id_fk", + "tableFrom": "poll_options", + "columnsFrom": [ + "poll_id" + ], + "tableTo": "polls", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.poll_votes": { + "name": "poll_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "poll_votes_poll_user_idx": { + "name": "poll_votes_poll_user_idx", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "poll_votes_option_user_uniq": { + "name": "poll_votes_option_user_uniq", + "columns": [ + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "poll_votes_poll_id_polls_id_fk": { + "name": "poll_votes_poll_id_polls_id_fk", + "tableFrom": "poll_votes", + "columnsFrom": [ + "poll_id" + ], + "tableTo": "polls", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "poll_votes_option_id_poll_options_id_fk": { + "name": "poll_votes_option_id_poll_options_id_fk", + "tableFrom": "poll_votes", + "columnsFrom": [ + "option_id" + ], + "tableTo": "poll_options", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "poll_votes_user_id_users_id_fk": { + "name": "poll_votes_user_id_users_id_fk", + "tableFrom": "poll_votes", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.polls": { + "name": "polls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_dashboard_poll": { + "name": "is_dashboard_poll", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_multiple": { + "name": "allow_multiple", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_vote_changes": { + "name": "allow_vote_changes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "results_visibility": { + "name": "results_visibility", + "type": "poll_results_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "polls_topic_idx": { + "name": "polls_topic_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "polls_dashboard_active_uniq": { + "name": "polls_dashboard_active_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"polls\".\"is_dashboard_poll\" = true", + "concurrently": false + } + }, + "foreignKeys": { + "polls_huddle_id_huddles_id_fk": { + "name": "polls_huddle_id_huddles_id_fk", + "tableFrom": "polls", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "polls_author_id_users_id_fk": { + "name": "polls_author_id_users_id_fk", + "tableFrom": "polls", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "polls_topic_id_forum_topics_id_fk": { + "name": "polls_topic_id_forum_topics_id_fk", + "tableFrom": "polls", + "columnsFrom": [ + "topic_id" + ], + "tableTo": "forum_topics", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.side_bets": { + "name": "side_bets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proposer_id": { + "name": "proposer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opponent_id": { + "name": "opponent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposer_roster_id": { + "name": "proposer_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "opponent_roster_id": { + "name": "opponent_roster_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "week": { + "name": "week", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season": { + "name": "season", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "prize_description": { + "name": "prize_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "side_bet_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "winner_id": { + "name": "winner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settlement_note": { + "name": "settlement_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "side_bets_huddle_idx": { + "name": "side_bets_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "side_bets_proposer_idx": { + "name": "side_bets_proposer_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "proposer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "side_bets_opponent_idx": { + "name": "side_bets_opponent_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "opponent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "side_bets_huddle_id_huddles_id_fk": { + "name": "side_bets_huddle_id_huddles_id_fk", + "tableFrom": "side_bets", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "side_bets_proposer_id_users_id_fk": { + "name": "side_bets_proposer_id_users_id_fk", + "tableFrom": "side_bets", + "columnsFrom": [ + "proposer_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "side_bets_opponent_id_users_id_fk": { + "name": "side_bets_opponent_id_users_id_fk", + "tableFrom": "side_bets", + "columnsFrom": [ + "opponent_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "side_bets_winner_id_users_id_fk": { + "name": "side_bets_winner_id_users_id_fk", + "tableFrom": "side_bets", + "columnsFrom": [ + "winner_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_answer_options": { + "name": "survey_answer_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_id": { + "name": "option_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "survey_answer_options_question_idx": { + "name": "survey_answer_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "survey_answer_options_response_option_uniq": { + "name": "survey_answer_options_response_option_uniq", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "survey_answer_options_response_id_survey_responses_id_fk": { + "name": "survey_answer_options_response_id_survey_responses_id_fk", + "tableFrom": "survey_answer_options", + "columnsFrom": [ + "response_id" + ], + "tableTo": "survey_responses", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "survey_answer_options_question_id_survey_questions_id_fk": { + "name": "survey_answer_options_question_id_survey_questions_id_fk", + "tableFrom": "survey_answer_options", + "columnsFrom": [ + "question_id" + ], + "tableTo": "survey_questions", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "survey_answer_options_option_id_survey_options_id_fk": { + "name": "survey_answer_options_option_id_survey_options_id_fk", + "tableFrom": "survey_answer_options", + "columnsFrom": [ + "option_id" + ], + "tableTo": "survey_options", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_answers": { + "name": "survey_answers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "response_id": { + "name": "response_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "text_value": { + "name": "text_value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "survey_answers_response_idx": { + "name": "survey_answers_response_idx", + "columns": [ + { + "expression": "response_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "survey_answers_question_idx": { + "name": "survey_answers_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "survey_answers_response_id_survey_responses_id_fk": { + "name": "survey_answers_response_id_survey_responses_id_fk", + "tableFrom": "survey_answers", + "columnsFrom": [ + "response_id" + ], + "tableTo": "survey_responses", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "survey_answers_question_id_survey_questions_id_fk": { + "name": "survey_answers_question_id_survey_questions_id_fk", + "tableFrom": "survey_answers", + "columnsFrom": [ + "question_id" + ], + "tableTo": "survey_questions", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_options": { + "name": "survey_options", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "question_id": { + "name": "question_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "survey_options_question_idx": { + "name": "survey_options_question_idx", + "columns": [ + { + "expression": "question_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "survey_options_question_id_survey_questions_id_fk": { + "name": "survey_options_question_id_survey_questions_id_fk", + "tableFrom": "survey_options", + "columnsFrom": [ + "question_id" + ], + "tableTo": "survey_questions", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_questions": { + "name": "survey_questions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "survey_question_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "survey_questions_survey_idx": { + "name": "survey_questions_survey_idx", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "survey_questions_survey_id_surveys_id_fk": { + "name": "survey_questions_survey_id_surveys_id_fk", + "tableFrom": "survey_questions", + "columnsFrom": [ + "survey_id" + ], + "tableTo": "surveys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.survey_responses": { + "name": "survey_responses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "survey_id": { + "name": "survey_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "survey_responses_survey_user_uniq": { + "name": "survey_responses_survey_user_uniq", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "survey_responses_survey_id_surveys_id_fk": { + "name": "survey_responses_survey_id_surveys_id_fk", + "tableFrom": "survey_responses", + "columnsFrom": [ + "survey_id" + ], + "tableTo": "surveys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "survey_responses_user_id_users_id_fk": { + "name": "survey_responses_user_id_users_id_fk", + "tableFrom": "survey_responses", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.surveys": { + "name": "surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "results_published": { + "name": "results_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_publish_on_close": { + "name": "auto_publish_on_close", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "anonymity": { + "name": "anonymity", + "type": "survey_anonymity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "surveys_huddle_created_idx": { + "name": "surveys_huddle_created_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "surveys_huddle_id_huddles_id_fk": { + "name": "surveys_huddle_id_huddles_id_fk", + "tableFrom": "surveys", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "surveys_author_id_users_id_fk": { + "name": "surveys_author_id_users_id_fk", + "tableFrom": "surveys", + "columnsFrom": [ + "author_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_claims": { + "name": "team_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "huddle_id": { + "name": "huddle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "roster_id": { + "name": "roster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "claim_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_claims_huddle_roster_approved_uniq": { + "name": "team_claims_huddle_roster_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "roster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"team_claims\".\"status\" = 'approved'", + "concurrently": false + }, + "team_claims_huddle_user_approved_uniq": { + "name": "team_claims_huddle_user_approved_uniq", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"team_claims\".\"status\" = 'approved'", + "concurrently": false + }, + "team_claims_huddle_idx": { + "name": "team_claims_huddle_idx", + "columns": [ + { + "expression": "huddle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "team_claims_user_idx": { + "name": "team_claims_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "team_claims_huddle_id_huddles_id_fk": { + "name": "team_claims_huddle_id_huddles_id_fk", + "tableFrom": "team_claims", + "columnsFrom": [ + "huddle_id" + ], + "tableTo": "huddles", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "team_claims_user_id_users_id_fk": { + "name": "team_claims_user_id_users_id_fk", + "tableFrom": "team_claims", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "team_claims_decided_by_users_id_fk": { + "name": "team_claims_decided_by_users_id_fk", + "tableFrom": "team_claims", + "columnsFrom": [ + "decided_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_username": { + "name": "sleeper_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sleeper_user_id": { + "name": "sleeper_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "synced_league_ids": { + "name": "synced_league_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_placeholder": { + "name": "is_placeholder", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "identity_synced_at": { + "name": "identity_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "users_username_idx": { + "name": "users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.claim_status": { + "name": "claim_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected" + ] + }, + "public.poll_results_visibility": { + "name": "poll_results_visibility", + "schema": "public", + "values": [ + "always", + "after_vote", + "after_close" + ] + }, + "public.side_bet_status": { + "name": "side_bet_status", + "schema": "public", + "values": [ + "pending", + "accepted", + "rejected", + "cancelled", + "settled" + ] + }, + "public.survey_anonymity": { + "name": "survey_anonymity", + "schema": "public", + "values": [ + "none", + "anonymous_to_league", + "anonymous_to_all" + ] + }, + "public.survey_question_type": { + "name": "survey_question_type", + "schema": "public", + "values": [ + "short_text", + "paragraph", + "multiple_choice", + "checkboxes" + ] + } + }, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/_journal.json b/server/drizzle/meta/_journal.json index 05bed97..ba2d22a 100644 --- a/server/drizzle/meta/_journal.json +++ b/server/drizzle/meta/_journal.json @@ -106,6 +106,20 @@ "when": 1786317326520, "tag": "0014_rename_legacy_pkeys", "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1786317846534, + "tag": "0015_drop_huddle_prefix", + "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1786318032277, + "tag": "0016_fix_truncated_fk_name", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/scripts/backfill-users.mjs b/server/scripts/backfill-users.mjs index 05a4d1e..eb0e89b 100644 --- a/server/scripts/backfill-users.mjs +++ b/server/scripts/backfill-users.mjs @@ -39,24 +39,24 @@ const clerk = createClerkClient({ secretKey }); * belongs here too, or the next FK migration will fail on orphaned rows. */ const USER_ID_COLUMNS = [ - ["huddle_commissioners", "user_id"], - ["huddle_commissioners", "added_by"], - ["huddle_team_claims", "user_id"], - ["huddle_team_claims", "decided_by"], - ["huddle_announcements", "author_id"], - ["huddle_dues_payments", "marked_by"], - ["huddle_awards", "granted_by"], - ["huddle_side_bets", "proposer_id"], - ["huddle_side_bets", "opponent_id"], - ["huddle_side_bets", "winner_id"], - ["huddle_forum_topics", "author_id"], - ["huddle_forum_topics", "deleted_by"], - ["huddle_forum_replies", "author_id"], - ["huddle_forum_replies", "deleted_by"], - ["huddle_polls", "author_id"], - ["huddle_poll_votes", "user_id"], - ["huddle_surveys", "author_id"], - ["huddle_survey_responses", "user_id"], + ["commissioners", "user_id"], + ["commissioners", "added_by"], + ["team_claims", "user_id"], + ["team_claims", "decided_by"], + ["announcements", "author_id"], + ["dues_payments", "marked_by"], + ["awards", "granted_by"], + ["side_bets", "proposer_id"], + ["side_bets", "opponent_id"], + ["side_bets", "winner_id"], + ["forum_topics", "author_id"], + ["forum_topics", "deleted_by"], + ["forum_replies", "author_id"], + ["forum_replies", "deleted_by"], + ["polls", "author_id"], + ["poll_votes", "user_id"], + ["surveys", "author_id"], + ["survey_responses", "user_id"], ]; const PAGE_SIZE = 500; diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index 5e5251e..6266371 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -110,8 +110,8 @@ export const huddles = pgTable( }), ); -export const huddleCommissioners = pgTable( - "huddle_commissioners", +export const commissioners = pgTable( + "commissioners", { huddleId: uuid("huddle_id") .notNull() @@ -128,12 +128,12 @@ export const huddleCommissioners = pgTable( }, (t) => ({ pk: primaryKey({ columns: [t.huddleId, t.userId] }), - byUser: index("huddle_commissioners_user_idx").on(t.userId), + byUser: index("commissioners_user_idx").on(t.userId), }), ); -export const huddleTeamClaims = pgTable( - "huddle_team_claims", +export const teamClaims = pgTable( + "team_claims", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -154,21 +154,21 @@ export const huddleTeamClaims = pgTable( }), }, (t) => ({ - uniqApprovedRoster: uniqueIndex("huddle_team_claims_huddle_roster_approved_uniq") + uniqApprovedRoster: uniqueIndex("team_claims_huddle_roster_approved_uniq") .on(t.huddleId, t.rosterId) .where(sql`${t.status} = 'approved'`), - uniqApprovedUser: uniqueIndex("huddle_team_claims_huddle_user_approved_uniq") + uniqApprovedUser: uniqueIndex("team_claims_huddle_user_approved_uniq") .on(t.huddleId, t.userId) .where(sql`${t.status} = 'approved'`), - byHuddle: index("huddle_team_claims_huddle_idx").on(t.huddleId), - byUser: index("huddle_team_claims_user_idx").on(t.userId), + byHuddle: index("team_claims_huddle_idx").on(t.huddleId), + byUser: index("team_claims_user_idx").on(t.userId), }), ); // ── Announcements ───────────────────────────────────────────────────────────── -export const huddleAnnouncements = pgTable( - "huddle_announcements", +export const announcements = pgTable( + "announcements", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -187,13 +187,13 @@ export const huddleAnnouncements = pgTable( .notNull(), }, (t) => ({ - byHuddle: index("huddle_announcements_huddle_idx").on(t.huddleId), + byHuddle: index("announcements_huddle_idx").on(t.huddleId), }), ); // ── Dues tracker ────────────────────────────────────────────────────────────── -export const huddleDuesConfig = pgTable("huddle_dues_config", { +export const duesConfig = pgTable("dues_config", { huddleId: uuid("huddle_id") .primaryKey() .references(() => huddles.id, { onDelete: "cascade" }), @@ -205,8 +205,8 @@ export const huddleDuesConfig = pgTable("huddle_dues_config", { .notNull(), }); -export const huddleDuesPayments = pgTable( - "huddle_dues_payments", +export const duesPayments = pgTable( + "dues_payments", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -223,7 +223,7 @@ export const huddleDuesPayments = pgTable( .notNull(), }, (t) => ({ - uniqRoster: uniqueIndex("huddle_dues_payments_huddle_roster_uniq").on( + uniqRoster: uniqueIndex("dues_payments_huddle_roster_uniq").on( t.huddleId, t.rosterId, ), @@ -232,18 +232,18 @@ export const huddleDuesPayments = pgTable( export type Huddle = typeof huddles.$inferSelect; export type NewHuddle = typeof huddles.$inferInsert; -export type HuddleCommissioner = typeof huddleCommissioners.$inferSelect; -export type TeamClaim = typeof huddleTeamClaims.$inferSelect; -export type NewTeamClaim = typeof huddleTeamClaims.$inferInsert; -export type HuddleAnnouncement = typeof huddleAnnouncements.$inferSelect; -export type NewHuddleAnnouncement = typeof huddleAnnouncements.$inferInsert; -export type HuddleDuesConfig = typeof huddleDuesConfig.$inferSelect; -export type HuddleDuesPayment = typeof huddleDuesPayments.$inferSelect; +export type HuddleCommissioner = typeof commissioners.$inferSelect; +export type TeamClaim = typeof teamClaims.$inferSelect; +export type NewTeamClaim = typeof teamClaims.$inferInsert; +export type HuddleAnnouncement = typeof announcements.$inferSelect; +export type NewHuddleAnnouncement = typeof announcements.$inferInsert; +export type HuddleDuesConfig = typeof duesConfig.$inferSelect; +export type HuddleDuesPayment = typeof duesPayments.$inferSelect; // ── Custom awards ───────────────────────────────────────────────────────────── -export const huddleAwards = pgTable( - "huddle_awards", +export const awards = pgTable( + "awards", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -266,18 +266,18 @@ export const huddleAwards = pgTable( .notNull(), }, (t) => ({ - byHuddle: index("huddle_awards_huddle_idx").on(t.huddleId), - byHuddleRoster: index("huddle_awards_huddle_roster_idx").on(t.huddleId, t.rosterId), + byHuddle: index("awards_huddle_idx").on(t.huddleId), + byHuddleRoster: index("awards_huddle_roster_idx").on(t.huddleId, t.rosterId), }), ); -export type HuddleAward = typeof huddleAwards.$inferSelect; -export type NewHuddleAward = typeof huddleAwards.$inferInsert; +export type HuddleAward = typeof awards.$inferSelect; +export type NewHuddleAward = typeof awards.$inferInsert; // ── Payout structure ────────────────────────────────────────────────────────── -export const huddlePayoutEntries = pgTable( - "huddle_payout_entries", +export const payoutEntries = pgTable( + "payout_entries", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -294,20 +294,20 @@ export const huddlePayoutEntries = pgTable( .notNull(), }, (t) => ({ - byHuddle: index("huddle_payout_entries_huddle_idx").on(t.huddleId), + byHuddle: index("payout_entries_huddle_idx").on(t.huddleId), }), ); -export type HuddlePayoutEntry = typeof huddlePayoutEntries.$inferSelect; -export type NewHuddlePayoutEntry = typeof huddlePayoutEntries.$inferInsert; +export type HuddlePayoutEntry = typeof payoutEntries.$inferSelect; +export type NewHuddlePayoutEntry = typeof payoutEntries.$inferInsert; // ── Active trophy control ───────────────────────────────────────────────────── // Commissioners choose which built-in auto-trophies are shown for their league. // A row exists for each type the commissioner has explicitly toggled; if no row // exists, the trophy is considered enabled (opt-out model, not opt-in). -export const huddleActiveTrophies = pgTable( - "huddle_active_trophies", +export const activeTrophies = pgTable( + "active_trophies", { huddleId: uuid("huddle_id") .notNull() @@ -324,7 +324,7 @@ export const huddleActiveTrophies = pgTable( }), ); -export type HuddleActiveTrophy = typeof huddleActiveTrophies.$inferSelect; +export type HuddleActiveTrophy = typeof activeTrophies.$inferSelect; // ── Side bets ────────────────────────────────────────────────────────────────── // League members propose bets against each other, tied to a specific week. @@ -338,8 +338,8 @@ export const sideBetStatus = pgEnum("side_bet_status", [ "settled", // winner determined ]); -export const huddleSideBets = pgTable( - "huddle_side_bets", +export const sideBets = pgTable( + "side_bets", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -373,20 +373,20 @@ export const huddleSideBets = pgTable( .notNull(), }, (t) => ({ - byHuddle: index("huddle_side_bets_huddle_idx").on(t.huddleId), - byProposer: index("huddle_side_bets_proposer_idx").on(t.huddleId, t.proposerId), - byOpponent: index("huddle_side_bets_opponent_idx").on(t.huddleId, t.opponentId), + byHuddle: index("side_bets_huddle_idx").on(t.huddleId), + byProposer: index("side_bets_proposer_idx").on(t.huddleId, t.proposerId), + byOpponent: index("side_bets_opponent_idx").on(t.huddleId, t.opponentId), }), ); -export type SideBet = typeof huddleSideBets.$inferSelect; -export type NewSideBet = typeof huddleSideBets.$inferInsert; +export type SideBet = typeof sideBets.$inferSelect; +export type NewSideBet = typeof sideBets.$inferInsert; // ── Countdown widget ───────────────────────────────────────────────────────── // Commissioner-configured countdown shown on the dashboard, e.g. counting down // to draft night or the trade deadline. One row per huddle. -export const huddleCountdownConfig = pgTable("huddle_countdown_config", { +export const countdownConfig = pgTable("countdown_config", { huddleId: uuid("huddle_id") .primaryKey() .references(() => huddles.id, { onDelete: "cascade" }), @@ -399,7 +399,7 @@ export const huddleCountdownConfig = pgTable("huddle_countdown_config", { .notNull(), }); -export type HuddleCountdownConfig = typeof huddleCountdownConfig.$inferSelect; +export type HuddleCountdownConfig = typeof countdownConfig.$inferSelect; // ── League forum ────────────────────────────────────────────────────────────── // Message-board style discussion, scoped to a huddle (persists across seasons). @@ -407,8 +407,8 @@ export type HuddleCountdownConfig = typeof huddleCountdownConfig.$inferSelect; // Deletes are soft (deletedAt/deletedBy) so pagination stays stable and // moderation leaves an audit trail. -export const huddleForumTopics = pgTable( - "huddle_forum_topics", +export const forumTopics = pgTable( + "forum_topics", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -434,20 +434,20 @@ export const huddleForumTopics = pgTable( }), }, (t) => ({ - byHuddleActivity: index("huddle_forum_topics_huddle_updated_idx").on( + byHuddleActivity: index("forum_topics_huddle_updated_idx").on( t.huddleId, t.updatedAt, ), }), ); -export const huddleForumReplies = pgTable( - "huddle_forum_replies", +export const forumReplies = pgTable( + "forum_replies", { id: uuid("id").defaultRandom().primaryKey(), topicId: uuid("topic_id") .notNull() - .references(() => huddleForumTopics.id, { onDelete: "cascade" }), + .references(() => forumTopics.id, { onDelete: "cascade" }), /** Denormalized from the topic so replies can be queried/indexed directly. */ huddleId: uuid("huddle_id") .notNull() @@ -465,12 +465,12 @@ export const huddleForumReplies = pgTable( }), }, (t) => ({ - byTopic: index("huddle_forum_replies_topic_idx").on(t.topicId, t.createdAt), + byTopic: index("forum_replies_topic_idx").on(t.topicId, t.createdAt), }), ); -export type HuddleForumTopic = typeof huddleForumTopics.$inferSelect; -export type HuddleForumReply = typeof huddleForumReplies.$inferSelect; +export type HuddleForumTopic = typeof forumTopics.$inferSelect; +export type HuddleForumReply = typeof forumReplies.$inferSelect; // ── Polls ───────────────────────────────────────────────────────────────────── // Two contexts share the same tables: a poll attached to a forum topic @@ -486,8 +486,8 @@ export const pollResultsVisibility = pgEnum("poll_results_visibility", [ "after_close", // results hidden until the poll's closesAt has passed ]); -export const huddlePolls = pgTable( - "huddle_polls", +export const polls = pgTable( + "polls", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -496,7 +496,7 @@ export const huddlePolls = pgTable( authorId: text("author_id") .notNull() .references(() => users.id, { onDelete: "restrict" }), - topicId: uuid("topic_id").references(() => huddleForumTopics.id, { onDelete: "cascade" }), + topicId: uuid("topic_id").references(() => forumTopics.id, { onDelete: "cascade" }), isDashboardPoll: boolean("is_dashboard_poll").notNull().default(false), question: text("question").notNull(), allowMultiple: boolean("allow_multiple").notNull().default(false), @@ -509,39 +509,39 @@ export const huddlePolls = pgTable( .notNull(), }, (t) => ({ - byTopic: index("huddle_polls_topic_idx").on(t.topicId), + byTopic: index("polls_topic_idx").on(t.topicId), // Enforces "at most one active dashboard poll per huddle" at the DB level. - uniqActiveDashboardPoll: uniqueIndex("huddle_polls_dashboard_active_uniq") + uniqActiveDashboardPoll: uniqueIndex("polls_dashboard_active_uniq") .on(t.huddleId) .where(sql`${t.isDashboardPoll} = true`), }), ); -export const huddlePollOptions = pgTable( - "huddle_poll_options", +export const pollOptions = pgTable( + "poll_options", { id: uuid("id").defaultRandom().primaryKey(), pollId: uuid("poll_id") .notNull() - .references(() => huddlePolls.id, { onDelete: "cascade" }), + .references(() => polls.id, { onDelete: "cascade" }), label: text("label").notNull(), sortOrder: integer("sort_order").notNull().default(0), }, (t) => ({ - byPoll: index("huddle_poll_options_poll_idx").on(t.pollId), + byPoll: index("poll_options_poll_idx").on(t.pollId), }), ); -export const huddlePollVotes = pgTable( - "huddle_poll_votes", +export const pollVotes = pgTable( + "poll_votes", { id: uuid("id").defaultRandom().primaryKey(), pollId: uuid("poll_id") .notNull() - .references(() => huddlePolls.id, { onDelete: "cascade" }), + .references(() => polls.id, { onDelete: "cascade" }), optionId: uuid("option_id") .notNull() - .references(() => huddlePollOptions.id, { onDelete: "cascade" }), + .references(() => pollOptions.id, { onDelete: "cascade" }), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "restrict" }), @@ -550,24 +550,24 @@ export const huddlePollVotes = pgTable( .notNull(), }, (t) => ({ - byPollUser: index("huddle_poll_votes_poll_user_idx").on(t.pollId, t.userId), - uniqOptionVote: uniqueIndex("huddle_poll_votes_option_user_uniq").on(t.optionId, t.userId), + byPollUser: index("poll_votes_poll_user_idx").on(t.pollId, t.userId), + uniqOptionVote: uniqueIndex("poll_votes_option_user_uniq").on(t.optionId, t.userId), }), ); -export type HuddlePoll = typeof huddlePolls.$inferSelect; -export type HuddlePollOption = typeof huddlePollOptions.$inferSelect; -export type HuddlePollVote = typeof huddlePollVotes.$inferSelect; +export type HuddlePoll = typeof polls.$inferSelect; +export type HuddlePollOption = typeof pollOptions.$inferSelect; +export type HuddlePollVote = typeof pollVotes.$inferSelect; // ── Surveys ─────────────────────────────────────────────────────────────────── // A commissioner-authored, multi-question form (short/long text or // single/multi-select), scoped to a huddle. Modeled as "a poll with several -// questions": huddleSurveyOptions plays the role of huddlePollOptions, and -// huddleSurveyAnswerOptions plays the role of huddlePollVotes (one row per -// selected option). Text-type answers live in the separate huddleSurveyAnswers +// questions": surveyOptions plays the role of pollOptions, and +// surveyAnswerOptions plays the role of pollVotes (one row per +// selected option). Text-type answers live in the separate surveyAnswers // table since they don't reference an option row. // -// Each member gets at most one huddleSurveyResponses row per survey; editing +// Each member gets at most one surveyResponses row per survey; editing // a response deletes and reinserts its answers (see surveyService.submitResponse), // mirroring pollService.castVote. Editing is only allowed before closesAt. @@ -592,8 +592,8 @@ export const surveyAnonymity = pgEnum("survey_anonymity", [ "anonymous_to_all", ]); -export const huddleSurveys = pgTable( - "huddle_surveys", +export const surveys = pgTable( + "surveys", { id: uuid("id").defaultRandom().primaryKey(), huddleId: uuid("huddle_id") @@ -616,50 +616,50 @@ export const huddleSurveys = pgTable( .notNull(), }, (t) => ({ - byHuddleActivity: index("huddle_surveys_huddle_created_idx").on(t.huddleId, t.createdAt), + byHuddleActivity: index("surveys_huddle_created_idx").on(t.huddleId, t.createdAt), }), ); -export const huddleSurveyQuestions = pgTable( - "huddle_survey_questions", +export const surveyQuestions = pgTable( + "survey_questions", { id: uuid("id").defaultRandom().primaryKey(), surveyId: uuid("survey_id") .notNull() - .references(() => huddleSurveys.id, { onDelete: "cascade" }), + .references(() => surveys.id, { onDelete: "cascade" }), type: surveyQuestionType("type").notNull(), prompt: text("prompt").notNull(), required: boolean("required").notNull().default(true), sortOrder: integer("sort_order").notNull().default(0), }, (t) => ({ - bySurvey: index("huddle_survey_questions_survey_idx").on(t.surveyId, t.sortOrder), + bySurvey: index("survey_questions_survey_idx").on(t.surveyId, t.sortOrder), }), ); /** Choices for multiple_choice / checkboxes questions. */ -export const huddleSurveyOptions = pgTable( - "huddle_survey_options", +export const surveyOptions = pgTable( + "survey_options", { id: uuid("id").defaultRandom().primaryKey(), questionId: uuid("question_id") .notNull() - .references(() => huddleSurveyQuestions.id, { onDelete: "cascade" }), + .references(() => surveyQuestions.id, { onDelete: "cascade" }), label: text("label").notNull(), sortOrder: integer("sort_order").notNull().default(0), }, (t) => ({ - byQuestion: index("huddle_survey_options_question_idx").on(t.questionId), + byQuestion: index("survey_options_question_idx").on(t.questionId), }), ); -export const huddleSurveyResponses = pgTable( - "huddle_survey_responses", +export const surveyResponses = pgTable( + "survey_responses", { id: uuid("id").defaultRandom().primaryKey(), surveyId: uuid("survey_id") .notNull() - .references(() => huddleSurveys.id, { onDelete: "cascade" }), + .references(() => surveys.id, { onDelete: "cascade" }), userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "restrict" }), @@ -671,7 +671,7 @@ export const huddleSurveyResponses = pgTable( .notNull(), }, (t) => ({ - uniqSurveyUser: uniqueIndex("huddle_survey_responses_survey_user_uniq").on( + uniqSurveyUser: uniqueIndex("survey_responses_survey_user_uniq").on( t.surveyId, t.userId, ), @@ -679,51 +679,51 @@ export const huddleSurveyResponses = pgTable( ); /** short_text / paragraph answers — one row per question per response. */ -export const huddleSurveyAnswers = pgTable( - "huddle_survey_answers", +export const surveyAnswers = pgTable( + "survey_answers", { id: uuid("id").defaultRandom().primaryKey(), responseId: uuid("response_id") .notNull() - .references(() => huddleSurveyResponses.id, { onDelete: "cascade" }), + .references(() => surveyResponses.id, { onDelete: "cascade" }), questionId: uuid("question_id") .notNull() - .references(() => huddleSurveyQuestions.id, { onDelete: "cascade" }), + .references(() => surveyQuestions.id, { onDelete: "cascade" }), textValue: text("text_value").notNull(), }, (t) => ({ - byResponse: index("huddle_survey_answers_response_idx").on(t.responseId), - byQuestion: index("huddle_survey_answers_question_idx").on(t.questionId), + byResponse: index("survey_answers_response_idx").on(t.responseId), + byQuestion: index("survey_answers_question_idx").on(t.questionId), }), ); /** multiple_choice / checkboxes answers — one row per selected option. */ -export const huddleSurveyAnswerOptions = pgTable( - "huddle_survey_answer_options", +export const surveyAnswerOptions = pgTable( + "survey_answer_options", { id: uuid("id").defaultRandom().primaryKey(), responseId: uuid("response_id") .notNull() - .references(() => huddleSurveyResponses.id, { onDelete: "cascade" }), + .references(() => surveyResponses.id, { onDelete: "cascade" }), questionId: uuid("question_id") .notNull() - .references(() => huddleSurveyQuestions.id, { onDelete: "cascade" }), + .references(() => surveyQuestions.id, { onDelete: "cascade" }), optionId: uuid("option_id") .notNull() - .references(() => huddleSurveyOptions.id, { onDelete: "cascade" }), + .references(() => surveyOptions.id, { onDelete: "cascade" }), }, (t) => ({ - byQuestion: index("huddle_survey_answer_options_question_idx").on(t.questionId), - uniqResponseOption: uniqueIndex("huddle_survey_answer_options_response_option_uniq").on( + byQuestion: index("survey_answer_options_question_idx").on(t.questionId), + uniqResponseOption: uniqueIndex("survey_answer_options_response_option_uniq").on( t.responseId, t.optionId, ), }), ); -export type HuddleSurvey = typeof huddleSurveys.$inferSelect; -export type HuddleSurveyQuestion = typeof huddleSurveyQuestions.$inferSelect; -export type HuddleSurveyOption = typeof huddleSurveyOptions.$inferSelect; -export type HuddleSurveyResponse = typeof huddleSurveyResponses.$inferSelect; -export type HuddleSurveyAnswer = typeof huddleSurveyAnswers.$inferSelect; -export type HuddleSurveyAnswerOption = typeof huddleSurveyAnswerOptions.$inferSelect; +export type HuddleSurvey = typeof surveys.$inferSelect; +export type HuddleSurveyQuestion = typeof surveyQuestions.$inferSelect; +export type HuddleSurveyOption = typeof surveyOptions.$inferSelect; +export type HuddleSurveyResponse = typeof surveyResponses.$inferSelect; +export type HuddleSurveyAnswer = typeof surveyAnswers.$inferSelect; +export type HuddleSurveyAnswerOption = typeof surveyAnswerOptions.$inferSelect; diff --git a/server/src/services/awardsService.ts b/server/src/services/awardsService.ts index e55f78d..5bd1983 100644 --- a/server/src/services/awardsService.ts +++ b/server/src/services/awardsService.ts @@ -6,7 +6,7 @@ */ import { and, desc, eq } from "drizzle-orm"; import { db } from "../db/client.js"; -import { huddleAwards, type HuddleAward } from "../db/schema.js"; +import { awards, type HuddleAward } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner } from "./huddlesService.js"; const fail = (status: number, message: string): never => { @@ -98,9 +98,9 @@ function validateAwardInput(input: { export async function listAwards(huddleId: string): Promise { return db .select() - .from(huddleAwards) - .where(eq(huddleAwards.huddleId, huddleId)) - .orderBy(desc(huddleAwards.createdAt)); + .from(awards) + .where(eq(awards.huddleId, huddleId)) + .orderBy(desc(awards.createdAt)); } /** @@ -112,14 +112,14 @@ export async function listAwardsForRoster( ): Promise { return db .select() - .from(huddleAwards) + .from(awards) .where( and( - eq(huddleAwards.huddleId, huddleId), - eq(huddleAwards.rosterId, rosterId), + eq(awards.huddleId, huddleId), + eq(awards.rosterId, rosterId), ), ) - .orderBy(desc(huddleAwards.createdAt)); + .orderBy(desc(awards.createdAt)); } // ── Mutations ───────────────────────────────────────────────────────────────── @@ -145,7 +145,7 @@ export async function createAward( const validated = validateAwardInput(input); const [created] = await db - .insert(huddleAwards) + .insert(awards) .values({ huddleId, grantedBy: userId, @@ -178,8 +178,8 @@ export async function updateAward( const rows = await db .select() - .from(huddleAwards) - .where(and(eq(huddleAwards.id, awardId), eq(huddleAwards.huddleId, huddleId))) + .from(awards) + .where(and(eq(awards.id, awardId), eq(awards.huddleId, huddleId))) .limit(1); if (!rows[0]) fail(404, "Award not found"); @@ -187,9 +187,9 @@ export async function updateAward( const validated = validateAwardInput(input); const [updated] = await db - .update(huddleAwards) + .update(awards) .set({ ...validated, updatedAt: new Date() }) - .where(eq(huddleAwards.id, awardId)) + .where(eq(awards.id, awardId)) .returning(); if (!updated) fail(500, "Failed to update award"); @@ -209,11 +209,11 @@ export async function deleteAward( const rows = await db .select() - .from(huddleAwards) + .from(awards) .where( and( - eq(huddleAwards.id, awardId), - eq(huddleAwards.huddleId, huddleId), + eq(awards.id, awardId), + eq(awards.huddleId, huddleId), ), ) .limit(1); @@ -221,6 +221,6 @@ export async function deleteAward( if (!rows[0]) fail(404, "Award not found"); await db - .delete(huddleAwards) - .where(eq(huddleAwards.id, awardId)); + .delete(awards) + .where(eq(awards.id, awardId)); } diff --git a/server/src/services/countdownService.ts b/server/src/services/countdownService.ts index baf3140..c41badd 100644 --- a/server/src/services/countdownService.ts +++ b/server/src/services/countdownService.ts @@ -7,7 +7,7 @@ import { eq } from "drizzle-orm"; import { db } from "../db/client.js"; import { - huddleCountdownConfig, + countdownConfig, type HuddleCountdownConfig, } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner } from "./huddlesService.js"; @@ -27,8 +27,8 @@ export async function getCountdownConfig( ): Promise { const rows = await db .select() - .from(huddleCountdownConfig) - .where(eq(huddleCountdownConfig.huddleId, huddleId)) + .from(countdownConfig) + .where(eq(countdownConfig.huddleId, huddleId)) .limit(1); return rows[0] ?? null; } @@ -64,7 +64,7 @@ export async function setCountdownConfig( if (isNaN(targetAt.getTime())) fail(400, "targetAt must be a valid date"); const [row] = await db - .insert(huddleCountdownConfig) + .insert(countdownConfig) .values({ huddleId, title, @@ -74,7 +74,7 @@ export async function setCountdownConfig( updatedAt: new Date(), }) .onConflictDoUpdate({ - target: huddleCountdownConfig.huddleId, + target: countdownConfig.huddleId, set: { title, subtitle, diff --git a/server/src/services/duesService.ts b/server/src/services/duesService.ts index 677e5d2..5f755a5 100644 --- a/server/src/services/duesService.ts +++ b/server/src/services/duesService.ts @@ -9,8 +9,8 @@ import { and, eq } from "drizzle-orm"; import { db } from "../db/client.js"; import { - huddleDuesConfig, - huddleDuesPayments, + duesConfig, + duesPayments, type HuddleDuesConfig, type HuddleDuesPayment, } from "../db/schema.js"; @@ -28,8 +28,8 @@ export async function getDuesConfig( ): Promise { const rows = await db .select() - .from(huddleDuesConfig) - .where(eq(huddleDuesConfig.huddleId, huddleId)) + .from(duesConfig) + .where(eq(duesConfig.huddleId, huddleId)) .limit(1); return rows[0] ?? null; } @@ -43,8 +43,8 @@ export async function getDuesPayments( ): Promise { return db .select() - .from(huddleDuesPayments) - .where(eq(huddleDuesPayments.huddleId, huddleId)); + .from(duesPayments) + .where(eq(duesPayments.huddleId, huddleId)); } // ---- Mutations ---- @@ -65,7 +65,7 @@ export async function setDuesConfig( fail(400, "amount must be a non-negative integer (cents)"); const [row] = await db - .insert(huddleDuesConfig) + .insert(duesConfig) .values({ huddleId, amount: opts.amount, @@ -74,7 +74,7 @@ export async function setDuesConfig( updatedAt: new Date(), }) .onConflictDoUpdate({ - target: huddleDuesConfig.huddleId, + target: duesConfig.huddleId, set: { amount: opts.amount, season: opts.season ?? null, @@ -108,7 +108,7 @@ export async function setDuesPaid( const paidAt = paid ? new Date() : null; const [row] = await db - .insert(huddleDuesPayments) + .insert(duesPayments) .values({ huddleId, rosterId, @@ -118,7 +118,7 @@ export async function setDuesPaid( updatedAt: new Date(), }) .onConflictDoUpdate({ - target: [huddleDuesPayments.huddleId, huddleDuesPayments.rosterId], + target: [duesPayments.huddleId, duesPayments.rosterId], set: { paidAt, markedBy: userId, diff --git a/server/src/services/forumService.ts b/server/src/services/forumService.ts index fc2e110..714e394 100644 --- a/server/src/services/forumService.ts +++ b/server/src/services/forumService.ts @@ -10,8 +10,8 @@ import { and, asc, desc, eq, gt, isNull, lt } from "drizzle-orm"; import { db } from "../db/client.js"; import { - huddleForumTopics, - huddleForumReplies, + forumTopics, + forumReplies, type HuddleForumTopic, type HuddleForumReply, } from "../db/schema.js"; @@ -35,14 +35,14 @@ export async function listTopics( opts: { limit?: number; before?: string } = {}, ): Promise { const limit = Math.min(opts.limit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE); - const conditions = [eq(huddleForumTopics.huddleId, huddleId), isNull(huddleForumTopics.deletedAt)]; - if (opts.before) conditions.push(lt(huddleForumTopics.updatedAt, new Date(opts.before))); + const conditions = [eq(forumTopics.huddleId, huddleId), isNull(forumTopics.deletedAt)]; + if (opts.before) conditions.push(lt(forumTopics.updatedAt, new Date(opts.before))); return db .select() - .from(huddleForumTopics) + .from(forumTopics) .where(and(...conditions)) - .orderBy(desc(huddleForumTopics.updatedAt)) + .orderBy(desc(forumTopics.updatedAt)) .limit(limit); } @@ -53,12 +53,12 @@ export async function getTopic( ): Promise { const rows = await db .select() - .from(huddleForumTopics) + .from(forumTopics) .where( and( - eq(huddleForumTopics.id, topicId), - eq(huddleForumTopics.huddleId, huddleId), - isNull(huddleForumTopics.deletedAt), + eq(forumTopics.id, topicId), + eq(forumTopics.huddleId, huddleId), + isNull(forumTopics.deletedAt), ), ) .limit(1); @@ -73,17 +73,17 @@ export async function listReplies( ): Promise { const limit = Math.min(opts.limit ?? MAX_PAGE_SIZE, MAX_PAGE_SIZE); const conditions = [ - eq(huddleForumReplies.topicId, topicId), - eq(huddleForumReplies.huddleId, huddleId), - isNull(huddleForumReplies.deletedAt), + eq(forumReplies.topicId, topicId), + eq(forumReplies.huddleId, huddleId), + isNull(forumReplies.deletedAt), ]; - if (opts.after) conditions.push(gt(huddleForumReplies.createdAt, new Date(opts.after))); + if (opts.after) conditions.push(gt(forumReplies.createdAt, new Date(opts.after))); return db .select() - .from(huddleForumReplies) + .from(forumReplies) .where(and(...conditions)) - .orderBy(asc(huddleForumReplies.createdAt)) + .orderBy(asc(forumReplies.createdAt)) .limit(limit); } @@ -108,7 +108,7 @@ export async function createTopic(opts: { fail(400, `body is required (max ${MAX_BODY_LEN} chars)`); const [row] = await db - .insert(huddleForumTopics) + .insert(forumTopics) .values({ huddleId: opts.huddleId, authorId: opts.userId, @@ -149,7 +149,7 @@ export async function createReply(opts: { fail(400, `body is required (max ${MAX_BODY_LEN} chars)`); const [row] = await db - .insert(huddleForumReplies) + .insert(forumReplies) .values({ topicId: opts.topicId, huddleId: opts.huddleId, @@ -161,9 +161,9 @@ export async function createReply(opts: { if (!row) fail(500, "Failed to post reply"); await db - .update(huddleForumTopics) + .update(forumTopics) .set({ updatedAt: new Date(), replyCount: topic.replyCount + 1 }) - .where(eq(huddleForumTopics.id, opts.topicId)); + .where(eq(forumTopics.id, opts.topicId)); return row!; } @@ -186,9 +186,9 @@ export async function deleteTopic(opts: { ); await db - .update(huddleForumTopics) + .update(forumTopics) .set({ deletedAt: new Date(), deletedBy: opts.userId }) - .where(eq(huddleForumTopics.id, opts.topicId)); + .where(eq(forumTopics.id, opts.topicId)); } export async function deleteReply(opts: { @@ -199,13 +199,13 @@ export async function deleteReply(opts: { }): Promise { const rows = await db .select() - .from(huddleForumReplies) + .from(forumReplies) .where( and( - eq(huddleForumReplies.id, opts.replyId), - eq(huddleForumReplies.topicId, opts.topicId), - eq(huddleForumReplies.huddleId, opts.huddleId), - isNull(huddleForumReplies.deletedAt), + eq(forumReplies.id, opts.replyId), + eq(forumReplies.topicId, opts.topicId), + eq(forumReplies.huddleId, opts.huddleId), + isNull(forumReplies.deletedAt), ), ) .limit(1); @@ -217,19 +217,19 @@ export async function deleteReply(opts: { fail(403, "Only the author or a commissioner can remove this reply"); await db - .update(huddleForumReplies) + .update(forumReplies) .set({ deletedAt: new Date(), deletedBy: opts.userId }) - .where(eq(huddleForumReplies.id, opts.replyId)); + .where(eq(forumReplies.id, opts.replyId)); const [topic] = await db .select() - .from(huddleForumTopics) - .where(eq(huddleForumTopics.id, opts.topicId)) + .from(forumTopics) + .where(eq(forumTopics.id, opts.topicId)) .limit(1); if (topic) { await db - .update(huddleForumTopics) + .update(forumTopics) .set({ replyCount: Math.max(0, topic.replyCount - 1) }) - .where(eq(huddleForumTopics.id, opts.topicId)); + .where(eq(forumTopics.id, opts.topicId)); } } diff --git a/server/src/services/huddlesService.ts b/server/src/services/huddlesService.ts index 16aa5ef..9b1573f 100644 --- a/server/src/services/huddlesService.ts +++ b/server/src/services/huddlesService.ts @@ -3,9 +3,9 @@ import { and, eq, count, inArray, desc } from "drizzle-orm"; import { db } from "../db/client.js"; import { huddles, - huddleCommissioners, - huddleAnnouncements, - huddleTeamClaims, + commissioners, + announcements, + teamClaims, type Huddle, type HuddleCommissioner, type HuddleAnnouncement, @@ -82,11 +82,11 @@ export async function isCommissioner( ): Promise { const rows = await db .select() - .from(huddleCommissioners) + .from(commissioners) .where( and( - eq(huddleCommissioners.huddleId, huddleId), - eq(huddleCommissioners.userId, userId), + eq(commissioners.huddleId, huddleId), + eq(commissioners.userId, userId), ), ) .limit(1); @@ -98,12 +98,12 @@ export async function isCommissioner( export async function hasApprovedClaim(huddleId: string, userId: string): Promise { const rows = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.huddleId, huddleId), - eq(huddleTeamClaims.userId, userId), - eq(huddleTeamClaims.status, "approved"), + eq(teamClaims.huddleId, huddleId), + eq(teamClaims.userId, userId), + eq(teamClaims.status, "approved"), ), ) .limit(1); @@ -113,8 +113,8 @@ export async function hasApprovedClaim(huddleId: string, userId: string): Promis async function commissionerCount(huddleId: string): Promise { const rows = await db .select({ n: count() }) - .from(huddleCommissioners) - .where(eq(huddleCommissioners.huddleId, huddleId)); + .from(commissioners) + .where(eq(commissioners.huddleId, huddleId)); return Number(rows[0]?.n ?? 0); } @@ -123,8 +123,8 @@ export async function listCommissioners( ): Promise { return db .select() - .from(huddleCommissioners) - .where(eq(huddleCommissioners.huddleId, huddleId)); + .from(commissioners) + .where(eq(commissioners.huddleId, huddleId)); } // ---- Huddle CRUD ---- @@ -144,7 +144,7 @@ export async function createHuddle(opts: { if (!created) fail(500, "Failed to create huddle"); // Seed the commissioner row - await db.insert(huddleCommissioners).values({ + await db.insert(commissioners).values({ huddleId: created!.id, userId: opts.commissionerUserId, addedBy: opts.commissionerUserId, @@ -161,16 +161,16 @@ export async function listHuddlesForUser( // Include pending claims so the user can see huddles they've requested to join const [commishRows, claimRows] = await Promise.all([ db - .select({ huddleId: huddleCommissioners.huddleId }) - .from(huddleCommissioners) - .where(eq(huddleCommissioners.userId, userId)), + .select({ huddleId: commissioners.huddleId }) + .from(commissioners) + .where(eq(commissioners.userId, userId)), db - .select({ huddleId: huddleTeamClaims.huddleId, status: huddleTeamClaims.status }) - .from(huddleTeamClaims) + .select({ huddleId: teamClaims.huddleId, status: teamClaims.status }) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.userId, userId), - inArray(huddleTeamClaims.status, ["approved", "pending"]), + eq(teamClaims.userId, userId), + inArray(teamClaims.status, ["approved", "pending"]), ), ), ]); @@ -255,7 +255,7 @@ export async function getHuddleByInviteCode( export async function listClaimsForHuddle( huddleId: string, ): Promise { - return db.select().from(huddleTeamClaims).where(eq(huddleTeamClaims.huddleId, huddleId)); + return db.select().from(teamClaims).where(eq(teamClaims.huddleId, huddleId)); } // ---- Claims ---- @@ -288,12 +288,12 @@ export async function submitClaim(opts: { const existingApproved = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.huddleId, opts.huddleId), - eq(huddleTeamClaims.userId, opts.userId), - eq(huddleTeamClaims.status, "approved"), + eq(teamClaims.huddleId, opts.huddleId), + eq(teamClaims.userId, opts.userId), + eq(teamClaims.status, "approved"), ), ) .limit(1); @@ -302,12 +302,12 @@ export async function submitClaim(opts: { const existingPending = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.huddleId, opts.huddleId), - eq(huddleTeamClaims.userId, opts.userId), - eq(huddleTeamClaims.status, "pending"), + eq(teamClaims.huddleId, opts.huddleId), + eq(teamClaims.userId, opts.userId), + eq(teamClaims.status, "pending"), ), ) .limit(1); @@ -316,19 +316,19 @@ export async function submitClaim(opts: { const rosterTaken = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.huddleId, opts.huddleId), - eq(huddleTeamClaims.rosterId, opts.rosterId as number), - eq(huddleTeamClaims.status, "approved"), + eq(teamClaims.huddleId, opts.huddleId), + eq(teamClaims.rosterId, opts.rosterId as number), + eq(teamClaims.status, "approved"), ), ) .limit(1); if (rosterTaken.length > 0) fail(409, "That team has already been claimed"); const [created] = await db - .insert(huddleTeamClaims) + .insert(teamClaims) .values({ huddleId: opts.huddleId, userId: opts.userId, @@ -352,11 +352,11 @@ export async function decideClaim(opts: { const rows = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.id, opts.claimId), - eq(huddleTeamClaims.huddleId, opts.huddleId), + eq(teamClaims.id, opts.claimId), + eq(teamClaims.huddleId, opts.huddleId), ), ) .limit(1); @@ -367,12 +367,12 @@ export async function decideClaim(opts: { if (opts.decision === "approved") { const rosterTaken = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.huddleId, opts.huddleId), - eq(huddleTeamClaims.rosterId, claim!.rosterId), - eq(huddleTeamClaims.status, "approved"), + eq(teamClaims.huddleId, opts.huddleId), + eq(teamClaims.rosterId, claim!.rosterId), + eq(teamClaims.status, "approved"), ), ) .limit(1); @@ -380,13 +380,13 @@ export async function decideClaim(opts: { } const [updated] = await db - .update(huddleTeamClaims) + .update(teamClaims) .set({ status: opts.decision, decidedAt: new Date(), decidedBy: opts.decidedBy, }) - .where(eq(huddleTeamClaims.id, opts.claimId)) + .where(eq(teamClaims.id, opts.claimId)) .returning(); if (!updated) fail(500, "Failed to update claim"); return updated!; @@ -405,12 +405,12 @@ export async function addCommissioner(opts: { // Check the new commissioner has an approved claim const hasClaim = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.huddleId, opts.huddleId), - eq(huddleTeamClaims.userId, opts.newUserId), - eq(huddleTeamClaims.status, "approved"), + eq(teamClaims.huddleId, opts.huddleId), + eq(teamClaims.userId, opts.newUserId), + eq(teamClaims.status, "approved"), ), ) .limit(1); @@ -418,7 +418,7 @@ export async function addCommissioner(opts: { fail(400, "User must have an approved team claim to become a commissioner"); const [row] = await db - .insert(huddleCommissioners) + .insert(commissioners) .values({ huddleId: opts.huddleId, userId: opts.newUserId, @@ -431,11 +431,11 @@ export async function addCommissioner(opts: { if (!row) { const existing = await db .select() - .from(huddleCommissioners) + .from(commissioners) .where( and( - eq(huddleCommissioners.huddleId, opts.huddleId), - eq(huddleCommissioners.userId, opts.newUserId), + eq(commissioners.huddleId, opts.huddleId), + eq(commissioners.userId, opts.newUserId), ), ) .limit(1); @@ -458,11 +458,11 @@ export async function removeCommissioner(opts: { fail(409, "Cannot remove the last commissioner — assign another first"); await db - .delete(huddleCommissioners) + .delete(commissioners) .where( and( - eq(huddleCommissioners.huddleId, opts.huddleId), - eq(huddleCommissioners.userId, opts.targetUserId), + eq(commissioners.huddleId, opts.huddleId), + eq(commissioners.userId, opts.targetUserId), ), ); } @@ -600,7 +600,7 @@ export async function createAnnouncement(opts: { fail(400, `body is required (max ${MAX_ANNOUNCEMENT_BODY_LEN} chars)`); const [created] = await db - .insert(huddleAnnouncements) + .insert(announcements) .values({ huddleId: opts.huddleId, authorId: opts.userId, @@ -618,9 +618,9 @@ export async function listAnnouncements( ): Promise { return db .select() - .from(huddleAnnouncements) - .where(eq(huddleAnnouncements.huddleId, huddleId)) - .orderBy(desc(huddleAnnouncements.createdAt)) + .from(announcements) + .where(eq(announcements.huddleId, huddleId)) + .orderBy(desc(announcements.createdAt)) .limit(limit); } @@ -634,19 +634,19 @@ export async function deleteAnnouncement(opts: { const rows = await db .select() - .from(huddleAnnouncements) + .from(announcements) .where( and( - eq(huddleAnnouncements.id, opts.announcementId), - eq(huddleAnnouncements.huddleId, opts.huddleId), + eq(announcements.id, opts.announcementId), + eq(announcements.huddleId, opts.huddleId), ), ) .limit(1); if (!rows[0]) fail(404, "Announcement not found"); await db - .delete(huddleAnnouncements) - .where(eq(huddleAnnouncements.id, opts.announcementId)); + .delete(announcements) + .where(eq(announcements.id, opts.announcementId)); } // Self-unclaim: user removes their own pending or approved claim @@ -657,19 +657,19 @@ export async function unclaimTeam(opts: { }): Promise { const rows = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.id, opts.claimId), - eq(huddleTeamClaims.huddleId, opts.huddleId), - eq(huddleTeamClaims.userId, opts.userId), + eq(teamClaims.id, opts.claimId), + eq(teamClaims.huddleId, opts.huddleId), + eq(teamClaims.userId, opts.userId), ), ) .limit(1); if (!rows[0]) fail(404, "Claim not found"); if (rows[0].status === "rejected") fail(409, "Claim has already been rejected"); - await db.delete(huddleTeamClaims).where(eq(huddleTeamClaims.id, opts.claimId)); + await db.delete(teamClaims).where(eq(teamClaims.id, opts.claimId)); } // Commissioner force-remove: remove any claim regardless of owner @@ -682,11 +682,11 @@ export async function forceRemoveClaim(opts: { fail(403, "Only a commissioner can remove team assignments"); const rows = await db .select() - .from(huddleTeamClaims) + .from(teamClaims) .where( and( - eq(huddleTeamClaims.id, opts.claimId), - eq(huddleTeamClaims.huddleId, opts.huddleId), + eq(teamClaims.id, opts.claimId), + eq(teamClaims.huddleId, opts.huddleId), ), ) .limit(1); @@ -700,5 +700,5 @@ export async function forceRemoveClaim(opts: { "You can't unlink yourself while you're the only commissioner — assign another commissioner first", ); } - await db.delete(huddleTeamClaims).where(eq(huddleTeamClaims.id, opts.claimId)); + await db.delete(teamClaims).where(eq(teamClaims.id, opts.claimId)); } diff --git a/server/src/services/payoutsService.ts b/server/src/services/payoutsService.ts index ecb433b..a1a6eb0 100644 --- a/server/src/services/payoutsService.ts +++ b/server/src/services/payoutsService.ts @@ -7,7 +7,7 @@ */ import { eq, asc } from "drizzle-orm"; import { db } from "../db/client.js"; -import { huddlePayoutEntries } from "../db/schema.js"; +import { payoutEntries } from "../db/schema.js"; import { isCommissioner, HuddlesServiceError } from "./huddlesService.js"; const fail = (status: number, msg: string): never => { @@ -23,9 +23,9 @@ export interface PayoutEntryInput { export async function listPayouts(huddleId: string) { return db .select() - .from(huddlePayoutEntries) - .where(eq(huddlePayoutEntries.huddleId, huddleId)) - .orderBy(asc(huddlePayoutEntries.sortOrder), asc(huddlePayoutEntries.createdAt)); + .from(payoutEntries) + .where(eq(payoutEntries.huddleId, huddleId)) + .orderBy(asc(payoutEntries.sortOrder), asc(payoutEntries.createdAt)); } /** @@ -56,11 +56,11 @@ export async function setPayouts( // Full replace: delete existing then insert new set. await db - .delete(huddlePayoutEntries) - .where(eq(huddlePayoutEntries.huddleId, huddleId)); + .delete(payoutEntries) + .where(eq(payoutEntries.huddleId, huddleId)); if (entries.length > 0) { - await db.insert(huddlePayoutEntries).values( + await db.insert(payoutEntries).values( entries.map((e, i) => ({ huddleId, label: e.label.trim(), diff --git a/server/src/services/pollService.ts b/server/src/services/pollService.ts index f75e288..ab8af28 100644 --- a/server/src/services/pollService.ts +++ b/server/src/services/pollService.ts @@ -10,9 +10,9 @@ import { and, asc, count, countDistinct, eq, inArray } from "drizzle-orm"; import { db } from "../db/client.js"; import { - huddlePolls, - huddlePollOptions, - huddlePollVotes, + polls, + pollOptions, + pollVotes, type HuddlePoll, } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner, hasApprovedClaim } from "./huddlesService.js"; @@ -66,19 +66,19 @@ export async function getPollWithResults( pollId: string, userId: string, ): Promise { - const [poll] = await db.select().from(huddlePolls).where(eq(huddlePolls.id, pollId)).limit(1); + const [poll] = await db.select().from(polls).where(eq(polls.id, pollId)).limit(1); if (!poll) return null; const optionRows = await db .select() - .from(huddlePollOptions) - .where(eq(huddlePollOptions.pollId, pollId)) - .orderBy(asc(huddlePollOptions.sortOrder)); + .from(pollOptions) + .where(eq(pollOptions.pollId, pollId)) + .orderBy(asc(pollOptions.sortOrder)); const myVoteRows = await db .select() - .from(huddlePollVotes) - .where(and(eq(huddlePollVotes.pollId, pollId), eq(huddlePollVotes.userId, userId))); + .from(pollVotes) + .where(and(eq(pollVotes.pollId, pollId), eq(pollVotes.userId, userId))); const myOptionIds = myVoteRows.map((v) => v.optionId); const hasVoted = myOptionIds.length > 0; @@ -93,16 +93,16 @@ export async function getPollWithResults( let totalVoters = 0; if (resultsVisible) { const countRows = await db - .select({ optionId: huddlePollVotes.optionId, n: count() }) - .from(huddlePollVotes) - .where(eq(huddlePollVotes.pollId, pollId)) - .groupBy(huddlePollVotes.optionId); + .select({ optionId: pollVotes.optionId, n: count() }) + .from(pollVotes) + .where(eq(pollVotes.pollId, pollId)) + .groupBy(pollVotes.optionId); voteCounts = new Map(countRows.map((r) => [r.optionId, Number(r.n)])); const [totalRow] = await db - .select({ n: countDistinct(huddlePollVotes.userId) }) - .from(huddlePollVotes) - .where(eq(huddlePollVotes.pollId, pollId)); + .select({ n: countDistinct(pollVotes.userId) }) + .from(pollVotes) + .where(eq(pollVotes.pollId, pollId)); totalVoters = Number(totalRow?.n ?? 0); } @@ -138,8 +138,8 @@ export async function getPollForTopic( ): Promise { const [poll] = await db .select() - .from(huddlePolls) - .where(and(eq(huddlePolls.huddleId, huddleId), eq(huddlePolls.topicId, topicId))) + .from(polls) + .where(and(eq(polls.huddleId, huddleId), eq(polls.topicId, topicId))) .limit(1); if (!poll) return null; return getPollWithResults(poll.id, userId); @@ -148,9 +148,9 @@ export async function getPollForTopic( /** Lightweight lookup for the poll ID attached to a topic, without loading results. */ export async function getPollIdForTopic(huddleId: string, topicId: string): Promise { const [poll] = await db - .select({ id: huddlePolls.id }) - .from(huddlePolls) - .where(and(eq(huddlePolls.huddleId, huddleId), eq(huddlePolls.topicId, topicId))) + .select({ id: polls.id }) + .from(polls) + .where(and(eq(polls.huddleId, huddleId), eq(polls.topicId, topicId))) .limit(1); return poll?.id ?? null; } @@ -162,8 +162,8 @@ export async function getDashboardPoll( ): Promise { const [poll] = await db .select() - .from(huddlePolls) - .where(and(eq(huddlePolls.huddleId, huddleId), eq(huddlePolls.isDashboardPoll, true))) + .from(polls) + .where(and(eq(polls.huddleId, huddleId), eq(polls.isDashboardPoll, true))) .limit(1); if (!poll) return null; return getPollWithResults(poll.id, userId); @@ -213,7 +213,7 @@ export async function createPoll(opts: { const { question, options, closesAt } = validateNewPoll(opts); const [pollRow] = await db - .insert(huddlePolls) + .insert(polls) .values({ huddleId: opts.huddleId, authorId: opts.authorId, @@ -229,7 +229,7 @@ export async function createPoll(opts: { if (!pollRow) fail(500, "Failed to create poll"); await db - .insert(huddlePollOptions) + .insert(pollOptions) .values(options.map((label, i) => ({ pollId: pollRow!.id, label, sortOrder: i }))); const result = await getPollWithResults(pollRow!.id, opts.authorId); @@ -248,9 +248,9 @@ export async function setDashboardPoll( fail(403, "Only a commissioner can set the dashboard poll"); await db - .update(huddlePolls) + .update(polls) .set({ isDashboardPoll: false }) - .where(and(eq(huddlePolls.huddleId, huddleId), eq(huddlePolls.isDashboardPoll, true))); + .where(and(eq(polls.huddleId, huddleId), eq(polls.isDashboardPoll, true))); return createPoll({ huddleId, @@ -272,8 +272,8 @@ export async function castVote(opts: { const [poll] = await db .select() - .from(huddlePolls) - .where(and(eq(huddlePolls.id, opts.pollId), eq(huddlePolls.huddleId, opts.huddleId))) + .from(polls) + .where(and(eq(polls.id, opts.pollId), eq(polls.huddleId, opts.huddleId))) .limit(1); if (!poll) return fail(404, "Poll not found"); if (poll.closesAt && poll.closesAt.getTime() <= Date.now()) @@ -286,25 +286,25 @@ export async function castVote(opts: { const validOptions = await db .select() - .from(huddlePollOptions) - .where(and(eq(huddlePollOptions.pollId, opts.pollId), inArray(huddlePollOptions.id, optionIds))); + .from(pollOptions) + .where(and(eq(pollOptions.pollId, opts.pollId), inArray(pollOptions.id, optionIds))); if (validOptions.length !== optionIds.length) fail(400, "One or more options are invalid"); const existingVotes = await db .select() - .from(huddlePollVotes) - .where(and(eq(huddlePollVotes.pollId, opts.pollId), eq(huddlePollVotes.userId, opts.userId))); + .from(pollVotes) + .where(and(eq(pollVotes.pollId, opts.pollId), eq(pollVotes.userId, opts.userId))); if (existingVotes.length > 0 && !poll.allowVoteChanges) fail(409, "You've already voted on this poll"); if (existingVotes.length > 0) { await db - .delete(huddlePollVotes) - .where(and(eq(huddlePollVotes.pollId, opts.pollId), eq(huddlePollVotes.userId, opts.userId))); + .delete(pollVotes) + .where(and(eq(pollVotes.pollId, opts.pollId), eq(pollVotes.userId, opts.userId))); } await db - .insert(huddlePollVotes) + .insert(pollVotes) .values(optionIds.map((optionId) => ({ pollId: opts.pollId, optionId, userId: opts.userId }))); const result = await getPollWithResults(opts.pollId, opts.userId); diff --git a/server/src/services/sideBetsService.ts b/server/src/services/sideBetsService.ts index 0b227ec..f79d6d2 100644 --- a/server/src/services/sideBetsService.ts +++ b/server/src/services/sideBetsService.ts @@ -7,7 +7,7 @@ */ import { and, eq, or } from "drizzle-orm"; import { db } from "../db/client.js"; -import { huddleSideBets, type SideBet } from "../db/schema.js"; +import { sideBets, type SideBet } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner } from "./huddlesService.js"; const fail = (status: number, message: string): never => { @@ -21,13 +21,13 @@ export async function listBets( huddleId: string, week?: number, ): Promise { - const conditions = [eq(huddleSideBets.huddleId, huddleId)]; - if (week !== undefined) conditions.push(eq(huddleSideBets.week, week)); + const conditions = [eq(sideBets.huddleId, huddleId)]; + if (week !== undefined) conditions.push(eq(sideBets.week, week)); return db .select() - .from(huddleSideBets) + .from(sideBets) .where(and(...conditions)) - .orderBy(huddleSideBets.createdAt); + .orderBy(sideBets.createdAt); } // ── Mutations ────────────────────────────────────────────────────────────────── @@ -54,7 +54,7 @@ export async function proposeBet(opts: { fail(400, "a bet cannot have both a cash amount and a prize description"); const [row] = await db - .insert(huddleSideBets) + .insert(sideBets) .values({ huddleId: opts.huddleId, proposerId: opts.proposerId, @@ -79,8 +79,8 @@ export async function respondToBet(opts: { }): Promise { const [bet] = await db .select() - .from(huddleSideBets) - .where(and(eq(huddleSideBets.id, opts.betId), eq(huddleSideBets.huddleId, opts.huddleId))) + .from(sideBets) + .where(and(eq(sideBets.id, opts.betId), eq(sideBets.huddleId, opts.huddleId))) .limit(1); if (!bet) fail(404, "Bet not found"); @@ -88,14 +88,14 @@ export async function respondToBet(opts: { if (bet.status !== "pending") fail(409, `Bet is already ${bet.status}`); if (opts.response === "rejected") { - await db.delete(huddleSideBets).where(eq(huddleSideBets.id, opts.betId)); + await db.delete(sideBets).where(eq(sideBets.id, opts.betId)); return { ...bet, status: "rejected" }; } const [updated] = await db - .update(huddleSideBets) + .update(sideBets) .set({ status: "accepted", updatedAt: new Date() }) - .where(eq(huddleSideBets.id, opts.betId)) + .where(eq(sideBets.id, opts.betId)) .returning(); return updated!; } @@ -107,8 +107,8 @@ export async function cancelBet(opts: { }): Promise { const [bet] = await db .select() - .from(huddleSideBets) - .where(and(eq(huddleSideBets.id, opts.betId), eq(huddleSideBets.huddleId, opts.huddleId))) + .from(sideBets) + .where(and(eq(sideBets.id, opts.betId), eq(sideBets.huddleId, opts.huddleId))) .limit(1); if (!bet) fail(404, "Bet not found"); @@ -122,7 +122,7 @@ export async function cancelBet(opts: { if (bet.status === "pending" && bet.proposerId !== opts.userId && !commish) fail(403, "Only the proposer (or a commissioner) can cancel a pending bet"); - await db.delete(huddleSideBets).where(eq(huddleSideBets.id, opts.betId)); + await db.delete(sideBets).where(eq(sideBets.id, opts.betId)); return { ...bet, status: "cancelled" }; } @@ -135,8 +135,8 @@ export async function settleBet(opts: { }): Promise { const [bet] = await db .select() - .from(huddleSideBets) - .where(and(eq(huddleSideBets.id, opts.betId), eq(huddleSideBets.huddleId, opts.huddleId))) + .from(sideBets) + .where(and(eq(sideBets.id, opts.betId), eq(sideBets.huddleId, opts.huddleId))) .limit(1); if (!bet) fail(404, "Bet not found"); @@ -150,14 +150,14 @@ export async function settleBet(opts: { fail(400, "winnerId must be one of the two parties"); const [updated] = await db - .update(huddleSideBets) + .update(sideBets) .set({ status: "settled", winnerId: opts.winnerId, settlementNote: opts.settlementNote ?? null, updatedAt: new Date(), }) - .where(eq(huddleSideBets.id, opts.betId)) + .where(eq(sideBets.id, opts.betId)) .returning(); return updated!; } diff --git a/server/src/services/surveyService.ts b/server/src/services/surveyService.ts index 1aa773b..7c94ba1 100644 --- a/server/src/services/surveyService.ts +++ b/server/src/services/surveyService.ts @@ -1,7 +1,7 @@ /** * Survey service — commissioner-authored, multi-question forms scoped to a * huddle. Modeled as "a poll with several questions": see the schema comment - * on `huddleSurveys` in `db/schema.ts` for how the tables map onto the poll + * on `surveys` in `db/schema.ts` for how the tables map onto the poll * tables this mirrors. * * Only commissioners can create/publish-results/delete a survey. Any approved @@ -12,12 +12,12 @@ import { and, asc, desc, eq, inArray, count } from "drizzle-orm"; import { db } from "../db/client.js"; import { - huddleSurveys, - huddleSurveyQuestions, - huddleSurveyOptions, - huddleSurveyResponses, - huddleSurveyAnswers, - huddleSurveyAnswerOptions, + surveys, + surveyQuestions, + surveyOptions, + surveyResponses, + surveyAnswers, + surveyAnswerOptions, type HuddleSurvey, } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner, hasApprovedClaim } from "./huddlesService.js"; @@ -153,18 +153,18 @@ function isClosed(survey: Pick): boolean { * background job in this app (serverless, no cron) — instead every read * path self-heals the flag the first time it notices the survey has closed. */ -async function applyAutoPublish(surveys: HuddleSurvey[]): Promise { +async function applyAutoPublish(rows: HuddleSurvey[]): Promise { const now = Date.now(); - const toPublish = surveys.filter( + const toPublish = rows.filter( (s) => !s.resultsPublished && s.autoPublishOnClose && s.closesAt.getTime() <= now, ); - if (toPublish.length === 0) return surveys; + if (toPublish.length === 0) return rows; const ids = toPublish.map((s) => s.id); - await db.update(huddleSurveys).set({ resultsPublished: true }).where(inArray(huddleSurveys.id, ids)); + await db.update(surveys).set({ resultsPublished: true }).where(inArray(surveys.id, ids)); const publishedIds = new Set(ids); - return surveys.map((s) => (publishedIds.has(s.id) ? { ...s, resultsPublished: true } : s)); + return rows.map((s) => (publishedIds.has(s.id) ? { ...s, resultsPublished: true } : s)); } // ── Queries ──────────────────────────────────────────────────────────────────── @@ -173,28 +173,28 @@ async function applyAutoPublish(surveys: HuddleSurvey[]): Promise { const rawSurveys = await db .select() - .from(huddleSurveys) - .where(eq(huddleSurveys.huddleId, huddleId)) - .orderBy(desc(huddleSurveys.createdAt)); + .from(surveys) + .where(eq(surveys.huddleId, huddleId)) + .orderBy(desc(surveys.createdAt)); if (rawSurveys.length === 0) return []; - const surveys = await applyAutoPublish(rawSurveys); - const ids = surveys.map((s) => s.id); + const published = await applyAutoPublish(rawSurveys); + const ids = published.map((s) => s.id); const countRows = await db - .select({ surveyId: huddleSurveyResponses.surveyId, n: count() }) - .from(huddleSurveyResponses) - .where(inArray(huddleSurveyResponses.surveyId, ids)) - .groupBy(huddleSurveyResponses.surveyId); + .select({ surveyId: surveyResponses.surveyId, n: count() }) + .from(surveyResponses) + .where(inArray(surveyResponses.surveyId, ids)) + .groupBy(surveyResponses.surveyId); const countBySurvey = new Map(countRows.map((r) => [r.surveyId, Number(r.n)])); const myResponseRows = await db - .select({ surveyId: huddleSurveyResponses.surveyId }) - .from(huddleSurveyResponses) - .where(and(inArray(huddleSurveyResponses.surveyId, ids), eq(huddleSurveyResponses.userId, userId))); + .select({ surveyId: surveyResponses.surveyId }) + .from(surveyResponses) + .where(and(inArray(surveyResponses.surveyId, ids), eq(surveyResponses.userId, userId))); const respondedSurveyIds = new Set(myResponseRows.map((r) => r.surveyId)); - return surveys.map((s) => ({ + return published.map((s) => ({ id: s.id, huddleId: s.huddleId, authorId: s.authorId, @@ -214,8 +214,8 @@ export async function listSurveys(huddleId: string, userId: string): Promise { const rows = await db .select() - .from(huddleSurveys) - .where(and(eq(huddleSurveys.id, surveyId), eq(huddleSurveys.huddleId, huddleId))) + .from(surveys) + .where(and(eq(surveys.id, surveyId), eq(surveys.huddleId, huddleId))) .limit(1); const survey = rows[0] ?? null; if (!survey) return null; @@ -226,17 +226,17 @@ async function getSurveyRow(huddleId: string, surveyId: string): Promise { const questionRows = await db .select() - .from(huddleSurveyQuestions) - .where(eq(huddleSurveyQuestions.surveyId, surveyId)) - .orderBy(asc(huddleSurveyQuestions.sortOrder)); + .from(surveyQuestions) + .where(eq(surveyQuestions.surveyId, surveyId)) + .orderBy(asc(surveyQuestions.sortOrder)); if (questionRows.length === 0) return []; const questionIds = questionRows.map((q) => q.id); const optionRows = await db .select() - .from(huddleSurveyOptions) - .where(inArray(huddleSurveyOptions.questionId, questionIds)) - .orderBy(asc(huddleSurveyOptions.sortOrder)); + .from(surveyOptions) + .where(inArray(surveyOptions.questionId, questionIds)) + .orderBy(asc(surveyOptions.sortOrder)); const optionsByQuestion = new Map(); for (const o of optionRows) { @@ -257,19 +257,19 @@ async function getQuestionsWithOptions(surveyId: string): Promise { const [response] = await db .select() - .from(huddleSurveyResponses) - .where(and(eq(huddleSurveyResponses.surveyId, surveyId), eq(huddleSurveyResponses.userId, userId))) + .from(surveyResponses) + .where(and(eq(surveyResponses.surveyId, surveyId), eq(surveyResponses.userId, userId))) .limit(1); if (!response) return null; const textRows = await db .select() - .from(huddleSurveyAnswers) - .where(eq(huddleSurveyAnswers.responseId, response.id)); + .from(surveyAnswers) + .where(eq(surveyAnswers.responseId, response.id)); const optionRows = await db .select() - .from(huddleSurveyAnswerOptions) - .where(eq(huddleSurveyAnswerOptions.responseId, response.id)); + .from(surveyAnswerOptions) + .where(eq(surveyAnswerOptions.responseId, response.id)); const optionIdsByQuestion = new Map(); for (const o of optionRows) { @@ -342,18 +342,18 @@ export async function getResults( const [totalRow] = await db .select({ n: count() }) - .from(huddleSurveyResponses) - .where(eq(huddleSurveyResponses.surveyId, survey.id)); + .from(surveyResponses) + .where(eq(surveyResponses.surveyId, survey.id)); const totalResponses = Number(totalRow?.n ?? 0); const results: SurveyResultsQuestion[] = []; for (const q of questions) { if (CHOICE_TYPES.has(q.type)) { const countRows = await db - .select({ optionId: huddleSurveyAnswerOptions.optionId, n: count() }) - .from(huddleSurveyAnswerOptions) - .where(eq(huddleSurveyAnswerOptions.questionId, q.id)) - .groupBy(huddleSurveyAnswerOptions.optionId); + .select({ optionId: surveyAnswerOptions.optionId, n: count() }) + .from(surveyAnswerOptions) + .where(eq(surveyAnswerOptions.questionId, q.id)) + .groupBy(surveyAnswerOptions.optionId); const countByOption = new Map(countRows.map((r) => [r.optionId, Number(r.n)])); results.push({ questionId: q.id, @@ -368,10 +368,10 @@ export async function getResults( }); } else { const rows = await db - .select({ userId: huddleSurveyResponses.userId, textValue: huddleSurveyAnswers.textValue }) - .from(huddleSurveyAnswers) - .innerJoin(huddleSurveyResponses, eq(huddleSurveyAnswers.responseId, huddleSurveyResponses.id)) - .where(eq(huddleSurveyAnswers.questionId, q.id)); + .select({ userId: surveyResponses.userId, textValue: surveyAnswers.textValue }) + .from(surveyAnswers) + .innerJoin(surveyResponses, eq(surveyAnswers.responseId, surveyResponses.id)) + .where(eq(surveyAnswers.questionId, q.id)); results.push({ questionId: q.id, prompt: q.prompt, @@ -463,14 +463,14 @@ async function insertQuestions(surveyId: string, questions: ValidatedQuestion[]) for (let i = 0; i < questions.length; i++) { const q = questions[i]!; const [questionRow] = await db - .insert(huddleSurveyQuestions) + .insert(surveyQuestions) .values({ surveyId, type: q.type, prompt: q.prompt, required: q.required, sortOrder: i }) .returning(); if (!questionRow) fail(500, "Failed to save question"); if (q.options.length > 0) { await db - .insert(huddleSurveyOptions) + .insert(surveyOptions) .values(q.options.map((label, j) => ({ questionId: questionRow!.id, label, sortOrder: j }))); } } @@ -480,7 +480,7 @@ async function insertQuestions(surveyId: string, questions: ValidatedQuestion[]) * Reconciles a choice question's options by label: a label that already * exists keeps its row (and thus its collected votes), a new label gets a * fresh row, and an existing label no longer present is deleted (dropping - * only the votes for that specific option, via FK cascade on huddleSurveyAnswerOptions). + * only the votes for that specific option, via FK cascade on surveyAnswerOptions). */ async function syncQuestionOptions( questionId: string, @@ -492,16 +492,16 @@ async function syncQuestionOptions( const removeIds = existingOptions.filter((o) => !keepLabels.has(o.label)).map((o) => o.id); if (removeIds.length > 0) { - await db.delete(huddleSurveyOptions).where(inArray(huddleSurveyOptions.id, removeIds)); + await db.delete(surveyOptions).where(inArray(surveyOptions.id, removeIds)); } for (let j = 0; j < incomingLabels.length; j++) { const label = incomingLabels[j]!; const existingId = existingByLabel.get(label); if (existingId) { - await db.update(huddleSurveyOptions).set({ sortOrder: j }).where(eq(huddleSurveyOptions.id, existingId)); + await db.update(surveyOptions).set({ sortOrder: j }).where(eq(surveyOptions.id, existingId)); } else { - await db.insert(huddleSurveyOptions).values({ questionId, label, sortOrder: j }); + await db.insert(surveyOptions).values({ questionId, label, sortOrder: j }); } } } @@ -520,7 +520,7 @@ export async function createSurvey(opts: { ); const [surveyRow] = await db - .insert(huddleSurveys) + .insert(surveys) .values({ huddleId: opts.huddleId, authorId: opts.userId, @@ -568,9 +568,9 @@ export async function updateSurvey(opts: { ); await db - .update(huddleSurveys) + .update(surveys) .set({ title, description, closesAt, anonymity, autoPublishOnClose }) - .where(eq(huddleSurveys.id, opts.surveyId)); + .where(eq(surveys.id, opts.surveyId)); const existingQuestions = await getQuestionsWithOptions(opts.surveyId); const existingById = new Map(existingQuestions.map((q) => [q.id, q])); @@ -578,7 +578,7 @@ export async function updateSurvey(opts: { const removedIds = existingQuestions.map((q) => q.id).filter((id) => !incomingIds.has(id)); if (removedIds.length > 0) { - await db.delete(huddleSurveyQuestions).where(inArray(huddleSurveyQuestions.id, removedIds)); + await db.delete(surveyQuestions).where(inArray(surveyQuestions.id, removedIds)); } for (let i = 0; i < questions.length; i++) { @@ -587,31 +587,31 @@ export async function updateSurvey(opts: { if (!match) { const [questionRow] = await db - .insert(huddleSurveyQuestions) + .insert(surveyQuestions) .values({ surveyId: opts.surveyId, type: q.type, prompt: q.prompt, required: q.required, sortOrder: i }) .returning(); if (!questionRow) fail(500, "Failed to save question"); if (q.options.length > 0) { await db - .insert(huddleSurveyOptions) + .insert(surveyOptions) .values(q.options.map((label, j) => ({ questionId: questionRow!.id, label, sortOrder: j }))); } continue; } await db - .update(huddleSurveyQuestions) + .update(surveyQuestions) .set({ type: q.type, prompt: q.prompt, required: q.required, sortOrder: i }) - .where(eq(huddleSurveyQuestions.id, match.id)); + .where(eq(surveyQuestions.id, match.id)); if (match.type !== q.type) { // Shape changed — the old answers/options no longer apply to this question. - await db.delete(huddleSurveyAnswers).where(eq(huddleSurveyAnswers.questionId, match.id)); - await db.delete(huddleSurveyAnswerOptions).where(eq(huddleSurveyAnswerOptions.questionId, match.id)); - await db.delete(huddleSurveyOptions).where(eq(huddleSurveyOptions.questionId, match.id)); + await db.delete(surveyAnswers).where(eq(surveyAnswers.questionId, match.id)); + await db.delete(surveyAnswerOptions).where(eq(surveyAnswerOptions.questionId, match.id)); + await db.delete(surveyOptions).where(eq(surveyOptions.questionId, match.id)); if (q.options.length > 0) { await db - .insert(huddleSurveyOptions) + .insert(surveyOptions) .values(q.options.map((label, j) => ({ questionId: match.id, label, sortOrder: j }))); } } else if (CHOICE_TYPES.has(q.type)) { @@ -664,22 +664,22 @@ export async function submitResponse(opts: { const [existing] = await db .select() - .from(huddleSurveyResponses) - .where(and(eq(huddleSurveyResponses.surveyId, survey.id), eq(huddleSurveyResponses.userId, opts.userId))) + .from(surveyResponses) + .where(and(eq(surveyResponses.surveyId, survey.id), eq(surveyResponses.userId, opts.userId))) .limit(1); let responseId: string; if (existing) { responseId = existing.id; - await db.delete(huddleSurveyAnswers).where(eq(huddleSurveyAnswers.responseId, responseId)); - await db.delete(huddleSurveyAnswerOptions).where(eq(huddleSurveyAnswerOptions.responseId, responseId)); + await db.delete(surveyAnswers).where(eq(surveyAnswers.responseId, responseId)); + await db.delete(surveyAnswerOptions).where(eq(surveyAnswerOptions.responseId, responseId)); await db - .update(huddleSurveyResponses) + .update(surveyResponses) .set({ updatedAt: new Date() }) - .where(eq(huddleSurveyResponses.id, responseId)); + .where(eq(surveyResponses.id, responseId)); } else { const [responseRow] = await db - .insert(huddleSurveyResponses) + .insert(surveyResponses) .values({ surveyId: survey.id, userId: opts.userId }) .returning(); if (!responseRow) return fail(500, "Failed to record response"); @@ -694,13 +694,13 @@ export async function submitResponse(opts: { const optionIds = [...new Set(answer.optionIds ?? [])]; if (optionIds.length > 0) { await db - .insert(huddleSurveyAnswerOptions) + .insert(surveyAnswerOptions) .values(optionIds.map((optionId) => ({ responseId, questionId: q.id, optionId }))); } } else { const textValue = answer.textValue?.trim() ?? ""; if (textValue) { - await db.insert(huddleSurveyAnswers).values({ responseId, questionId: q.id, textValue }); + await db.insert(surveyAnswers).values({ responseId, questionId: q.id, textValue }); } } } @@ -723,9 +723,9 @@ export async function setResultsPublished( if (!survey) fail(404, "Survey not found"); await db - .update(huddleSurveys) + .update(surveys) .set({ resultsPublished: published }) - .where(eq(huddleSurveys.id, surveyId)); + .where(eq(surveys.id, surveyId)); } export async function deleteSurvey(huddleId: string, surveyId: string, userId: string): Promise { @@ -735,5 +735,5 @@ export async function deleteSurvey(huddleId: string, surveyId: string, userId: s const survey = await getSurveyRow(huddleId, surveyId); if (!survey) fail(404, "Survey not found"); - await db.delete(huddleSurveys).where(eq(huddleSurveys.id, surveyId)); + await db.delete(surveys).where(eq(surveys.id, surveyId)); } diff --git a/server/src/services/trophyControlService.ts b/server/src/services/trophyControlService.ts index a26981f..f60762b 100644 --- a/server/src/services/trophyControlService.ts +++ b/server/src/services/trophyControlService.ts @@ -7,7 +7,7 @@ */ import { eq, and } from "drizzle-orm"; import { db } from "../db/client.js"; -import { huddleActiveTrophies } from "../db/schema.js"; +import { activeTrophies } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner } from "./huddlesService.js"; const fail = (status: number, message: string): never => { @@ -34,8 +34,8 @@ export async function getActiveTrophies( ): Promise> { const rows = await db .select() - .from(huddleActiveTrophies) - .where(eq(huddleActiveTrophies.huddleId, huddleId)); + .from(activeTrophies) + .where(eq(activeTrophies.huddleId, huddleId)); const result: Record = {}; // Default all types to enabled @@ -65,7 +65,7 @@ export async function setTrophyEnabled( fail(400, `Unknown trophy type: ${trophyType}`); await db - .insert(huddleActiveTrophies) + .insert(activeTrophies) .values({ huddleId, trophyType, @@ -73,7 +73,7 @@ export async function setTrophyEnabled( updatedAt: new Date(), }) .onConflictDoUpdate({ - target: [huddleActiveTrophies.huddleId, huddleActiveTrophies.trophyType], + target: [activeTrophies.huddleId, activeTrophies.trophyType], set: { enabled, updatedAt: new Date() }, }); } From da5e09acb4e49c530aafea7f663a7c867f8e12f4 Mon Sep 17 00:00:00 2001 From: Brandon Pham Date: Sun, 9 Aug 2026 18:34:31 -0500 Subject: [PATCH 4/4] refactor: drop the Huddle prefix from domain type names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follows 6120cd0 through to the TypeScript layer. `HuddleAward` -> `Award`, `HuddlePoll` -> `Poll`, `HuddleSurveyResponse` -> `SurveyResponse`, and so on for 21 types across schema.ts, the services and the client. This mostly brings the server in line with the client rather than the reverse: client/src/types/huddle.ts had already settled on DuesConfig, ForumTopic, Poll, PollOption, SideBet and PayoutEntry, with only HuddleAward and HuddleAnnouncement still carrying the prefix. Both are now unprefixed too. Kept where `Huddle` is load-bearing rather than decorative: Huddle, HuddleClaim, HuddleClaimSummary, HuddleDetail, HuddleDetailResponse, HuddleMemberStatus. One collision: lucide-react exports an `Award` icon that CommissionerPage passes to StubSection, and `AwardIcon` was already taken by the award-glyph asset type from useHuddles — so the icon is imported as `LucideAward`. Server and client both typecheck; services reading every renamed table still return the same rows (1 claim, 1 commissioner, 2 announcements, 1 award, 5 payouts, 5 trophies) and the API responds as expected. Co-Authored-By: Claude Opus 5 --- PLAYBOOK.md | 2 + client/src/hooks/useHuddles.ts | 14 +++--- client/src/pages/CommissionerPage.tsx | 12 ++--- client/src/pages/LeagueSettingsPage.tsx | 4 +- client/src/pages/TeamPage.tsx | 4 +- client/src/types/huddle.ts | 4 +- .../src/widgets/dashboard/Announcements.tsx | 4 +- server/src/db/schema.ts | 44 +++++++++---------- server/src/services/awardsService.ts | 10 ++--- server/src/services/countdownService.ts | 6 +-- server/src/services/duesService.ts | 12 ++--- server/src/services/forumService.ts | 14 +++--- server/src/services/huddlesService.ts | 12 ++--- server/src/services/pollService.ts | 4 +- server/src/services/surveyService.ts | 8 ++-- 15 files changed, 79 insertions(+), 75 deletions(-) diff --git a/PLAYBOOK.md b/PLAYBOOK.md index a059610..c1cda8b 100644 --- a/PLAYBOOK.md +++ b/PLAYBOOK.md @@ -532,6 +532,8 @@ This replaces the old `psql -f ...` workflow — no `psql` install required, and The one structural distinction worth knowing isn't in the names: **`users` is global; everything else is reachable from a huddle**, directly via `huddle_id` or transitively through a parent (`poll_options` → `polls` → huddle). If you add auth tables later (`sessions`, `accounts`), they join `users` on the global side. +The same rule applies to TypeScript types (`Award`, `Poll`, `SurveyResponse`, not `HuddleAward`), on both sides of the wire — the client had already settled on the unprefixed names, so `0015`'s follow-up brought the server in line rather than the other way round. Keep the `Huddle` prefix only where it's doing real work: `Huddle` itself, `HuddleClaim`, `HuddleDetailResponse`, `HuddleMemberStatus`. Watch for collisions with lucide-react icons when you strip a prefix — `CommissionerPage` imports `Award as LucideAward` for exactly this reason. + **Renaming a table is a three-part job, and Postgres only does the first part.** `ALTER TABLE ... RENAME TO` leaves every constraint and index still carrying the old name, and Drizzle doesn't track those names, so they will never show up in a generated diff — they just rot until something collides. `0015` renamed 115 objects for 21 tables. Generate the statements from the live schema (`pg_constraint` + `pg_index`) rather than by hand. Two traps that cost a follow-up migration (`0016`) the first time: diff --git a/client/src/hooks/useHuddles.ts b/client/src/hooks/useHuddles.ts index 96d4178..384df37 100644 --- a/client/src/hooks/useHuddles.ts +++ b/client/src/hooks/useHuddles.ts @@ -546,7 +546,7 @@ export function useRemoveClaim() { // ── Announcements ───────────────────────────────────────────────────────────── -import type { HuddleAnnouncement } from "../types/huddle"; +import type { Announcement } from "../types/huddle"; /** Fetches announcements for a huddle, newest first. */ export function useAnnouncements(huddleId: string | null) { @@ -555,7 +555,7 @@ export function useAnnouncements(huddleId: string | null) { queryKey: ["huddle-announcements", huddleId], queryFn: async () => { const token = await getToken(); - const res = await axios.get<{ announcements: HuddleAnnouncement[] }>( + const res = await axios.get<{ announcements: Announcement[] }>( `/api/huddles/${huddleId}/announcements`, { headers: authHeader(token) }, ); @@ -572,7 +572,7 @@ export function useCreateAnnouncement() { return useMutation({ mutationFn: async (input: { huddleId: string; title: string; body: string }) => { const token = await getToken(); - const res = await axios.post<{ announcement: HuddleAnnouncement }>( + const res = await axios.post<{ announcement: Announcement }>( `/api/huddles/${input.huddleId}/announcements`, { title: input.title, body: input.body }, { headers: authHeader(token) }, @@ -603,7 +603,7 @@ export function useDeleteAnnouncement() { // ── Awards ──────────────────────────────────────────────────────────────────── -import type { HuddleAward } from "../types/huddle"; +import type { Award } from "../types/huddle"; export function useAwards(huddleId: string | null, rosterId?: number) { const { getToken } = useAuth(); @@ -612,7 +612,7 @@ export function useAwards(huddleId: string | null, rosterId?: number) { queryFn: async () => { const token = await getToken(); const params = rosterId !== undefined ? { rosterId } : {}; - const res = await axios.get<{ awards: HuddleAward[] }>( + const res = await axios.get<{ awards: Award[] }>( `/api/huddles/${huddleId}/awards`, { params, headers: authHeader(token) }, ); @@ -637,7 +637,7 @@ export function useCreateAward() { season?: string; }) => { const token = await getToken(); - const res = await axios.post<{ award: HuddleAward }>( + const res = await axios.post<{ award: Award }>( `/api/huddles/${input.huddleId}/awards`, { rosterId: input.rosterId, glyph: input.glyph, color: input.color, title: input.title, description: input.description, season: input.season }, @@ -729,7 +729,7 @@ export function useUpdateAward() { season?: string; }) => { const token = await getToken(); - const res = await axios.patch<{ award: HuddleAward }>( + const res = await axios.patch<{ award: Award }>( `/api/huddles/${input.huddleId}/awards/${input.awardId}`, { rosterId: input.rosterId, glyph: input.glyph, color: input.color, title: input.title, description: input.description, season: input.season }, diff --git a/client/src/pages/CommissionerPage.tsx b/client/src/pages/CommissionerPage.tsx index 846ef43..1b884e3 100644 --- a/client/src/pages/CommissionerPage.tsx +++ b/client/src/pages/CommissionerPage.tsx @@ -19,7 +19,9 @@ */ import { useState, useMemo } from "react"; import { Navigate, useNavigate } from "react-router-dom"; -import { Megaphone, DollarSign, Trophy, Award, Plus, Trash2, Timer, BarChart3 } from "lucide-react"; +// lucide's `Award` is aliased: `Award` is the domain type imported below, and +// `AwardIcon` is already taken by the award-glyph asset type from useHuddles. +import { Megaphone, DollarSign, Trophy, Award as LucideAward, Plus, Trash2, Timer, BarChart3 } from "lucide-react"; import { useAppSelector } from "../store/hooks"; import { useLeagueUsers, useLeagueRosters } from "../hooks/useSleeper"; import { @@ -63,7 +65,7 @@ import type { Roster, TeamUser } from "../types/fantasy"; import type { CommissionerSummary, HuddleClaimSummary, - HuddleAward, + Award, UserSummary, ActiveTrophies, } from "../types/huddle"; @@ -1295,7 +1297,7 @@ function AwardBadge({ deleting, iconSvg, }: { - award: HuddleAward; + award: Award; teamName: string; onEdit: () => void; onDelete: () => void; @@ -1493,7 +1495,7 @@ function TrophyRoomPanel({ const [rosterId, setRosterId] = useState(""); const [season, setSeason] = useState(""); - function startEdit(a: HuddleAward) { + function startEdit(a: Award) { setEditingId(a.id); setGlyph(a.glyph); setColor(a.color); @@ -1995,7 +1997,7 @@ export function CommissionerPage() { /> ) : (
- {awards.map((a: HuddleAward) => ( + {awards.map((a: Award) => (
}) { +function HuddleAwardsStrip({ awards, iconMap }: { awards: Award[]; iconMap: Map }) { if (awards.length === 0) return null; return (
diff --git a/client/src/types/huddle.ts b/client/src/types/huddle.ts index 6fe139f..4dc09a1 100644 --- a/client/src/types/huddle.ts +++ b/client/src/types/huddle.ts @@ -67,7 +67,7 @@ export interface HuddleDetailResponse { myClaim: { id: string; rosterId: number; status: ClaimStatus } | null; } -export interface HuddleAnnouncement { +export interface Announcement { id: string; huddleId: string; authorId: string; @@ -289,7 +289,7 @@ export interface SurveyAnswerInput { optionIds?: string[]; } -export interface HuddleAward { +export interface Award { id: string; huddleId: string; rosterId: number; diff --git a/client/src/widgets/dashboard/Announcements.tsx b/client/src/widgets/dashboard/Announcements.tsx index 05b540d..ecafd2b 100644 --- a/client/src/widgets/dashboard/Announcements.tsx +++ b/client/src/widgets/dashboard/Announcements.tsx @@ -8,11 +8,11 @@ */ import { useSelectedLeagueHuddle, useAnnouncements } from "../../hooks/useHuddles"; import { SectionHead } from "./_shared"; -import type { HuddleAnnouncement } from "../../types/huddle"; +import type { Announcement } from "../../types/huddle"; // ---- Single announcement card ---- -function AnnouncementCard({ item }: { item: HuddleAnnouncement }) { +function AnnouncementCard({ item }: { item: Announcement }) { const date = new Date(item.createdAt).toLocaleDateString("en-US", { month: "short", day: "numeric", diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index 6266371..5fe1e35 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -232,13 +232,13 @@ export const duesPayments = pgTable( export type Huddle = typeof huddles.$inferSelect; export type NewHuddle = typeof huddles.$inferInsert; -export type HuddleCommissioner = typeof commissioners.$inferSelect; +export type Commissioner = typeof commissioners.$inferSelect; export type TeamClaim = typeof teamClaims.$inferSelect; export type NewTeamClaim = typeof teamClaims.$inferInsert; -export type HuddleAnnouncement = typeof announcements.$inferSelect; -export type NewHuddleAnnouncement = typeof announcements.$inferInsert; -export type HuddleDuesConfig = typeof duesConfig.$inferSelect; -export type HuddleDuesPayment = typeof duesPayments.$inferSelect; +export type Announcement = typeof announcements.$inferSelect; +export type NewAnnouncement = typeof announcements.$inferInsert; +export type DuesConfig = typeof duesConfig.$inferSelect; +export type DuesPayment = typeof duesPayments.$inferSelect; // ── Custom awards ───────────────────────────────────────────────────────────── @@ -271,8 +271,8 @@ export const awards = pgTable( }), ); -export type HuddleAward = typeof awards.$inferSelect; -export type NewHuddleAward = typeof awards.$inferInsert; +export type Award = typeof awards.$inferSelect; +export type NewAward = typeof awards.$inferInsert; // ── Payout structure ────────────────────────────────────────────────────────── @@ -298,8 +298,8 @@ export const payoutEntries = pgTable( }), ); -export type HuddlePayoutEntry = typeof payoutEntries.$inferSelect; -export type NewHuddlePayoutEntry = typeof payoutEntries.$inferInsert; +export type PayoutEntry = typeof payoutEntries.$inferSelect; +export type NewPayoutEntry = typeof payoutEntries.$inferInsert; // ── Active trophy control ───────────────────────────────────────────────────── // Commissioners choose which built-in auto-trophies are shown for their league. @@ -324,7 +324,7 @@ export const activeTrophies = pgTable( }), ); -export type HuddleActiveTrophy = typeof activeTrophies.$inferSelect; +export type ActiveTrophy = typeof activeTrophies.$inferSelect; // ── Side bets ────────────────────────────────────────────────────────────────── // League members propose bets against each other, tied to a specific week. @@ -399,7 +399,7 @@ export const countdownConfig = pgTable("countdown_config", { .notNull(), }); -export type HuddleCountdownConfig = typeof countdownConfig.$inferSelect; +export type CountdownConfig = typeof countdownConfig.$inferSelect; // ── League forum ────────────────────────────────────────────────────────────── // Message-board style discussion, scoped to a huddle (persists across seasons). @@ -469,8 +469,8 @@ export const forumReplies = pgTable( }), ); -export type HuddleForumTopic = typeof forumTopics.$inferSelect; -export type HuddleForumReply = typeof forumReplies.$inferSelect; +export type ForumTopic = typeof forumTopics.$inferSelect; +export type ForumReply = typeof forumReplies.$inferSelect; // ── Polls ───────────────────────────────────────────────────────────────────── // Two contexts share the same tables: a poll attached to a forum topic @@ -555,9 +555,9 @@ export const pollVotes = pgTable( }), ); -export type HuddlePoll = typeof polls.$inferSelect; -export type HuddlePollOption = typeof pollOptions.$inferSelect; -export type HuddlePollVote = typeof pollVotes.$inferSelect; +export type Poll = typeof polls.$inferSelect; +export type PollOption = typeof pollOptions.$inferSelect; +export type PollVote = typeof pollVotes.$inferSelect; // ── Surveys ─────────────────────────────────────────────────────────────────── // A commissioner-authored, multi-question form (short/long text or @@ -721,9 +721,9 @@ export const surveyAnswerOptions = pgTable( }), ); -export type HuddleSurvey = typeof surveys.$inferSelect; -export type HuddleSurveyQuestion = typeof surveyQuestions.$inferSelect; -export type HuddleSurveyOption = typeof surveyOptions.$inferSelect; -export type HuddleSurveyResponse = typeof surveyResponses.$inferSelect; -export type HuddleSurveyAnswer = typeof surveyAnswers.$inferSelect; -export type HuddleSurveyAnswerOption = typeof surveyAnswerOptions.$inferSelect; +export type Survey = typeof surveys.$inferSelect; +export type SurveyQuestion = typeof surveyQuestions.$inferSelect; +export type SurveyOption = typeof surveyOptions.$inferSelect; +export type SurveyResponse = typeof surveyResponses.$inferSelect; +export type SurveyAnswer = typeof surveyAnswers.$inferSelect; +export type SurveyAnswerOption = typeof surveyAnswerOptions.$inferSelect; diff --git a/server/src/services/awardsService.ts b/server/src/services/awardsService.ts index 5bd1983..68aba00 100644 --- a/server/src/services/awardsService.ts +++ b/server/src/services/awardsService.ts @@ -6,7 +6,7 @@ */ import { and, desc, eq } from "drizzle-orm"; import { db } from "../db/client.js"; -import { awards, type HuddleAward } from "../db/schema.js"; +import { awards, type Award } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner } from "./huddlesService.js"; const fail = (status: number, message: string): never => { @@ -95,7 +95,7 @@ function validateAwardInput(input: { /** * All awards for a huddle, newest first. */ -export async function listAwards(huddleId: string): Promise { +export async function listAwards(huddleId: string): Promise { return db .select() .from(awards) @@ -109,7 +109,7 @@ export async function listAwards(huddleId: string): Promise { export async function listAwardsForRoster( huddleId: string, rosterId: number, -): Promise { +): Promise { return db .select() .from(awards) @@ -138,7 +138,7 @@ export async function createAward( description?: unknown; season?: unknown; }, -): Promise { +): Promise { if (!(await isCommissioner(huddleId, userId))) fail(403, "Only a commissioner can grant awards"); @@ -172,7 +172,7 @@ export async function updateAward( description?: unknown; season?: unknown; }, -): Promise { +): Promise { if (!(await isCommissioner(huddleId, userId))) fail(403, "Only a commissioner can update awards"); diff --git a/server/src/services/countdownService.ts b/server/src/services/countdownService.ts index c41badd..9255f98 100644 --- a/server/src/services/countdownService.ts +++ b/server/src/services/countdownService.ts @@ -8,7 +8,7 @@ import { eq } from "drizzle-orm"; import { db } from "../db/client.js"; import { countdownConfig, - type HuddleCountdownConfig, + type CountdownConfig, } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner } from "./huddlesService.js"; @@ -24,7 +24,7 @@ const MAX_SUBTITLE_LEN = 120; /** Returns the current countdown config for a huddle, or null if none is set. */ export async function getCountdownConfig( huddleId: string, -): Promise { +): Promise { const rows = await db .select() .from(countdownConfig) @@ -48,7 +48,7 @@ export async function setCountdownConfig( targetAt: string; enabled: boolean; }, -): Promise { +): Promise { if (!(await isCommissioner(huddleId, userId))) fail(403, "Only a commissioner can set the countdown"); diff --git a/server/src/services/duesService.ts b/server/src/services/duesService.ts index 5f755a5..9d2c7a8 100644 --- a/server/src/services/duesService.ts +++ b/server/src/services/duesService.ts @@ -11,8 +11,8 @@ import { db } from "../db/client.js"; import { duesConfig, duesPayments, - type HuddleDuesConfig, - type HuddleDuesPayment, + type DuesConfig, + type DuesPayment, } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner } from "./huddlesService.js"; @@ -25,7 +25,7 @@ const fail = (status: number, message: string): never => { /** Returns the current dues config for a huddle, or null if none is set. */ export async function getDuesConfig( huddleId: string, -): Promise { +): Promise { const rows = await db .select() .from(duesConfig) @@ -40,7 +40,7 @@ export async function getDuesConfig( */ export async function getDuesPayments( huddleId: string, -): Promise { +): Promise { return db .select() .from(duesPayments) @@ -57,7 +57,7 @@ export async function setDuesConfig( huddleId: string, userId: string, opts: { amount: number; season?: string | null; note?: string | null }, -): Promise { +): Promise { if (!(await isCommissioner(huddleId, userId))) fail(403, "Only a commissioner can set dues"); @@ -98,7 +98,7 @@ export async function setDuesPaid( rosterId: number, paid: boolean, note?: string | null, -): Promise { +): Promise { if (!(await isCommissioner(huddleId, userId))) fail(403, "Only a commissioner can mark dues as paid"); diff --git a/server/src/services/forumService.ts b/server/src/services/forumService.ts index 714e394..513cbe6 100644 --- a/server/src/services/forumService.ts +++ b/server/src/services/forumService.ts @@ -12,8 +12,8 @@ import { db } from "../db/client.js"; import { forumTopics, forumReplies, - type HuddleForumTopic, - type HuddleForumReply, + type ForumTopic, + type ForumReply, } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner, hasApprovedClaim } from "./huddlesService.js"; import { createPoll, type NewPollInput } from "./pollService.js"; @@ -33,7 +33,7 @@ const MAX_PAGE_SIZE = 50; export async function listTopics( huddleId: string, opts: { limit?: number; before?: string } = {}, -): Promise { +): Promise { const limit = Math.min(opts.limit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE); const conditions = [eq(forumTopics.huddleId, huddleId), isNull(forumTopics.deletedAt)]; if (opts.before) conditions.push(lt(forumTopics.updatedAt, new Date(opts.before))); @@ -50,7 +50,7 @@ export async function listTopics( export async function getTopic( huddleId: string, topicId: string, -): Promise { +): Promise { const rows = await db .select() .from(forumTopics) @@ -70,7 +70,7 @@ export async function listReplies( huddleId: string, topicId: string, opts: { limit?: number; after?: string } = {}, -): Promise { +): Promise { const limit = Math.min(opts.limit ?? MAX_PAGE_SIZE, MAX_PAGE_SIZE); const conditions = [ eq(forumReplies.topicId, topicId), @@ -95,7 +95,7 @@ export async function createTopic(opts: { title: string; body: string; poll?: NewPollInput; -}): Promise { +}): Promise { if (!(await hasApprovedClaim(opts.huddleId, opts.userId))) fail(403, "Only approved members can post to the forum"); @@ -137,7 +137,7 @@ export async function createReply(opts: { topicId: string; userId: string; body: string; -}): Promise { +}): Promise { if (!(await hasApprovedClaim(opts.huddleId, opts.userId))) fail(403, "Only approved members can post to the forum"); diff --git a/server/src/services/huddlesService.ts b/server/src/services/huddlesService.ts index 9b1573f..5b594de 100644 --- a/server/src/services/huddlesService.ts +++ b/server/src/services/huddlesService.ts @@ -7,8 +7,8 @@ import { announcements, teamClaims, type Huddle, - type HuddleCommissioner, - type HuddleAnnouncement, + type Commissioner, + type Announcement, type TeamClaim, } from "../db/schema.js"; @@ -120,7 +120,7 @@ async function commissionerCount(huddleId: string): Promise { export async function listCommissioners( huddleId: string, -): Promise { +): Promise { return db .select() .from(commissioners) @@ -398,7 +398,7 @@ export async function addCommissioner(opts: { huddleId: string; newUserId: string; actingUserId: string; -}): Promise { +}): Promise { if (!(await isCommissioner(opts.huddleId, opts.actingUserId))) fail(403, "Only a commissioner can add co-commissioners"); @@ -581,7 +581,7 @@ export async function createAnnouncement(opts: { userId: string; title: unknown; body: unknown; -}): Promise { +}): Promise { if (!(await isCommissioner(opts.huddleId, opts.userId))) fail(403, "Only a commissioner can post announcements"); @@ -615,7 +615,7 @@ export async function createAnnouncement(opts: { export async function listAnnouncements( huddleId: string, limit = 10, -): Promise { +): Promise { return db .select() .from(announcements) diff --git a/server/src/services/pollService.ts b/server/src/services/pollService.ts index ab8af28..4757604 100644 --- a/server/src/services/pollService.ts +++ b/server/src/services/pollService.ts @@ -13,7 +13,7 @@ import { polls, pollOptions, pollVotes, - type HuddlePoll, + type Poll, } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner, hasApprovedClaim } from "./huddlesService.js"; @@ -312,4 +312,4 @@ export async function castVote(opts: { return result; } -export type { HuddlePoll }; +export type { Poll }; diff --git a/server/src/services/surveyService.ts b/server/src/services/surveyService.ts index 7c94ba1..5deed3f 100644 --- a/server/src/services/surveyService.ts +++ b/server/src/services/surveyService.ts @@ -18,7 +18,7 @@ import { surveyResponses, surveyAnswers, surveyAnswerOptions, - type HuddleSurvey, + type Survey, } from "../db/schema.js"; import { HuddlesServiceError, isCommissioner, hasApprovedClaim } from "./huddlesService.js"; @@ -143,7 +143,7 @@ export interface SurveyResults { questions: SurveyResultsQuestion[]; } -function isClosed(survey: Pick): boolean { +function isClosed(survey: Pick): boolean { return survey.closesAt.getTime() <= Date.now(); } @@ -153,7 +153,7 @@ function isClosed(survey: Pick): boolean { * background job in this app (serverless, no cron) — instead every read * path self-heals the flag the first time it notices the survey has closed. */ -async function applyAutoPublish(rows: HuddleSurvey[]): Promise { +async function applyAutoPublish(rows: Survey[]): Promise { const now = Date.now(); const toPublish = rows.filter( (s) => !s.resultsPublished && s.autoPublishOnClose && s.closesAt.getTime() <= now, @@ -211,7 +211,7 @@ export async function listSurveys(huddleId: string, userId: string): Promise { +async function getSurveyRow(huddleId: string, surveyId: string): Promise { const rows = await db .select() .from(surveys)