mmday-firebase/src/services/predictionService.ts
2026-04-13 09:18:34 +09:00

107 lines
3.2 KiB
TypeScript

import { HttpError } from "../middleware/errors.js";
import { getGame, listByDate, type GameWithId } from "../repositories/gameRepository.js";
import {
getUserVote,
submitVote,
changeVote,
getCounts,
getUserDateVotes,
} from "../repositories/voteRepository.js";
import type { Game, VoteEntry } from "../types/panit.js";
import { fromTimestamp, parseDateString, type DateString } from "../types/dateString.js";
function gameDate(game: Game): DateString {
return fromTimestamp(game.time);
}
function sideOf(game: Game, teamCode: string): "home" | "away" {
if (teamCode === game.homeTeamCode) return "home";
if (teamCode === game.awayTeamCode) return "away";
throw new HttpError(400, `teamCode ${teamCode} not in game ${game.homeTeamCode}/${game.awayTeamCode}`);
}
async function loadWaitingGame(gameId: string): Promise<Game> {
const game = await getGame(gameId);
if (!game) throw new HttpError(404, `game not found: ${gameId}`);
if (game.status !== "waiting") {
throw new HttpError(409, `game status is ${game.status}, voting closed`);
}
return game;
}
export async function createPrediction(
uid: string,
body: { gameId?: string; selectedTeamCode?: string }
): Promise<{ ok: true }> {
if (!body.gameId || !body.selectedTeamCode) {
throw new HttpError(400, "gameId and selectedTeamCode required");
}
const game = await loadWaitingGame(body.gameId);
const side = sideOf(game, body.selectedTeamCode);
const existing = await getUserVote(body.gameId, uid);
if (existing) throw new HttpError(409, "already voted; use PUT to change");
await submitVote({
gameId: body.gameId,
uid,
date: gameDate(game),
side,
team: body.selectedTeamCode,
});
return { ok: true };
}
export async function updatePrediction(
uid: string,
body: { gameId?: string; selectedTeamCode?: string }
): Promise<{ ok: true; changed: boolean }> {
if (!body.gameId || !body.selectedTeamCode) {
throw new HttpError(400, "gameId and selectedTeamCode required");
}
const game = await loadWaitingGame(body.gameId);
const newSide = sideOf(game, body.selectedTeamCode);
const existing = await getUserVote(body.gameId, uid);
if (!existing) throw new HttpError(400, "no prior vote; use POST");
if (existing.team === body.selectedTeamCode) return { ok: true, changed: false };
const oldSide = sideOf(game, existing.team);
await changeVote({
gameId: body.gameId,
uid,
date: gameDate(game),
oldSide,
newSide,
newTeam: body.selectedTeamCode,
});
return { ok: true, changed: true };
}
export async function getMyVotes(
uid: string,
date: string
): Promise<Record<string, VoteEntry>> {
try {
return getUserDateVotes(uid, parseDateString(date));
} catch (err) {
throw new HttpError(400, (err as Error).message);
}
}
export async function listGamesByDate(date: string): Promise<GameWithId[]> {
try {
return await listByDate(parseDateString(date));
} catch (err) {
throw new HttpError(400, (err as Error).message);
}
}
export async function getSummary(
gameId: string
): Promise<{ homeCount: number; awayCount: number }> {
if (!gameId) throw new HttpError(400, "gameId required");
return getCounts(gameId);
}