feat: API 변화 저널과 outbox worker를 연결

입력 이벤트의 업무 변경, 성공 원장, revision 및 outbox를 한 transaction에서 커밋한다. 설문의 commit 전 Redis 발행을 제거하고 접속 점수용 private revision과 재시도·retention을 갖춘 dispatcher lifecycle을 추가한다.
This commit is contained in:
2026-08-16 18:22:57 +00:00
parent b41b161e9c
commit f858ca43f4
15 changed files with 606 additions and 52 deletions
+5 -21
View File
@@ -1,7 +1,7 @@
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { asRecord, createEmptyRealtimeReadModelChanges, LiteHashDRBG, RandUtil } from '@sammo-ts/common';
import { asRecord, LiteHashDRBG, RandUtil } from '@sammo-ts/common';
import { GamePrisma } from '@sammo-ts/infra';
import {
ITEM_KEYS,
@@ -17,24 +17,8 @@ import {
} from '@sammo-ts/logic';
import { authedProcedure, router } from '../../trpc.js';
import type { GameApiContext } from '../../context.js';
import { getMyGeneral } from '../shared/general.js';
import { loadCurrentGameTime, type CurrentGameTime } from '../../services/gameClock.js';
import { publishRealtimeReadModelChanges } from '../../realtime/publisher.js';
const publishFrontStatusChange = async (
ctx: GameApiContext,
options: { generalId?: number; global?: boolean }
): Promise<void> => {
const changes = createEmptyRealtimeReadModelChanges();
if (options.generalId) changes.frontStatusActorIds = [options.generalId];
if (options.global) changes.frontStatusChanged = true;
try {
await publishRealtimeReadModelChanges(ctx.redis, ctx.profile.name, changes);
} catch {
// 설문 DB mutation은 이미 commit되었으므로 실시간 알림 실패로 되돌리지 않는다.
}
};
const hasAdminRole = (roles: string[], profileName: string): boolean => {
if (roles.includes('superuser') || roles.includes('admin') || roles.includes('admin.superuser')) {
@@ -523,7 +507,7 @@ export const voteRouter = router({
throw new TRPCError({ code: 'BAD_REQUEST', message: rewardResult.reason });
}
await publishFrontStatusChange(ctx, { generalId: general.id });
ctx.changeJournal?.mark('front.general', general.id);
return { ok: true, wonLottery: rewardResult.awardedUnique };
}),
addComment: authedProcedure
@@ -634,7 +618,7 @@ export const voteRouter = router({
)
`);
await publishFrontStatusChange(ctx, { global: true });
ctx.changeJournal?.mark('front.global');
return { ok: true };
}),
updatePoll: adminProcedure
@@ -732,7 +716,7 @@ export const voteRouter = router({
`);
if (input.title !== undefined || endAt !== undefined) {
await publishFrontStatusChange(ctx, { global: true });
ctx.changeJournal?.mark('front.global');
}
return { ok: true };
}),
@@ -748,7 +732,7 @@ export const voteRouter = router({
if (!rows[0]?.id) {
throw new TRPCError({ code: 'NOT_FOUND', message: '설문조사가 없습니다.' });
}
await publishFrontStatusChange(ctx, { global: true });
ctx.changeJournal?.mark('front.global');
return { ok: true };
}),
getAdminStatus: adminProcedure.query(async () => ({ ok: true })),