Fix 5 code review issues in sport-based template conversion
- Migration (0114): TRUNCATE season_template_sports before adding NOT NULL sport_id column to prevent failure on non-empty tables - findDraftableSportsSeasonBySportId: add orderBy(year DESC, draftOn DESC) so the newest season is returned deterministically when multiple draft windows overlap - leagues/new.tsx loader: build a Map from the already-fetched allSportsSeasons instead of firing N×M per-sport DB queries (eliminates ~30 redundant queries) - schema: add UNIQUE(templateId, sportId) on season_template_sports to prevent duplicate sport entries via concurrent requests - schema: add UNIQUE(name) on season_templates so import merge-mode lookup by name is unambiguous (migration 0115) https://claude.ai/code/session_01CoRLHERakMncbPs877izvw
This commit is contained in:
parent
5cea6fd9b5
commit
553c565951
7 changed files with 6251 additions and 11 deletions
|
|
@ -162,6 +162,7 @@ export async function findDraftableSportsSeasonBySportId(sportId: string) {
|
||||||
gte(ss.draftOff, today),
|
gte(ss.draftOff, today),
|
||||||
isNull(ss.fantasySeasonId)
|
isNull(ss.fantasySeasonId)
|
||||||
),
|
),
|
||||||
|
orderBy: (ss, { desc }) => [desc(ss.year), desc(ss.draftOn)],
|
||||||
with: {
|
with: {
|
||||||
sport: true,
|
sport: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { createSeason } from "~/models/season";
|
||||||
import { linkMultipleSportsToSeason } from "~/models/season-sport";
|
import { linkMultipleSportsToSeason } from "~/models/season-sport";
|
||||||
import { createManyTeams } from "~/models/team";
|
import { createManyTeams } from "~/models/team";
|
||||||
import { findActiveSeasonTemplates, findSeasonTemplateWithSports } from "~/models/season-template";
|
import { findActiveSeasonTemplates, findSeasonTemplateWithSports } from "~/models/season-template";
|
||||||
import { findDraftableSportsSeasons, findDraftableSportsSeasonBySportId } from "~/models/sports-season";
|
import { findDraftableSportsSeasons } from "~/models/sports-season";
|
||||||
import { findUserById, updateUser } from "~/models/user";
|
import { findUserById, updateUser } from "~/models/user";
|
||||||
import { generateUniqueTeamNames, prependOwnerToTeamName } from "~/utils/team-names";
|
import { generateUniqueTeamNames, prependOwnerToTeamName } from "~/utils/team-names";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
|
@ -103,19 +103,18 @@ export async function loader(args: Route.LoaderArgs) {
|
||||||
const templates = await findActiveSeasonTemplates();
|
const templates = await findActiveSeasonTemplates();
|
||||||
const allSportsSeasons = await findDraftableSportsSeasons();
|
const allSportsSeasons = await findDraftableSportsSeasons();
|
||||||
|
|
||||||
|
const draftableSeasonBySportId = new Map(
|
||||||
|
allSportsSeasons.map((s) => [s.sportId, s])
|
||||||
|
);
|
||||||
|
|
||||||
const templatesWithSports = await Promise.all(
|
const templatesWithSports = await Promise.all(
|
||||||
templates.map(async (template) => {
|
templates.map(async (template) => {
|
||||||
const t = await findSeasonTemplateWithSports(template.id);
|
const t = await findSeasonTemplateWithSports(template.id);
|
||||||
const resolvedSeasons = await Promise.all(
|
|
||||||
(t?.seasonTemplateSports ?? []).map((ts: { sportId: string }) =>
|
|
||||||
findDraftableSportsSeasonBySportId(ts.sportId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
...template,
|
...template,
|
||||||
sportsSeasonIds: resolvedSeasons
|
sportsSeasonIds: (t?.seasonTemplateSports ?? [])
|
||||||
.filter((s): s is NonNullable<typeof s> => s != null)
|
.map((ts: { sportId: string }) => draftableSeasonBySportId.get(ts.sportId)?.id)
|
||||||
.map((s) => s.id),
|
.filter((id): id is string => id != null),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,9 @@ export const seasonTemplates = pgTable("season_templates", {
|
||||||
isActive: boolean("is_active").notNull().default(true),
|
isActive: boolean("is_active").notNull().default(true),
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
}, (t) => ({
|
||||||
|
uniqueName: uniqueIndex("season_templates_name_unique").on(t.name),
|
||||||
|
}));
|
||||||
|
|
||||||
export const seasons = pgTable("seasons", {
|
export const seasons = pgTable("seasons", {
|
||||||
id: uuid("id").primaryKey().defaultRandom(),
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
|
@ -570,7 +572,9 @@ export const seasonTemplateSports = pgTable("season_template_sports", {
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => sports.id, { onDelete: "cascade" }),
|
.references(() => sports.id, { onDelete: "cascade" }),
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
});
|
}, (t) => ({
|
||||||
|
uniqueTemplateSport: uniqueIndex("season_template_sports_template_sport_unique").on(t.templateId, t.sportId),
|
||||||
|
}));
|
||||||
|
|
||||||
export const seasonSports = pgTable("season_sports", {
|
export const seasonSports = pgTable("season_sports", {
|
||||||
id: uuid("id").primaryKey().defaultRandom(),
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
TRUNCATE TABLE "season_template_sports";
|
||||||
|
--> statement-breakpoint
|
||||||
ALTER TABLE "season_template_sports" DROP CONSTRAINT "season_template_sports_sports_season_id_sports_seasons_id_fk";
|
ALTER TABLE "season_template_sports" DROP CONSTRAINT "season_template_sports_sports_season_id_sports_seasons_id_fk";
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
ALTER TABLE "season_template_sports" ADD COLUMN "sport_id" uuid NOT NULL;--> statement-breakpoint
|
ALTER TABLE "season_template_sports" ADD COLUMN "sport_id" uuid NOT NULL;--> statement-breakpoint
|
||||||
|
|
|
||||||
2
drizzle/0115_spooky_spot.sql
Normal file
2
drizzle/0115_spooky_spot.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "season_template_sports_template_sport_unique" ON "season_template_sports" USING btree ("template_id","sport_id");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS "season_templates_name_unique" ON "season_templates" USING btree ("name");
|
||||||
6225
drizzle/meta/0115_snapshot.json
Normal file
6225
drizzle/meta/0115_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -806,6 +806,13 @@
|
||||||
"when": 1780595525976,
|
"when": 1780595525976,
|
||||||
"tag": "0114_warm_swarm",
|
"tag": "0114_warm_swarm",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 115,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1780596702982,
|
||||||
|
"tag": "0115_spooky_spot",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue