regionals enrollments
This commit is contained in:
@@ -129,3 +129,260 @@ export const unenrollFreeAgent = createServerFn()
|
||||
logger.info('Player unenrolled as free agent', { playerId: player.id });
|
||||
})
|
||||
);
|
||||
|
||||
export const generateRandomTeams = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
tournamentId: z.string(),
|
||||
seed: z.number().optional()
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
const freeAgents = await pbAdmin.getFreeAgents(data.tournamentId);
|
||||
|
||||
if (freeAgents.length < 2) {
|
||||
throw new Error("Need at least 2 players to create teams");
|
||||
}
|
||||
|
||||
if (freeAgents.length % 2 !== 0) {
|
||||
throw new Error("Need an even number of players to create teams");
|
||||
}
|
||||
|
||||
const playerIds = freeAgents.map(fa => fa.player?.id).filter(Boolean) as string[];
|
||||
|
||||
const allTeams = await pbAdmin.getTeamsWithFilter(
|
||||
playerIds.map(id => `players.id ?= "${id}"`).join(" || "),
|
||||
"players,tournaments"
|
||||
);
|
||||
|
||||
const invalidPairings = new Set<string>();
|
||||
const mostRecentRegionalPartners = new Map<string, string>();
|
||||
|
||||
let mostRecentRegionalDate: Date | null = null;
|
||||
|
||||
for (const team of allTeams) {
|
||||
const teamPlayers = (team.expand?.players || []) as any[];
|
||||
if (teamPlayers.length !== 2) continue;
|
||||
|
||||
const [p1, p2] = teamPlayers.map((p: any) => p.id).sort();
|
||||
const pairKey = `${p1}|${p2}`;
|
||||
|
||||
const teamTournaments = (team.expand?.tournaments || []) as any[];
|
||||
const hasMainlineTournament = teamTournaments.some((t: any) => !t.regional);
|
||||
|
||||
if (hasMainlineTournament) {
|
||||
invalidPairings.add(pairKey);
|
||||
} else if (team.private && teamTournaments.length > 0) {
|
||||
const regionalTournaments = teamTournaments.filter((t: any) => t.regional);
|
||||
for (const tournament of regionalTournaments) {
|
||||
const tournamentDate = new Date(tournament.start_time);
|
||||
if (!mostRecentRegionalDate || tournamentDate > mostRecentRegionalDate) {
|
||||
mostRecentRegionalDate = tournamentDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mostRecentRegionalDate) {
|
||||
for (const team of allTeams) {
|
||||
if (!team.private) continue;
|
||||
|
||||
const teamPlayers = (team.expand?.players || []) as any[];
|
||||
if (teamPlayers.length !== 2) continue;
|
||||
|
||||
const teamTournaments = (team.expand?.tournaments || []) as any[];
|
||||
const regionalTournaments = teamTournaments.filter((t: any) => t.regional);
|
||||
|
||||
for (const tournament of regionalTournaments) {
|
||||
const tournamentDate = new Date(tournament.start_time);
|
||||
if (tournamentDate.getTime() === mostRecentRegionalDate.getTime()) {
|
||||
const [p1Id, p2Id] = teamPlayers.map((p: any) => p.id);
|
||||
mostRecentRegionalPartners.set(p1Id, p2Id);
|
||||
mostRecentRegionalPartners.set(p2Id, p1Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function canPairPlayers(p1Id: string, p2Id: string): boolean {
|
||||
const pairKey = [p1Id, p2Id].sort().join('|');
|
||||
if (invalidPairings.has(pairKey)) return false;
|
||||
|
||||
const p1LastPartner = mostRecentRegionalPartners.get(p1Id);
|
||||
if (p1LastPartner === p2Id) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const seed = data.seed || Math.floor(Math.random() * 1000000);
|
||||
|
||||
function seededRandom(s: number) {
|
||||
const x = Math.sin(s++) * 10000;
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
let currentSeed = seed;
|
||||
const shuffled = [...freeAgents];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(seededRandom(currentSeed++) * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
|
||||
const assignments = [];
|
||||
const paired = new Set<string>();
|
||||
const MAX_ATTEMPTS = 1000;
|
||||
let attempts = 0;
|
||||
|
||||
while (paired.size < shuffled.length && attempts < MAX_ATTEMPTS) {
|
||||
attempts++;
|
||||
|
||||
for (let i = 0; i < shuffled.length; i++) {
|
||||
if (paired.has(shuffled[i].player!.id)) continue;
|
||||
|
||||
for (let j = i + 1; j < shuffled.length; j++) {
|
||||
if (paired.has(shuffled[j].player!.id)) continue;
|
||||
|
||||
const player1 = shuffled[i].player!;
|
||||
const player2 = shuffled[j].player!;
|
||||
|
||||
if (canPairPlayers(player1.id, player2.id)) {
|
||||
const teamName = `${player1.first_name} And ${player2.first_name}`;
|
||||
|
||||
assignments.push({
|
||||
player1,
|
||||
player2,
|
||||
teamName
|
||||
});
|
||||
|
||||
paired.add(player1.id);
|
||||
paired.add(player2.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (paired.size < shuffled.length) {
|
||||
currentSeed++;
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(seededRandom(currentSeed++) * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
assignments.length = 0;
|
||||
paired.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (paired.size < shuffled.length) {
|
||||
throw new Error("Unable to create valid pairings with current restrictions. Please manually adjust enrollments.");
|
||||
}
|
||||
|
||||
logger.info('Generated random team assignments with restrictions', {
|
||||
tournamentId: data.tournamentId,
|
||||
teamCount: assignments.length,
|
||||
seed,
|
||||
attempts
|
||||
});
|
||||
|
||||
return { assignments, seed };
|
||||
})
|
||||
);
|
||||
|
||||
export const confirmTeamAssignments = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
tournamentId: z.string(),
|
||||
assignments: z.array(z.object({
|
||||
player1Id: z.string(),
|
||||
player2Id: z.string(),
|
||||
teamName: z.string()
|
||||
}))
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
const createdTeams = [];
|
||||
let reusedCount = 0;
|
||||
|
||||
for (const assignment of data.assignments) {
|
||||
const existingTeams = await pbAdmin.getTeamsWithFilter(
|
||||
`private = true && players.id ?= "${assignment.player1Id}" && players.id ?= "${assignment.player2Id}"`,
|
||||
"players,tournaments"
|
||||
);
|
||||
|
||||
let teamToUse = null;
|
||||
|
||||
for (const team of existingTeams) {
|
||||
const teamPlayers = (team.expand?.players || []) as any[];
|
||||
|
||||
if (teamPlayers.length !== 2) continue;
|
||||
|
||||
const playerIds = teamPlayers.map((p: any) => p.id).sort();
|
||||
const assignmentIds = [assignment.player1Id, assignment.player2Id].sort();
|
||||
if (playerIds[0] !== assignmentIds[0] || playerIds[1] !== assignmentIds[1]) continue;
|
||||
|
||||
const teamTournaments = (team.expand?.tournaments || []) as any[];
|
||||
const hasMainlineTournament = teamTournaments.some((t: any) => !t.regional);
|
||||
|
||||
if (!hasMainlineTournament) {
|
||||
teamToUse = team;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (teamToUse) {
|
||||
await pbAdmin.enrollTeam(data.tournamentId, teamToUse.id);
|
||||
createdTeams.push(teamToUse);
|
||||
reusedCount++;
|
||||
logger.info('Reusing existing regional team', { teamId: teamToUse.id, teamName: teamToUse.name });
|
||||
} else {
|
||||
const team = await pbAdmin.createTeam({
|
||||
name: assignment.teamName,
|
||||
players: [assignment.player1Id, assignment.player2Id],
|
||||
private: true
|
||||
});
|
||||
|
||||
await pbAdmin.enrollTeam(data.tournamentId, team.id);
|
||||
createdTeams.push(team);
|
||||
}
|
||||
}
|
||||
|
||||
for (const assignment of data.assignments) {
|
||||
await pbAdmin.unenrollFreeAgent(assignment.player1Id, data.tournamentId);
|
||||
await pbAdmin.unenrollFreeAgent(assignment.player2Id, data.tournamentId);
|
||||
}
|
||||
|
||||
logger.info('Confirmed team assignments', {
|
||||
tournamentId: data.tournamentId,
|
||||
teamCount: createdTeams.length,
|
||||
reusedCount,
|
||||
newCount: createdTeams.length - reusedCount
|
||||
});
|
||||
|
||||
return { teams: createdTeams };
|
||||
})
|
||||
);
|
||||
|
||||
export const adminEnrollPlayer = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
playerId: z.string(),
|
||||
tournamentId: z.string()
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
await pbAdmin.enrollFreeAgent(data.playerId, "", data.tournamentId);
|
||||
logger.info('Admin enrolled player', { playerId: data.playerId, tournamentId: data.tournamentId });
|
||||
})
|
||||
);
|
||||
|
||||
export const adminUnenrollPlayer = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
playerId: z.string(),
|
||||
tournamentId: z.string()
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
await pbAdmin.unenrollFreeAgent(data.playerId, data.tournamentId);
|
||||
logger.info('Admin unenrolled player', { playerId: data.playerId, tournamentId: data.tournamentId });
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user