Files
core2026/app/game-api/src/services/legacyArchiveStore.ts
T
Hide_D d6a25c63b0 feat: 이전 서버 기록 조회 화면을 통합
지난 플레이를 현재 장수 카드와 전투·숙련도·기록 컴포넌트로 표시하고, 중앙 archive의 소유자 기반 조회를 연결한다. 명예의 전당과 왕조에 현재/이전 서버 source 분리를 추가한다.
2026-08-17 15:55:43 +00:00

265 lines
8.8 KiB
TypeScript

import { GamePrisma } from '@sammo-ts/infra';
import { isLegacyArchiveProfile, LEGACY_ARCHIVE_PROFILES, type LegacyArchiveProfile } from '@sammo-ts/common';
import type { DatabaseClient } from '../context.js';
export { isLegacyArchiveProfile, LEGACY_ARCHIVE_PROFILES, type LegacyArchiveProfile };
export type LegacyArchiveDatabase = Pick<DatabaseClient, '$queryRaw'>;
export interface LegacyGameHistoryRow {
sourceProfile: LegacyArchiveProfile;
serverId: string;
legacyId: number;
openedAt: Date;
completedAt: Date | null;
legacyDate: Date;
winnerNation: number | null;
map: string | null;
season: number;
scenario: number;
scenarioName: string;
rawEnv: unknown;
}
export interface LegacyGeneralRow {
sourceProfile: LegacyArchiveProfile;
serverId: string;
generalNo: number;
legacyId: number;
owner: string | null;
name: string;
lastYearMonth: number;
turnTime: Date;
schemaVersion: number;
sourceFormat: string;
data: unknown;
}
export interface LegacyNationRow {
sourceProfile: LegacyArchiveProfile;
legacyId: number;
serverId: string;
nation: number;
data: unknown;
archivedAt: Date;
}
export interface LegacyEmperorRow {
id: bigint;
sourceProfile: LegacyArchiveProfile;
legacyId: number;
serverId: string | null;
data: unknown;
}
export interface LegacyHallOptionRow {
sourceProfile: LegacyArchiveProfile;
season: number;
scenario: number;
scenarioName: string;
count: bigint;
}
export interface LegacyHallRow {
sourceProfile: LegacyArchiveProfile;
serverId: string;
generalNo: number;
type: string;
value: number;
owner: string | null;
aux: unknown;
}
export const findLegacyGeneralsByOwner = async (
db: LegacyArchiveDatabase,
owner: string
): Promise<LegacyGeneralRow[]> =>
db.$queryRaw<LegacyGeneralRow[]>(GamePrisma.sql`
SELECT
"source_profile" AS "sourceProfile",
"server_id" AS "serverId",
"general_no" AS "generalNo",
"legacy_id" AS "legacyId",
"owner",
"name",
"last_yearmonth" AS "lastYearMonth",
"turntime" AS "turnTime",
"schema_version" AS "schemaVersion",
"source_format" AS "sourceFormat",
"data"
FROM "legacy_archive"."general"
WHERE "owner" = ${owner}
ORDER BY "last_yearmonth" DESC, "source_profile", "server_id" DESC, "general_no"
`);
export const findLegacyGeneral = async (
db: LegacyArchiveDatabase,
input: { owner: string; sourceProfile: LegacyArchiveProfile; serverId: string; generalNo: number }
): Promise<LegacyGeneralRow | null> => {
const rows = await db.$queryRaw<LegacyGeneralRow[]>(GamePrisma.sql`
SELECT
"source_profile" AS "sourceProfile",
"server_id" AS "serverId",
"general_no" AS "generalNo",
"legacy_id" AS "legacyId",
"owner",
"name",
"last_yearmonth" AS "lastYearMonth",
"turntime" AS "turnTime",
"schema_version" AS "schemaVersion",
"source_format" AS "sourceFormat",
"data"
FROM "legacy_archive"."general"
WHERE "owner" = ${input.owner}
AND "source_profile" = ${input.sourceProfile}
AND "server_id" = ${input.serverId}
AND "general_no" = ${input.generalNo}
LIMIT 1
`);
return rows[0] ?? null;
};
export const findLegacyGeneralsForServer = async (
db: LegacyArchiveDatabase,
input: { sourceProfile: LegacyArchiveProfile; serverId: string; generalNos: number[] }
): Promise<Array<Pick<LegacyGeneralRow, 'generalNo' | 'name' | 'lastYearMonth'>>> => {
if (input.generalNos.length === 0) return [];
return db.$queryRaw<Array<Pick<LegacyGeneralRow, 'generalNo' | 'name' | 'lastYearMonth'>>>(GamePrisma.sql`
SELECT
"general_no" AS "generalNo",
"name",
"last_yearmonth" AS "lastYearMonth"
FROM "legacy_archive"."general"
WHERE "source_profile" = ${input.sourceProfile}
AND "server_id" = ${input.serverId}
AND "general_no" IN (${GamePrisma.join(input.generalNos)})
`);
};
export const findLegacyGames = async (
db: LegacyArchiveDatabase,
keys?: Array<{ sourceProfile: LegacyArchiveProfile; serverId: string }>
): Promise<LegacyGameHistoryRow[]> => {
if (keys && keys.length === 0) return [];
const condition = keys
? GamePrisma.sql`WHERE ("source_profile", "server_id") IN (${GamePrisma.join(
keys.map(({ sourceProfile, serverId }) => GamePrisma.sql`(${sourceProfile}, ${serverId})`)
)})`
: GamePrisma.empty;
return db.$queryRaw<LegacyGameHistoryRow[]>(GamePrisma.sql`
SELECT
"source_profile" AS "sourceProfile",
"server_id" AS "serverId",
"legacy_id" AS "legacyId",
"opened_at" AS "openedAt",
"completed_at" AS "completedAt",
"legacy_date" AS "legacyDate",
"winner_nation" AS "winnerNation",
"map",
"season",
"scenario",
"scenario_name" AS "scenarioName",
"raw_env" AS "rawEnv"
FROM "legacy_archive"."game_history"
${condition}
`);
};
export const findLegacyNations = async (
db: LegacyArchiveDatabase,
keys: Array<{ sourceProfile: LegacyArchiveProfile; serverId: string }>
): Promise<LegacyNationRow[]> => {
if (keys.length === 0) return [];
return db.$queryRaw<LegacyNationRow[]>(GamePrisma.sql`
SELECT
"source_profile" AS "sourceProfile",
"legacy_id" AS "legacyId",
"server_id" AS "serverId",
"nation",
"data",
"archived_at" AS "archivedAt"
FROM "legacy_archive"."nation"
WHERE ("source_profile", "server_id") IN (${GamePrisma.join(
keys.map(({ sourceProfile, serverId }) => GamePrisma.sql`(${sourceProfile}, ${serverId})`)
)})
ORDER BY "archived_at" DESC, "legacy_id" DESC
`);
};
export const findLegacyEmperors = async (
db: LegacyArchiveDatabase,
keys?: Array<{ sourceProfile: LegacyArchiveProfile; serverId: string }>
): Promise<LegacyEmperorRow[]> => {
if (keys && keys.length === 0) return [];
const condition = keys
? GamePrisma.sql`WHERE ("source_profile", "server_id") IN (${GamePrisma.join(
keys.map(({ sourceProfile, serverId }) => GamePrisma.sql`(${sourceProfile}, ${serverId})`)
)})`
: GamePrisma.empty;
return db.$queryRaw<LegacyEmperorRow[]>(GamePrisma.sql`
SELECT
"id",
"source_profile" AS "sourceProfile",
"legacy_id" AS "legacyId",
"server_id" AS "serverId",
"data"
FROM "legacy_archive"."emperor"
${condition}
ORDER BY "id" DESC
`);
};
export const findLegacyEmperor = async (db: LegacyArchiveDatabase, id: number): Promise<LegacyEmperorRow | null> => {
const rows = await db.$queryRaw<LegacyEmperorRow[]>(GamePrisma.sql`
SELECT
"id",
"source_profile" AS "sourceProfile",
"legacy_id" AS "legacyId",
"server_id" AS "serverId",
"data"
FROM "legacy_archive"."emperor"
WHERE "id" = ${id}
LIMIT 1
`);
return rows[0] ?? null;
};
export const findLegacyHallOptions = async (db: LegacyArchiveDatabase): Promise<LegacyHallOptionRow[]> =>
db.$queryRaw<LegacyHallOptionRow[]>(GamePrisma.sql`
SELECT
"source_profile" AS "sourceProfile",
"season",
"scenario",
MAX("scenario_name") AS "scenarioName",
COUNT(*)::bigint AS "count"
FROM "legacy_archive"."game_history"
GROUP BY "source_profile", "season", "scenario"
ORDER BY "season" DESC, "source_profile", "scenario"
`);
export const findLegacyHallRows = async (
db: LegacyArchiveDatabase,
input: { sourceProfile: LegacyArchiveProfile; season: number; scenario?: number; type: string; take: number }
): Promise<LegacyHallRow[]> => {
const scenarioCondition =
input.scenario === undefined ? GamePrisma.empty : GamePrisma.sql`AND "scenario" = ${input.scenario}`;
return db.$queryRaw<LegacyHallRow[]>(GamePrisma.sql`
SELECT
"source_profile" AS "sourceProfile",
"server_id" AS "serverId",
"general_no" AS "generalNo",
"type",
"value",
"owner",
"aux"
FROM "legacy_archive"."hall"
WHERE "source_profile" = ${input.sourceProfile}
AND "season" = ${input.season}
${scenarioCondition}
AND "type" = ${input.type}
ORDER BY "value" DESC
LIMIT ${input.take}
`);
};