diff --git a/packages/iios-service/prisma/migrations/20260630193410_messaging_init/migration.sql b/packages/iios-service/prisma/migrations/20260630193410_messaging_init/migration.sql new file mode 100644 index 0000000..7a88985 --- /dev/null +++ b/packages/iios-service/prisma/migrations/20260630193410_messaging_init/migration.sql @@ -0,0 +1,44 @@ +-- CreateEnum +CREATE TYPE "IiosReceiptKind" AS ENUM ('DELIVERED', 'READ', 'ACKED', 'FAILED', 'SUPPRESSED', 'RATE_LIMITED'); + +-- CreateTable +CREATE TABLE "IiosMessageReceipt" ( + "id" TEXT NOT NULL, + "interactionId" TEXT NOT NULL, + "actorId" TEXT, + "receiptKind" "IiosReceiptKind" NOT NULL, + "occurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "providerReceiptRef" TEXT, + "metadata" JSONB, + + CONSTRAINT "IiosMessageReceipt_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosUnreadCounter" ( + "threadId" TEXT NOT NULL, + "actorId" TEXT NOT NULL, + "unreadCount" INTEGER NOT NULL DEFAULT 0, + "lastReadInteractionId" TEXT, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "IiosUnreadCounter_pkey" PRIMARY KEY ("threadId","actorId") +); + +-- CreateIndex +CREATE INDEX "IiosMessageReceipt_interactionId_idx" ON "IiosMessageReceipt"("interactionId"); + +-- CreateIndex +CREATE UNIQUE INDEX "IiosMessageReceipt_interactionId_actorId_receiptKind_key" ON "IiosMessageReceipt"("interactionId", "actorId", "receiptKind"); + +-- AddForeignKey +ALTER TABLE "IiosMessageReceipt" ADD CONSTRAINT "IiosMessageReceipt_interactionId_fkey" FOREIGN KEY ("interactionId") REFERENCES "IiosInteraction"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosMessageReceipt" ADD CONSTRAINT "IiosMessageReceipt_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "IiosActorRef"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosUnreadCounter" ADD CONSTRAINT "IiosUnreadCounter_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "IiosThread"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosUnreadCounter" ADD CONSTRAINT "IiosUnreadCounter_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "IiosActorRef"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/iios-service/prisma/schema.prisma b/packages/iios-service/prisma/schema.prisma index 20255a8..d195d2b 100644 --- a/packages/iios-service/prisma/schema.prisma +++ b/packages/iios-service/prisma/schema.prisma @@ -66,6 +66,15 @@ enum IiosDeliveryState { REDACTED } +enum IiosReceiptKind { + DELIVERED + READ + ACKED + FAILED + SUPPRESSED + RATE_LIMITED +} + // ─── Kernel tables ──────────────────────────────────────────────── /// Frozen six-vector scope. orgId + appId are REQUIRED (no global-by-null). @@ -124,6 +133,8 @@ model IiosActorRef { threadsCreated IiosThread[] @relation("ThreadCreatedBy") participations IiosThreadParticipant[] interactions IiosInteraction[] + receipts IiosMessageReceipt[] + unreadCounters IiosUnreadCounter[] } /// A configured channel surface (PORTAL in P1). @@ -163,8 +174,9 @@ model IiosThread { updatedAt DateTime @updatedAt metadata Json? - participants IiosThreadParticipant[] - interactions IiosInteraction[] + participants IiosThreadParticipant[] + interactions IiosInteraction[] + unreadCounters IiosUnreadCounter[] @@index([scopeId, status]) } @@ -207,7 +219,8 @@ model IiosInteraction { traceId String? metadata Json? - parts IiosMessagePart[] + parts IiosMessagePart[] + receipts IiosMessageReceipt[] @@unique([scopeId, idempotencyKey]) @@index([threadId, occurredAt]) @@ -255,3 +268,34 @@ model IiosProcessedEvent { @@id([consumerName, eventId]) } + +// ─── P2: messaging — receipts & unread ──────────────────────────── + +/// Delivery/read receipt for a message interaction, per actor. +model IiosMessageReceipt { + id String @id @default(cuid()) + interactionId String + interaction IiosInteraction @relation(fields: [interactionId], references: [id], onDelete: Cascade) + actorId String? + actor IiosActorRef? @relation(fields: [actorId], references: [id]) + receiptKind IiosReceiptKind + occurredAt DateTime @default(now()) + providerReceiptRef String? + metadata Json? + + @@unique([interactionId, actorId, receiptKind]) + @@index([interactionId]) +} + +/// Per-(thread, actor) unread count + last-read marker. +model IiosUnreadCounter { + threadId String + thread IiosThread @relation(fields: [threadId], references: [id], onDelete: Cascade) + actorId String + actor IiosActorRef @relation(fields: [actorId], references: [id]) + unreadCount Int @default(0) + lastReadInteractionId String? + updatedAt DateTime @updatedAt + + @@id([threadId, actorId]) +}