feat: Ref 호환 접속 제한과 벌점 초기화 구현
실행 중인 프로필에서만 접속 벌점을 누적하고 제한 임계값과 대상 경로를 Ref 순서에 맞춘다. 자기 턴 명령 성공 시 순간 점수를 같은 flush에서 초기화하며 월간 누적 감쇠는 유지한다. 제한 중 메인 자동 갱신과 실시간 구독을 중지하고 수동 갱신 성공 시 복구한다.
This commit is contained in:
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
||||
|
||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||
|
||||
import { authedProcedure } from '../../../trpc.js';
|
||||
import { accessLimitAuthedInputProcedure } from '../../../trpc.js';
|
||||
import { getMyGeneral } from '../../shared/general.js';
|
||||
import {
|
||||
assertNationAccess,
|
||||
@@ -13,77 +13,75 @@ import {
|
||||
type GeneralLogType,
|
||||
} from '../shared.js';
|
||||
|
||||
export const getGeneralLog = authedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
generalId: z.number().int().positive(),
|
||||
type: zGeneralLogType,
|
||||
beforeId: z.number().int().positive().optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const me = await getMyGeneral(ctx);
|
||||
assertNationAccess(me);
|
||||
export const getGeneralLog = accessLimitAuthedInputProcedure(
|
||||
z.object({
|
||||
generalId: z.number().int().positive(),
|
||||
type: zGeneralLogType,
|
||||
beforeId: z.number().int().positive().optional(),
|
||||
})
|
||||
).query(async ({ ctx, input }) => {
|
||||
const me = await getMyGeneral(ctx);
|
||||
assertNationAccess(me);
|
||||
|
||||
const [nation, target] = await Promise.all([
|
||||
ctx.db.nation.findUnique({
|
||||
where: { id: me.nationId },
|
||||
select: { meta: true },
|
||||
}),
|
||||
ctx.db.general.findUnique({
|
||||
where: { id: input.generalId },
|
||||
select: { id: true, nationId: true, npcState: true },
|
||||
}),
|
||||
]);
|
||||
const [nation, target] = await Promise.all([
|
||||
ctx.db.nation.findUnique({
|
||||
where: { id: me.nationId },
|
||||
select: { meta: true },
|
||||
}),
|
||||
ctx.db.general.findUnique({
|
||||
where: { id: input.generalId },
|
||||
select: { id: true, nationId: true, npcState: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!nation) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Nation not found' });
|
||||
}
|
||||
if (!target) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'General not found' });
|
||||
}
|
||||
if (!nation) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Nation not found' });
|
||||
}
|
||||
if (!target) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'General not found' });
|
||||
}
|
||||
|
||||
const permissionLevel = resolveNationPermission(me, nation.meta, true);
|
||||
if (permissionLevel < 1) {
|
||||
throw new TRPCError({ code: 'FORBIDDEN', message: '권한이 부족합니다.' });
|
||||
}
|
||||
if (target.nationId !== me.nationId) {
|
||||
throw new TRPCError({ code: 'FORBIDDEN', message: '같은 나라의 장수가 아닙니다.' });
|
||||
}
|
||||
if (input.type === 'generalAction' && target.npcState < 2 && target.id !== me.id && permissionLevel < 2) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: '권한이 부족합니다. 유저 장수의 개인 기록은 수뇌만 열람 가능합니다.',
|
||||
});
|
||||
}
|
||||
|
||||
const categoryMap: Record<GeneralLogType, LogCategory> = {
|
||||
generalHistory: LogCategory.HISTORY,
|
||||
generalAction: LogCategory.ACTION,
|
||||
battleResult: LogCategory.BATTLE_BRIEF,
|
||||
battleDetail: LogCategory.BATTLE_DETAIL,
|
||||
};
|
||||
|
||||
const logs = await ctx.db.logEntry.findMany({
|
||||
where: {
|
||||
generalId: target.id,
|
||||
scope: LogScope.GENERAL,
|
||||
category: categoryMap[input.type],
|
||||
...(input.type !== 'generalHistory' && input.beforeId ? { id: { lt: input.beforeId } } : {}),
|
||||
},
|
||||
orderBy: { id: 'desc' },
|
||||
...(input.type === 'generalHistory' ? {} : { take: 30 }),
|
||||
const permissionLevel = resolveNationPermission(me, nation.meta, true);
|
||||
if (permissionLevel < 1) {
|
||||
throw new TRPCError({ code: 'FORBIDDEN', message: '권한이 부족합니다.' });
|
||||
}
|
||||
if (target.nationId !== me.nationId) {
|
||||
throw new TRPCError({ code: 'FORBIDDEN', message: '같은 나라의 장수가 아닙니다.' });
|
||||
}
|
||||
if (input.type === 'generalAction' && target.npcState < 2 && target.id !== me.id && permissionLevel < 2) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: '권한이 부족합니다. 유저 장수의 개인 기록은 수뇌만 열람 가능합니다.',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: input.type,
|
||||
const categoryMap: Record<GeneralLogType, LogCategory> = {
|
||||
generalHistory: LogCategory.HISTORY,
|
||||
generalAction: LogCategory.ACTION,
|
||||
battleResult: LogCategory.BATTLE_BRIEF,
|
||||
battleDetail: LogCategory.BATTLE_DETAIL,
|
||||
};
|
||||
|
||||
const logs = await ctx.db.logEntry.findMany({
|
||||
where: {
|
||||
generalId: target.id,
|
||||
logs: logs.map((entry) => ({
|
||||
id: entry.id,
|
||||
text: entry.text,
|
||||
year: entry.year,
|
||||
month: entry.month,
|
||||
createdAt: formatDateTime(entry.createdAt),
|
||||
})),
|
||||
};
|
||||
scope: LogScope.GENERAL,
|
||||
category: categoryMap[input.type],
|
||||
...(input.type !== 'generalHistory' && input.beforeId ? { id: { lt: input.beforeId } } : {}),
|
||||
},
|
||||
orderBy: { id: 'desc' },
|
||||
...(input.type === 'generalHistory' ? {} : { take: 30 }),
|
||||
});
|
||||
|
||||
return {
|
||||
type: input.type,
|
||||
generalId: target.id,
|
||||
logs: logs.map((entry) => ({
|
||||
id: entry.id,
|
||||
text: entry.text,
|
||||
year: entry.year,
|
||||
month: entry.month,
|
||||
createdAt: formatDateTime(entry.createdAt),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user