feat(frontend): align in-game layouts with ref
This commit is contained in:
@@ -47,11 +47,24 @@ export const diplomacyRouter = router({
|
||||
});
|
||||
|
||||
const nations = await ctx.db.nation.findMany({
|
||||
where: { id: { not: me.nationId } },
|
||||
select: { id: true, name: true, color: true, level: true },
|
||||
orderBy: { id: 'asc' },
|
||||
});
|
||||
|
||||
const signerIds = [
|
||||
...new Set(
|
||||
letters.flatMap((letter) =>
|
||||
letter.destSignerId === null ? [letter.srcSignerId] : [letter.srcSignerId, letter.destSignerId]
|
||||
)
|
||||
),
|
||||
];
|
||||
const signers = await ctx.db.general.findMany({
|
||||
where: { id: { in: signerIds } },
|
||||
select: { id: true, name: true, picture: true, imageServer: true },
|
||||
});
|
||||
const nationById = new Map(nations.map((nation) => [nation.id, nation]));
|
||||
const signerById = new Map(signers.map((general) => [general.id, general]));
|
||||
|
||||
const result = letters.map((letter) => {
|
||||
const aux = asRecord(letter.aux);
|
||||
const src = asRecord(aux.src);
|
||||
@@ -60,24 +73,32 @@ export const diplomacyRouter = router({
|
||||
const detail =
|
||||
permission < 3 && letter.textDetail ? '(권한이 부족합니다)' : purifyDiplomacyHtml(letter.textDetail);
|
||||
const reason = asRecord(aux.reason);
|
||||
const srcNation = nationById.get(letter.srcNationId);
|
||||
const destNation = nationById.get(letter.destNationId);
|
||||
const srcSigner = signerById.get(letter.srcSignerId);
|
||||
const destSigner = letter.destSignerId === null ? undefined : signerById.get(letter.destSignerId);
|
||||
|
||||
return {
|
||||
id: letter.id,
|
||||
src: {
|
||||
nationId: letter.srcNationId,
|
||||
nationName: typeof src.nationName === 'string' ? src.nationName : '',
|
||||
nationColor: typeof src.nationColor === 'string' ? src.nationColor : '',
|
||||
generalId: typeof src.generalId === 'number' ? src.generalId : null,
|
||||
generalName: typeof src.generalName === 'string' ? src.generalName : null,
|
||||
nationName: typeof src.nationName === 'string' ? src.nationName : (srcNation?.name ?? ''),
|
||||
nationColor: typeof src.nationColor === 'string' ? src.nationColor : (srcNation?.color ?? ''),
|
||||
generalId: typeof src.generalId === 'number' ? src.generalId : letter.srcSignerId,
|
||||
generalName: typeof src.generalName === 'string' ? src.generalName : (srcSigner?.name ?? null),
|
||||
generalIcon: typeof src.generalIcon === 'string' ? src.generalIcon : null,
|
||||
generalPicture: srcSigner?.picture ?? null,
|
||||
generalImageServer: srcSigner?.imageServer ?? 0,
|
||||
},
|
||||
dest: {
|
||||
nationId: letter.destNationId,
|
||||
nationName: typeof dest.nationName === 'string' ? dest.nationName : '',
|
||||
nationColor: typeof dest.nationColor === 'string' ? dest.nationColor : '',
|
||||
generalId: typeof dest.generalId === 'number' ? dest.generalId : null,
|
||||
generalName: typeof dest.generalName === 'string' ? dest.generalName : null,
|
||||
nationName: typeof dest.nationName === 'string' ? dest.nationName : (destNation?.name ?? ''),
|
||||
nationColor: typeof dest.nationColor === 'string' ? dest.nationColor : (destNation?.color ?? ''),
|
||||
generalId: typeof dest.generalId === 'number' ? dest.generalId : letter.destSignerId,
|
||||
generalName: typeof dest.generalName === 'string' ? dest.generalName : (destSigner?.name ?? null),
|
||||
generalIcon: typeof dest.generalIcon === 'string' ? dest.generalIcon : null,
|
||||
generalPicture: destSigner?.picture ?? null,
|
||||
generalImageServer: destSigner?.imageServer ?? 0,
|
||||
},
|
||||
prevId: letter.prevId,
|
||||
state: mapLetterState(letter.state),
|
||||
@@ -95,7 +116,7 @@ export const diplomacyRouter = router({
|
||||
|
||||
return {
|
||||
letters: result,
|
||||
nations,
|
||||
nations: nations.filter((nation) => nation.id !== me.nationId),
|
||||
myNationId: me.nationId,
|
||||
permission,
|
||||
};
|
||||
|
||||
@@ -78,32 +78,33 @@ const storedLetter = {
|
||||
textBrief: '<p>공개</p><img src=x onerror="globalThis.__briefXss=1"><script>alert(1)</script>',
|
||||
textDetail: '<strong>기밀</strong><a href="javascript:alert(2)" onclick="alert(3)">링크</a>',
|
||||
date: new Date('2026-07-31T00:00:00.000Z'),
|
||||
srcSignerId: 1,
|
||||
destSignerId: 2,
|
||||
aux: {
|
||||
src: { nationName: '위', nationColor: '#0000ff', generalId: 1, generalName: '외교담당' },
|
||||
dest: { nationName: '촉', nationColor: '#ff0000' },
|
||||
},
|
||||
};
|
||||
|
||||
const buildContext = (officerLevel = 12) => {
|
||||
const buildContext = (officerLevel = 12, letter: Record<string, unknown> = storedLetter) => {
|
||||
const create = vi.fn(async () => ({ id: 9 }));
|
||||
const db = {
|
||||
general: {
|
||||
findFirst: vi.fn(async () => buildGeneral(officerLevel)),
|
||||
findMany: vi.fn(async () => [
|
||||
{ id: 1, name: '현재 송신자', picture: 'src.jpg', imageServer: 0 },
|
||||
{ id: 2, name: '현재 수신자', picture: 'dest.jpg', imageServer: 0 },
|
||||
]),
|
||||
},
|
||||
nation: {
|
||||
findUnique: vi.fn(async () => ({ meta: {} })),
|
||||
findMany: vi.fn(async ({ where }: { where: { id?: { in?: number[]; not?: number } } }) => {
|
||||
if (where.id?.in) {
|
||||
return [
|
||||
{ id: 1, name: '위', color: '#0000ff' },
|
||||
{ id: 2, name: '촉', color: '#ff0000' },
|
||||
];
|
||||
}
|
||||
return [{ id: 2, name: '촉', color: '#ff0000', level: 5 }];
|
||||
}),
|
||||
findMany: vi.fn(async () => [
|
||||
{ id: 1, name: '위', color: '#0000ff', level: 5 },
|
||||
{ id: 2, name: '촉', color: '#ff0000', level: 5 },
|
||||
]),
|
||||
},
|
||||
diplomacyLetter: {
|
||||
findMany: vi.fn(async () => [storedLetter]),
|
||||
findMany: vi.fn(async () => [letter]),
|
||||
findFirst: vi.fn(async () => null),
|
||||
create,
|
||||
},
|
||||
@@ -163,6 +164,29 @@ describe('diplomacy HTML API boundary', () => {
|
||||
expect(redacted.letters[0]?.detail).toBe('(권한이 부족합니다)');
|
||||
});
|
||||
|
||||
it('reconstructs imported letters whose snapshot metadata is empty', async () => {
|
||||
const imported = { ...storedLetter, aux: {} };
|
||||
const result = await buildContext(12, imported).caller.diplomacy.getLetters();
|
||||
|
||||
expect(result.nations).toEqual([{ id: 2, name: '촉', color: '#ff0000', level: 5 }]);
|
||||
expect(result.letters[0]).toMatchObject({
|
||||
src: {
|
||||
nationName: '위',
|
||||
nationColor: '#0000ff',
|
||||
generalId: 1,
|
||||
generalName: '현재 송신자',
|
||||
generalPicture: 'src.jpg',
|
||||
},
|
||||
dest: {
|
||||
nationName: '촉',
|
||||
nationColor: '#ff0000',
|
||||
generalId: 2,
|
||||
generalName: '현재 수신자',
|
||||
generalPicture: 'dest.jpg',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects direct send mutations below the Ref diplomacy permission without writing', async () => {
|
||||
const fixture = buildContext(5);
|
||||
await expect(
|
||||
|
||||
Reference in New Issue
Block a user