feat: 프로필별 플레이 감사 조회와 권한 검사 연결

This commit is contained in:
2026-09-16 02:52:52 +00:00
parent 35155e6b59
commit ed2597aef3
12 changed files with 718 additions and 1 deletions
+182
View File
@@ -0,0 +1,182 @@
import { z } from 'zod';
import { canReadPlayAuditAccounts } from '@sammo-ts/common';
import { router } from '../../trpc.js';
import {
auditProcedure,
findAuditMonth,
pageResult,
readAudit,
readAuditWorld,
zAuditPage,
zAuditMonth,
} from './shared.js';
import {
citySelect,
generalSelect,
projectCurrentCity,
projectCurrentGeneral,
zAuditCityData,
zAuditGeneralData,
} from './projection.js';
export const playAuditRouter = router({
capabilities: auditProcedure.query(({ ctx }) => ({
profileName: ctx.profile.name,
read: true,
accounts: canReadPlayAuditAccounts(ctx.auth!.user.roles, ctx.profile.name),
})),
coverage: auditProcedure
.input(
z
.object({ cursor: zAuditMonth.optional(), limit: z.number().int().min(1).max(200).default(50) })
.strict()
.default({ limit: 50 })
)
.query(({ ctx, input }) =>
readAudit(ctx, async (tx) => {
const world = await readAuditWorld(tx);
const samples = world.serverId
? await tx.playAuditMonth.findMany({
where: {
serverId: world.serverId,
...(input.cursor
? {
OR: [
{ year: { gt: input.cursor.year } },
{ year: input.cursor.year, month: { gt: input.cursor.month } },
{
year: input.cursor.year,
month: input.cursor.month,
kind: { gt: input.cursor.kind },
},
],
}
: {}),
},
take: input.limit + 1,
orderBy: [{ year: 'asc' }, { month: 'asc' }, { kind: 'asc' }],
select: { year: true, month: true, kind: true, settlementsComplete: true, createdAt: true },
})
: [];
return {
...world,
status: !world.serverId
? ('IDENTITY_MISSING' as const)
: samples.length
? ('COLLECTED' as const)
: input.cursor
? ('PAGE_EMPTY' as const)
: ('NOT_COLLECTED' as const),
samples: samples.slice(0, input.limit),
nextCursor:
samples.length > input.limit
? {
year: samples[input.limit - 1]!.year,
month: samples[input.limit - 1]!.month,
kind: z.enum(['MONTH_END', 'FINAL']).parse(samples[input.limit - 1]!.kind),
}
: null,
};
})
),
generals: auditProcedure
.input(
zAuditPage.extend({
cityId: z.number().int().nonnegative().optional(),
population: z.enum(['human', 'npc', 'troopNpc']).optional(),
})
)
.query(({ ctx, input }) =>
readAudit(ctx, async (tx) => {
const world = await readAuditWorld(tx);
const npcState =
input.population === 'human'
? { lt: 2 }
: input.population === 'npc'
? { gte: 2, not: 5 }
: input.population === 'troopNpc'
? 5
: undefined;
const filter = { nationId: input.nationId, cityId: input.cityId, npcState };
if (input.at) {
const sample = await findAuditMonth(tx, world, input.at);
const rows = sample
? await tx.playAuditGeneral.findMany({
where: {
sampleId: sample.id,
...filter,
generalId: input.cursor === undefined ? undefined : { gt: input.cursor },
},
orderBy: { generalId: 'asc' },
take: input.limit + 1,
select: { data: true },
})
: [];
return {
...world,
sample,
collected: Boolean(sample),
...pageResult(
rows.map((row) => zAuditGeneralData.parse(row.data)),
input.limit,
(row) => row.id
),
};
}
const rows = await tx.general.findMany({
where: { ...filter, id: input.cursor === undefined ? undefined : { gt: input.cursor } },
orderBy: { id: 'asc' },
take: input.limit + 1,
select: generalSelect,
});
return {
...world,
sample: null,
collected: true,
...pageResult(rows.map(projectCurrentGeneral), input.limit, (row) => row.id),
};
})
),
cities: auditProcedure.input(zAuditPage).query(({ ctx, input }) =>
readAudit(ctx, async (tx) => {
const world = await readAuditWorld(tx);
if (input.at) {
const sample = await findAuditMonth(tx, world, input.at);
const rows = sample
? await tx.playAuditCity.findMany({
where: {
sampleId: sample.id,
nationId: input.nationId,
cityId: input.cursor === undefined ? undefined : { gt: input.cursor },
},
orderBy: { cityId: 'asc' },
take: input.limit + 1,
select: { data: true },
})
: [];
return {
...world,
sample,
collected: Boolean(sample),
...pageResult(
rows.map((row) => zAuditCityData.parse(row.data)),
input.limit,
(row) => row.id
),
};
}
const rows = await tx.city.findMany({
where: { nationId: input.nationId, id: input.cursor === undefined ? undefined : { gt: input.cursor } },
orderBy: { id: 'asc' },
take: input.limit + 1,
select: citySelect,
});
return {
...world,
sample: null,
collected: true,
...pageResult(rows.map(projectCurrentCity), input.limit, (row) => row.id),
};
})
),
});