feat(service): P2.3 Socket.io /message gateway + real SessionVerifier

open_thread (create-or-join) / send_message / read / delivered / typing; HS256
token verified on connect (SessionVerifier reuses AppTokenVerifier pattern);
OutboxBus bridge propagates REST/ingest messages to rooms (dedup vs inline emit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 01:12:49 +05:30
parent 827fb52f5f
commit 50ef34dd90
6 changed files with 375 additions and 5 deletions
@@ -183,6 +183,31 @@ export class MessageService {
return { interactionId, actorId: actor.id };
}
async markDelivered(
threadId: string,
principal: MessagePrincipal,
interactionId: string,
): Promise<{ interactionId: string; actorId: string }> {
const thread = await this.prisma.iiosThread.findUnique({ where: { id: threadId } });
if (!thread) throw new NotFoundException('thread not found');
const actor = await this.resolveActor(thread.scopeId, principal);
await this.prisma.iiosMessageReceipt.upsert({
where: { interactionId_actorId_receiptKind: { interactionId, actorId: actor.id, receiptKind: 'DELIVERED' } },
create: { interactionId, actorId: actor.id, receiptKind: 'DELIVERED' },
update: { occurredAt: new Date() },
});
return { interactionId, actorId: actor.id };
}
async getMessageById(id: string): Promise<MessageDto | null> {
const i = await this.prisma.iiosInteraction.findUnique({
where: { id },
include: { parts: { orderBy: { partIndex: 'asc' } } },
});
if (!i || !i.threadId) return null;
return this.toDto(i, i.threadId);
}
async history(threadId: string): Promise<MessageDto[]> {
const interactions = await this.prisma.iiosInteraction.findMany({
where: { threadId },