brackt/app/routes/admin.sports-seasons.$id.expected-values.server.ts
Claude 2c2df277d1
fix: validate participantIds server-side instead of trusting form input
Fetch participants from the DB in the action using the season ID from
the URL params, eliminating the untrusted participantIds hidden field.
Remove the now-unused hidden input from the frontend form.

https://claude.ai/code/session_01WHRynBCcugSK7HHEmN6Yuy
2026-02-19 23:40:38 +00:00

86 lines
3 KiB
TypeScript

import type { Route } from "./+types/admin.sports-seasons.$id.expected-values";
import { findSportsSeasonById } from "~/models/sports-season";
import { findParticipantsBySportsSeasonId } from "~/models/participant";
import {
upsertParticipantEV,
getAllParticipantEVsForSeason
} from "~/models/participant-expected-value";
export async function loader({ params }: Route.LoaderArgs) {
const sportsSeason = await findSportsSeasonById(params.id);
if (!sportsSeason) {
throw new Response("Sports season not found", { status: 404 });
}
const participants = await findParticipantsBySportsSeasonId(params.id);
const existingEVs = await getAllParticipantEVsForSeason(params.id);
// Create a map of participant ID to EV data
const evMap = new Map(existingEVs.map(ev => [ev.participantId, ev]));
return {
sportsSeason: sportsSeason as typeof sportsSeason & { sport: { id: string; name: string; type: string; slug: string } },
participants,
existingEVs: evMap,
};
}
const scoringRules = {
pointsFor1st: 100,
pointsFor2nd: 70,
pointsFor3rd: 50,
pointsFor4th: 40,
pointsFor5th: 25,
pointsFor6th: 25,
pointsFor7th: 15,
pointsFor8th: 15,
};
export async function action({ request, params }: Route.ActionArgs) {
const formData = await request.formData();
const participants = await findParticipantsBySportsSeasonId(params.id);
const participantIds = participants.map((p: { id: string }) => p.id);
try {
const results = await Promise.all(
participantIds.map(async (participantId) => {
const probFirst = parseFloat(formData.get(`probFirst_${participantId}`) as string || "0") / 100;
const probSecond = parseFloat(formData.get(`probSecond_${participantId}`) as string || "0") / 100;
const probThird = parseFloat(formData.get(`probThird_${participantId}`) as string || "0") / 100;
const probFourth = parseFloat(formData.get(`probFourth_${participantId}`) as string || "0") / 100;
const probFifth = parseFloat(formData.get(`probFifth_${participantId}`) as string || "0") / 100;
const probSixth = parseFloat(formData.get(`probSixth_${participantId}`) as string || "0") / 100;
const probSeventh = parseFloat(formData.get(`probSeventh_${participantId}`) as string || "0") / 100;
const probEighth = parseFloat(formData.get(`probEighth_${participantId}`) as string || "0") / 100;
return upsertParticipantEV({
participantId,
sportsSeasonId: params.id,
probabilities: {
probFirst,
probSecond,
probThird,
probFourth,
probFifth,
probSixth,
probSeventh,
probEighth,
},
scoringRules,
source: "manual",
});
})
);
const totalEV = results.reduce((sum, r) => sum + parseFloat(r.expectedValue), 0);
return { success: true, totalEV };
} catch (error) {
console.error("Error saving probabilities:", error);
return {
error: error instanceof Error ? error.message : "Failed to save probabilities"
};
}
}