import { FieldValue, Timestamp } from "firebase-admin/firestore"; import { firestore } from "../firebase"; import { isDateString } from "../types/dateString"; import type { SeasonConfig, SeasonHistoryDoc } from "../types/season"; const CONFIG_COLLECTION = "config"; const CONFIG_DOC = "season"; /** * `config/season`을 읽는다. 문서가 없거나 필수 필드가 온전치 않으면 * 시즌제 비활성으로 취급해 `null`을 반환한다. */ export async function getSeasonConfig(): Promise { const snap = await firestore .collection(CONFIG_COLLECTION) .doc(CONFIG_DOC) .get(); if (!snap.exists) return null; const data = snap.data() ?? {}; if (typeof data.id !== "string" || data.id.length === 0) return null; if (!isDateString(data.startDate) || !isDateString(data.endDate)) return null; const config: SeasonConfig = { id: data.id, startDate: data.startDate, endDate: data.endDate, }; if (data.settledAt instanceof Timestamp) config.settledAt = data.settledAt; return config; } /** 정산 완료 마커 기록 — 이후 `maybeSettleSeason`은 no-op이 된다. */ export async function markSeasonSettled(): Promise { await firestore .collection(CONFIG_COLLECTION) .doc(CONFIG_DOC) .set({ settledAt: FieldValue.serverTimestamp() }, { merge: true }); } /** * 해당 시즌의 `seasonHistory`가 이미 기록된 유저와 그 최종 점수 목록. * 정산이 중간에 실패했던 경우, 재실행에서 이들의 점수를 순위 산정에 * 다시 합류시키기 위한 조회다(collection group — seasonId 단일 필드 인덱스 필요). */ export async function listSeasonHistoryEntries( seasonId: string ): Promise> { const snap = await firestore .collectionGroup("seasonHistory") .where("seasonId", "==", seasonId) .get(); return snap.docs .map((d) => ({ uid: d.ref.parent.parent?.id ?? "", tierPoints: (d.data() as Partial).tierPoints ?? 0, })) .filter((e) => e.uid.length > 0); } /** 배치 한도(500 write)의 여유분 — 유저당 2 write(아카이브 + 리셋). */ const SETTLE_CHUNK = 200; /** * 유저별 시즌 정산 쓰기: `seasonHistory/{seasonId}` 아카이브와 * `tierPoints` 리셋(+`rankSnapshot` 제거)을 같은 배치에 묶는다. * 중간 실패 시에도 유저 단위로는 "정산 완료" 아니면 "점수 유지"만 존재한다. */ export async function settleUsers( entries: Array<{ uid: string; history: Omit }> ): Promise { for (let i = 0; i < entries.length; i += SETTLE_CHUNK) { const batch = firestore.batch(); for (const { uid, history } of entries.slice(i, i + SETTLE_CHUNK)) { const userRef = firestore.collection("users").doc(uid); batch.set(userRef.collection("seasonHistory").doc(history.seasonId), { ...history, settledAt: FieldValue.serverTimestamp(), }); batch.update(userRef, { tierPoints: 0, rankSnapshot: FieldValue.delete(), }); } await batch.commit(); } }