feat: 프로필 감사 화면에서 정책 버전과 전후 값을 조회
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { nationSeries, zAuditNation } from './nationSeries.js';
|
||||
import { cityDetail, generalDetail, generalTurns } from './details.js';
|
||||
import { generalLogs } from './logs.js';
|
||||
import { policyHistory, policyVersion } from './policies.js';
|
||||
import { z } from 'zod';
|
||||
import { canReadPlayAuditAccounts } from '@sammo-ts/common';
|
||||
import { router } from '../../trpc.js';
|
||||
@@ -23,6 +24,8 @@ import {
|
||||
} from './projection.js';
|
||||
|
||||
export const playAuditRouter = router({
|
||||
policyHistory,
|
||||
policyVersion,
|
||||
generalLogs,
|
||||
cityDetail,
|
||||
generalDetail,
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
import { asRecord } from '@sammo-ts/common';
|
||||
import type { GamePrisma } from '@sammo-ts/infra';
|
||||
import { auditProcedure, monthOrdinal, readAudit, readAuditWorld, zAuditMonth } from './shared.js';
|
||||
|
||||
const zArea = z.enum(['NPC_VALUES', 'NPC_NATION_PRIORITY', 'NPC_GENERAL_PRIORITY', 'DEFENCE']);
|
||||
const zMonth = zAuditMonth.omit({ kind: true });
|
||||
const zActor = z
|
||||
.object({
|
||||
generalId: z.number().int(),
|
||||
name: z.string(),
|
||||
nationId: z.number().int(),
|
||||
officerLevel: z.number().int(),
|
||||
npcState: z.number().int(),
|
||||
})
|
||||
.nullable();
|
||||
const summarySelect = {
|
||||
id: true,
|
||||
schemaVersion: true,
|
||||
nationId: true,
|
||||
area: true,
|
||||
revision: true,
|
||||
previousId: true,
|
||||
source: true,
|
||||
year: true,
|
||||
month: true,
|
||||
actor: true,
|
||||
createdAt: true,
|
||||
} satisfies GamePrisma.PlayAuditPolicySelect;
|
||||
type Summary = GamePrisma.PlayAuditPolicyGetPayload<{ select: typeof summarySelect }>;
|
||||
|
||||
/** 향후 자국 권한 API의 공개 정보 경계. 실제 자국 인가는 호출부에서 별도로 수행한다. */
|
||||
export const projectPolicySummary = (row: Summary) => {
|
||||
if (row.schemaVersion !== 1)
|
||||
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: '지원하지 않는 정책 기록 버전입니다.' });
|
||||
return {
|
||||
id: row.id,
|
||||
nationId: row.nationId,
|
||||
area: zArea.parse(row.area),
|
||||
revision: row.revision,
|
||||
previousId: row.previousId,
|
||||
source: z.enum(['BASELINE', 'CHANGE', 'OBSERVED_GAP']).parse(row.source),
|
||||
year: row.year,
|
||||
month: row.month,
|
||||
actor: zActor.parse(row.actor),
|
||||
createdAt: row.createdAt,
|
||||
};
|
||||
};
|
||||
|
||||
/** 계정·요청·내부 진단 값을 제외한 정책 전후 projection이다. */
|
||||
export const projectPolicyConfiguration = (
|
||||
row: Summary & { before: GamePrisma.JsonValue; after: GamePrisma.JsonValue }
|
||||
) => {
|
||||
const before = row.before === null ? null : asRecord(row.before);
|
||||
const after = asRecord(row.after);
|
||||
return {
|
||||
...projectPolicySummary(row),
|
||||
fields: [...new Set([...Object.keys(before ?? {}), ...Object.keys(after)])].sort().map((key) => ({
|
||||
key,
|
||||
beforeJson: before === null ? null : JSON.stringify(before[key] ?? null),
|
||||
afterJson: JSON.stringify(after[key] ?? null),
|
||||
changed: before !== null && JSON.stringify(before[key] ?? null) !== JSON.stringify(after[key] ?? null),
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
export const policyHistory = auditProcedure
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
nationId: z.number().int().nonnegative(),
|
||||
area: zArea,
|
||||
from: zMonth,
|
||||
to: zMonth,
|
||||
cursor: z.number().int().positive().optional(),
|
||||
limit: z.number().int().min(1).max(200).default(50),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
.query(({ ctx, input }) =>
|
||||
readAudit(ctx, async (tx) => {
|
||||
const world = await readAuditWorld(tx);
|
||||
const from = monthOrdinal(input.from.year, input.from.month);
|
||||
const to = monthOrdinal(input.to.year, input.to.month);
|
||||
if (
|
||||
from > to ||
|
||||
from < monthOrdinal(world.startYear, world.startMonth) ||
|
||||
to > monthOrdinal(world.year, world.month)
|
||||
)
|
||||
throw new TRPCError({ code: 'BAD_REQUEST', message: '현재 기수 안의 정책 조회 기간을 선택해 주세요.' });
|
||||
const rows = world.serverId
|
||||
? await tx.playAuditPolicy.findMany({
|
||||
where: {
|
||||
serverId: world.serverId,
|
||||
nationId: input.nationId,
|
||||
area: input.area,
|
||||
revision: input.cursor === undefined ? undefined : { lt: input.cursor },
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{ year: { gt: input.from.year } },
|
||||
{ year: input.from.year, month: { gte: input.from.month } },
|
||||
],
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{ year: { lt: input.to.year } },
|
||||
{ year: input.to.year, month: { lte: input.to.month } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: { revision: 'desc' },
|
||||
take: input.limit + 1,
|
||||
select: summarySelect,
|
||||
})
|
||||
: [];
|
||||
return {
|
||||
...world,
|
||||
coverage: world.serverId ? ('RECORDED_VERSIONS_ONLY' as const) : ('IDENTITY_MISSING' as const),
|
||||
items: rows.slice(0, input.limit).map(projectPolicySummary),
|
||||
nextCursor: rows.length > input.limit ? rows[input.limit - 1]!.revision : null,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const policyVersion = auditProcedure
|
||||
.input(z.object({ id: z.string().regex(/^[a-f0-9]{64}$/) }).strict())
|
||||
.query(({ ctx, input }) =>
|
||||
readAudit(ctx, async (tx) => {
|
||||
const world = await readAuditWorld(tx);
|
||||
const row = world.serverId
|
||||
? await tx.playAuditPolicy.findFirst({
|
||||
where: { id: input.id, serverId: world.serverId },
|
||||
select: {
|
||||
...summarySelect,
|
||||
tick: true,
|
||||
ordinal: true,
|
||||
before: true,
|
||||
after: true,
|
||||
requestId: true,
|
||||
inputSequence: true,
|
||||
},
|
||||
})
|
||||
: null;
|
||||
if (!row) throw new TRPCError({ code: 'NOT_FOUND', message: '현재 기수의 정책 버전을 찾을 수 없습니다.' });
|
||||
return {
|
||||
...world,
|
||||
version: {
|
||||
...projectPolicyConfiguration(row),
|
||||
tick: row.tick?.toString() ?? null,
|
||||
ordinal: row.ordinal,
|
||||
requestId: row.requestId,
|
||||
inputSequence: row.inputSequence?.toString() ?? null,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
Reference in New Issue
Block a user