mmday-firebase/src/repositories/voteRepository.ts
윤정민 c94ce69e4f Remove redundant .js extensions from import paths.
- TypeScript 소스 파일 내 모든 import 구문에서 불필요한 `.js` 확장자를 제거하여 모듈 참조 방식을 표준화했습니다.
- 핸들러, 서비스, 리포지토리 및 테스트 코드를 포함한 프로젝트 전반의 import 경로를 일관성 있게 정리했습니다.
2026-05-06 16:17:51 +09:00

136 lines
4.5 KiB
TypeScript

import { ServerValue } from "firebase-admin/database";
import { rtdb } from "../firebase";
import type { VoteEntry } from "../types/panit";
import type { DateString } from "../types/dateString";
/**
* 특정 경기의 해당 유저 투표를 조회한다.
*
* @param gameId - 경기 ID
* @param uid - 유저 ID
* @returns 유저의 투표 정보. 없으면 `null`.
*/
export async function getUserVote(gameId: string, uid: string): Promise<VoteEntry | null> {
const snap = await rtdb.ref(`/votes/${gameId}/users/${uid}`).get();
return snap.exists() ? (snap.val() as VoteEntry) : null;
}
/**
* 경기의 홈/원정 투표 카운트를 조회한다. 값이 없으면 0으로 채운다.
*
* @param gameId - 경기 ID
*/
export async function getCounts(
gameId: string
): Promise<{ homeCount: number; awayCount: number }> {
const snap = await rtdb.ref(`/votes/${gameId}/counts`).get();
const val = snap.val() ?? {};
return {
homeCount: Number(val.homeCount ?? 0),
awayCount: Number(val.awayCount ?? 0),
};
}
/**
* 특정 경기에 투표한 모든 유저의 기록을 조회한다.
*
* @param gameId - 경기 ID
* @returns `{ [uid]: VoteEntry }` 맵. 아무도 없으면 빈 객체.
*/
export async function getAllUserVotes(
gameId: string
): Promise<Record<string, VoteEntry>> {
const snap = await rtdb.ref(`/votes/${gameId}/users`).get();
return snap.exists() ? (snap.val() as Record<string, VoteEntry>) : {};
}
/**
* 새로운 투표를 제출한다. 카운트 증가, 유저별 투표, 날짜별 유저 투표 인덱스를 원자적으로 갱신한다.
*
* @param params.gameId - 경기 ID
* @param params.uid - 투표한 유저 ID
* @param params.date - 경기 날짜 (유저별 날짜 인덱스 경로에 사용)
* @param params.side - 투표 진영 (`home` | `away`)
* @param params.team - 투표한 팀 코드
*/
export async function submitVote(params: {
gameId: string;
uid: string;
date: DateString;
side: "home" | "away";
team: string;
}): Promise<void> {
const { gameId, uid, date, side, team } = params;
const counterKey = side === "home" ? "homeCount" : "awayCount";
const updates: Record<string, unknown> = {};
updates[`/votes/${gameId}/counts/${counterKey}`] = ServerValue.increment(1);
updates[`/votes/${gameId}/users/${uid}`] = { team };
updates[`/userVotes/${uid}/${date}/${gameId}`] = { team };
await rtdb.ref().update(updates);
}
/**
* 기존 투표를 다른 진영/팀으로 변경한다. 이전 카운트를 감소시키고 새 카운트를 증가시킨다.
*
* @param params.gameId - 경기 ID
* @param params.uid - 유저 ID
* @param params.date - 경기 날짜
* @param params.oldSide - 기존 투표 진영
* @param params.newSide - 변경할 진영
* @param params.newTeam - 변경할 팀 코드
*/
export async function changeVote(params: {
gameId: string;
uid: string;
date: DateString;
oldSide: "home" | "away";
newSide: "home" | "away";
newTeam: string;
}): Promise<void> {
const { gameId, uid, date, oldSide, newSide, newTeam } = params;
const updates: Record<string, unknown> = {};
updates[`/votes/${gameId}/counts/${oldSide}Count`] = ServerValue.increment(-1);
updates[`/votes/${gameId}/counts/${newSide}Count`] = ServerValue.increment(1);
updates[`/votes/${gameId}/users/${uid}`] = { team: newTeam };
updates[`/userVotes/${uid}/${date}/${gameId}`] = { team: newTeam };
await rtdb.ref().update(updates);
}
/**
* 특정 유저가 특정 날짜에 한 모든 경기 투표를 조회한다.
*
* @param uid - 유저 ID
* @param date - 날짜 (YYYY-MM-DD)
* @returns `{ [gameId]: VoteEntry }` 맵. 투표가 없으면 빈 객체.
*/
export async function getUserDateVotes(
uid: string,
date: DateString
): Promise<Record<string, VoteEntry>> {
const snap = await rtdb.ref(`/userVotes/${uid}/${date}`).get();
return snap.exists() ? (snap.val() as Record<string, VoteEntry>) : {};
}
/**
* 특정 경기의 모든 투표 데이터를 삭제한다 (카운트/유저 투표 전체).
*
* 주의: `/userVotes/{uid}/{date}/{gameId}` 인덱스는 이 함수로 정리되지 않는다.
*
* @param gameId - 삭제할 경기 ID
*/
export async function deleteGameVotes(gameId: string): Promise<void> {
await rtdb.ref(`/votes/${gameId}`).remove();
}
/**
* 유저별 일자 인덱스에서 특정 경기 항목을 제거한다.
* 취소/리컨실리 경로에서 아카이브 대상에서 제외하기 위해 사용.
*/
export async function deleteUserVoteGame(
uid: string,
date: DateString,
gameId: string
): Promise<void> {
await rtdb.ref(`/userVotes/${uid}/${date}/${gameId}`).remove();
}