feat: 과거 장수 전투 결과 보존 이관 추가

preserved batres 파일을 기수 단위로 검증·체크포인트하고 과거 장수 상세에 연결한다. check-plan에는 전체 이전 항목과 정보 설명을 함께 노출한다.
This commit is contained in:
2026-08-18 13:15:33 +00:00
parent 2c4d52208b
commit c04a93a76c
20 changed files with 1359 additions and 51 deletions
+17 -2
View File
@@ -18,6 +18,7 @@ import {
import {
findLegacyEmperors,
findLegacyGeneral,
findLegacyGeneralBattleResult,
findLegacyGeneralsByOwner,
findLegacyGames,
findLegacyNations,
@@ -412,6 +413,8 @@ export const archiveRouter = router({
let entry: GeneralArchiveEntry | null = null;
let nationRows: ArchiveNationEntry[] = [];
let dynastyId: number | null = null;
let battleResultContent: string | null = null;
let battleResultAvailable = false;
if (input.source === 'legacy') {
if (!LEGACY_ARCHIVE_PROFILES.includes(sourceProfile as LegacyArchiveProfile)) {
throw new TRPCError({ code: 'BAD_REQUEST', message: '지원하지 않는 이전 서버 프로필입니다.' });
@@ -435,9 +438,14 @@ export const archiveRouter = router({
snapshot: canonicalSnapshot(row.data, row.name),
};
const keyInput = [{ sourceProfile: profile, serverId: input.serverId }];
const [nations, emperors] = await Promise.all([
const [nations, emperors, battleResult] = await Promise.all([
findLegacyNations(ctx.db, keyInput),
findLegacyEmperors(ctx.db, keyInput),
findLegacyGeneralBattleResult(ctx.db, {
sourceProfile: profile,
serverId: input.serverId,
generalNo: input.generalNo,
}),
]);
nationRows = nations.map((nation) => ({
source: 'legacy',
@@ -448,6 +456,8 @@ export const archiveRouter = router({
data: asRecord(nation.data),
}));
dynastyId = Number(emperors[0]?.id ?? 0) || null;
battleResultContent = battleResult?.content ?? null;
battleResultAvailable = battleResult !== null;
}
} else {
const row = await ctx.db.oldGeneral.findFirst({
@@ -494,13 +504,18 @@ export const archiveRouter = router({
const nation = resolveNation(nationMap, entry);
const snapshot = entry.snapshot;
const general = await buildGeneralDetail(entry, nation);
const battleResultEntries = (battleResultContent ?? '')
.split(/\r?\n/u)
.map((text, index) => ({ id: index + 1, text }))
.filter((item) => item.text.length > 0)
.reverse();
const logs = {
generalHistory: {
available: snapshot.availability.history,
entries: snapshot.history.map((text, index) => ({ id: index + 1, text })),
},
battleDetail: { available: snapshot.availability.battleDetailLogs, entries: [] },
battleResult: { available: snapshot.availability.battleResultLogs, entries: [] },
battleResult: { available: battleResultAvailable, entries: battleResultEntries },
generalAction: { available: false, entries: [] },
};
return {
@@ -36,6 +36,12 @@ export interface LegacyGeneralRow {
data: unknown;
}
export interface LegacyGeneralBattleResultRow {
content: string;
lineCount: number;
contentHash: string;
}
export interface LegacyNationRow {
sourceProfile: LegacyArchiveProfile;
legacyId: number;
@@ -120,6 +126,24 @@ export const findLegacyGeneral = async (
return rows[0] ?? null;
};
export const findLegacyGeneralBattleResult = async (
db: LegacyArchiveDatabase,
input: { sourceProfile: LegacyArchiveProfile; serverId: string; generalNo: number }
): Promise<LegacyGeneralBattleResultRow | null> => {
const rows = await db.$queryRaw<LegacyGeneralBattleResultRow[]>(GamePrisma.sql`
SELECT
"content",
"line_count" AS "lineCount",
"content_hash" AS "contentHash"
FROM "legacy_archive"."general_battle_result"
WHERE "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[] }