feat: 과거 장수 전투 결과 보존 이관 추가
preserved batres 파일을 기수 단위로 검증·체크포인트하고 과거 장수 상세에 연결한다. check-plan에는 전체 이전 항목과 정보 설명을 함께 노출한다.
This commit is contained in:
@@ -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[] }
|
||||
|
||||
@@ -30,6 +30,15 @@ const context = (session: GameSessionTokenPayload | null, includeLegacy = false)
|
||||
$queryRaw: async (query: { strings?: readonly string[] }) => {
|
||||
if (!includeLegacy) return [];
|
||||
const sql = query.strings?.join(' ') ?? '';
|
||||
if (sql.includes('legacy_archive"."general_battle_result')) {
|
||||
return [
|
||||
{
|
||||
content: '<S>◆</>190년 1월:첫 전투\n<S>◆</>190년 2월:둘째 전투\n',
|
||||
lineCount: 2,
|
||||
contentHash: 'a'.repeat(64),
|
||||
},
|
||||
];
|
||||
}
|
||||
if (sql.includes('legacy_archive"."general')) {
|
||||
return [
|
||||
{
|
||||
@@ -385,6 +394,13 @@ describe('archive.myPastPlays', () => {
|
||||
logs: expect.objectContaining({
|
||||
generalHistory: { available: true, entries: [{ id: 1, text: '이전 서버 열전' }] },
|
||||
battleDetail: { available: false, entries: [] },
|
||||
battleResult: {
|
||||
available: true,
|
||||
entries: [
|
||||
{ id: 2, text: '<S>◆</>190년 2월:둘째 전투' },
|
||||
{ id: 1, text: '<S>◆</>190년 1월:첫 전투' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(JSON.stringify(detail)).not.toContain('raw_data');
|
||||
|
||||
@@ -115,7 +115,16 @@ const installArchive = async (page: Page, options: { battleAvailable?: boolean }
|
||||
],
|
||||
},
|
||||
battleDetail: { available: false, entries: [] },
|
||||
battleResult: { available: false, entries: [] },
|
||||
battleResult: {
|
||||
available: true,
|
||||
entries: [
|
||||
{
|
||||
id: 2,
|
||||
text: '<S>◆</>214년 3월:<div class="small_war_log">관우 7000 ← 장비 0</div>',
|
||||
},
|
||||
{ id: 1, text: '<S>◆</>214년 2월:관우 6500 → 여포 0' },
|
||||
],
|
||||
},
|
||||
generalAction: { available: false, entries: [] },
|
||||
},
|
||||
});
|
||||
@@ -177,9 +186,8 @@ test('past plays is available without a current general and preserves desktop in
|
||||
await expect(page.locator('[data-log-type="battleDetail"]')).toContainText(
|
||||
'이 기수에는 전투 기록이 보존되지 않았습니다.'
|
||||
);
|
||||
await expect(page.locator('[data-log-type="battleResult"]')).toContainText(
|
||||
'이 기수에는 전투 결과가 보존되지 않았습니다.'
|
||||
);
|
||||
await expect(page.locator('[data-log-type="battleResult"]')).toContainText('214년 3월:관우 7000 ← 장비 0');
|
||||
await expect(page.locator('[data-log-type="battleResult"]')).not.toContainText('<div');
|
||||
await expect(page.locator('[data-log-type="generalAction"]')).toContainText(
|
||||
'이 기수에는 개인 기록이 보존되지 않았습니다.'
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('readReleaseManifest', () => {
|
||||
await expect(readReleaseManifest(workspaceRoot)).resolves.toMatchObject({
|
||||
controllerProtocol: RELEASE_CONTROLLER_PROTOCOL,
|
||||
gatewaySchemaHead: '20260818000000_add_legacy_import_checkpoints',
|
||||
gameSchemaHead: '20260818000000_add_legacy_import_checkpoints',
|
||||
gameSchemaHead: '20260818010000_add_legacy_battle_result_logs',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user