import { Inject, Injectable, NotFoundException } from '@nestjs/common'; import type { IiosPlatformPorts } from '@insignia/iios-contracts'; import { PrismaService } from '../prisma/prisma.service'; import { PLATFORM_PORTS } from '../platform/platform-ports'; import { decideOrThrow } from '../platform/fail-closed'; export interface ThreadMessage { interactionId: string; actorId: string | null; kind: string; occurredAt: Date; parts: Array<{ kind: string; bodyText: string | null; contentRef: string | null; mimeType: string | null; sizeBytes: number | null }>; } @Injectable() export class ThreadsService { constructor( private readonly prisma: PrismaService, @Inject(PLATFORM_PORTS) private readonly ports: IiosPlatformPorts, ) {} async getMessages(threadId: string): Promise<{ threadId: string; messages: ThreadMessage[] }> { const thread = await this.prisma.iiosThread.findUnique({ where: { id: threadId } }); if (!thread) throw new NotFoundException('thread not found'); // Reads are policy-scoped too (fail-closed). await decideOrThrow(this.ports, { action: 'iios.thread.read', threadId, scopeId: thread.scopeId }); const interactions = await this.prisma.iiosInteraction.findMany({ where: { threadId }, orderBy: { occurredAt: 'asc' }, include: { parts: { orderBy: { partIndex: 'asc' } } }, }); return { threadId, messages: interactions.map((i) => ({ interactionId: i.id, actorId: i.actorId, kind: i.kind, occurredAt: i.occurredAt, parts: i.parts.map((p) => ({ kind: p.kind, bodyText: p.bodyText, contentRef: p.contentRef, mimeType: p.mimeType, sizeBytes: p.sizeBytes != null ? Number(p.sizeBytes) : null })), })), }; } }