- TypeScript 소스 파일 내 모든 import 구문에서 불필요한 `.js` 확장자를 제거하여 모듈 참조 방식을 표준화했습니다. - 핸들러, 서비스, 리포지토리 및 테스트 코드를 포함한 프로젝트 전반의 import 경로를 일관성 있게 정리했습니다.
136 lines
4.5 KiB
TypeScript
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();
|
|
}
|