feat: 계정 Web Push 전달 기반과 사건 판정을 추가한다
게임 상태 변경과 같은 트랜잭션에 내구성 outbox를 기록하고 Gateway가 계정 설정과 기기 구독으로 fan-out하도록 한다. 전역 기본값은 비활성으로 유지하며 migration과 회귀 테스트를 함께 추가한다.
This commit is contained in:
@@ -47,4 +47,5 @@ export interface DatabaseClient {
|
||||
inputEvent: GamePrisma.InputEventDelegate;
|
||||
turnDaemonLease: GamePrisma.TurnDaemonLeaseDelegate;
|
||||
readModelOutbox: GamePrisma.ReadModelOutboxDelegate;
|
||||
webPushOutbox: GamePrisma.WebPushOutboxDelegate;
|
||||
}
|
||||
|
||||
@@ -10,3 +10,4 @@ export * from './readModelChangeJournal.js';
|
||||
export * from './readModelOutboxDispatcher.js';
|
||||
export * from './readModelCoverageActivation.js';
|
||||
export * from './gameSchemaAdvisoryLock.js';
|
||||
export * from './webPushOutbox.js';
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { WebPushEventType } from '@sammo-ts/common';
|
||||
|
||||
import type { GamePrisma } from './gamePrisma.js';
|
||||
|
||||
export type WebPushOutboxDatabase = Pick<GamePrisma.TransactionClient, 'general' | 'webPushOutbox'>;
|
||||
|
||||
export interface WebPushOutboxEventInput {
|
||||
eventId: string;
|
||||
eventType: WebPushEventType;
|
||||
userIds?: readonly string[];
|
||||
year?: number;
|
||||
month?: number;
|
||||
}
|
||||
|
||||
const uniqueUserIds = (values: readonly string[]): string[] => [...new Set(values.filter(Boolean))].sort();
|
||||
|
||||
export const enqueueWebPushOutboxEvents = async (
|
||||
db: WebPushOutboxDatabase,
|
||||
events: readonly WebPushOutboxEventInput[]
|
||||
): Promise<number> => {
|
||||
if (events.length === 0) return 0;
|
||||
const result = await db.webPushOutbox.createMany({
|
||||
data: events.map((event) => ({
|
||||
eventId: event.eventId,
|
||||
eventType: event.eventType,
|
||||
userIds: uniqueUserIds(event.userIds ?? []),
|
||||
...(event.year === undefined ? {} : { year: event.year }),
|
||||
...(event.month === undefined ? {} : { month: event.month }),
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
return result.count;
|
||||
};
|
||||
|
||||
export const enqueuePrivateMessageWebPush = async (
|
||||
db: WebPushOutboxDatabase,
|
||||
draft: { msgType: string; mailbox: number; destId: number },
|
||||
messageId: number
|
||||
): Promise<void> => {
|
||||
if (draft.msgType !== 'private' || draft.mailbox !== draft.destId) return;
|
||||
const recipient = await db.general.findUnique({
|
||||
where: { id: draft.destId },
|
||||
select: { userId: true },
|
||||
});
|
||||
if (!recipient?.userId) return;
|
||||
await enqueueWebPushOutboxEvents(db, [
|
||||
{
|
||||
eventId: `message:${messageId}`,
|
||||
eventType: 'PRIVATE_MESSAGE_RECEIVED',
|
||||
userIds: [recipient.userId],
|
||||
},
|
||||
]);
|
||||
};
|
||||
Reference in New Issue
Block a user