evOo-local/packages/db/src/external-conversations.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

103 lines
3.3 KiB
TypeScript

import {
type Actor,
AutomatedSenderPoliciesSchema,
type ExternalConversation,
type ExternalConversationPolicy,
} from "@rakazo/contracts";
import type { PrismaClient } from "./client.js";
import { IsolationError } from "./scope.js";
import { previewFromBlocks } from "./thread-listing.js";
export function createExternalConversationRepos(prisma: PrismaClient) {
return {
async listForSpaces(actor: Actor, spaceIds: string[]): Promise<ExternalConversation[]> {
if (spaceIds.length === 0) return [];
const conversations = await prisma.externalConversation.findMany({
where: {
spaceId: { in: spaceIds },
userId: actor.userId,
bot: { archivedAt: null },
thread: { isNot: null },
},
select: {
id: true,
spaceId: true,
botId: true,
provider: true,
displayName: true,
participantNames: true,
teamChatAmbientEnabled: true,
teamChatRules: true,
automatedSenderPolicies: true,
updatedAt: true,
messages: {
where: { senderIsBot: true },
orderBy: { createdAt: "desc" },
take: 100,
select: { senderId: true, senderName: true },
},
thread: {
select: {
id: true,
unread: true,
messages: {
orderBy: { seq: "desc" },
take: 1,
select: { blocks: true },
},
},
},
},
orderBy: [{ updatedAt: "desc" }, { id: "asc" }],
});
return conversations.map((conversation) => {
if (!conversation.thread) {
throw new IsolationError("External conversation is missing its thread");
}
return {
id: conversation.id,
spaceId: conversation.spaceId,
botId: conversation.botId,
provider: conversation.provider,
displayName: conversation.displayName,
participantNames: conversation.participantNames,
teamChatAmbientEnabled: conversation.teamChatAmbientEnabled,
teamChatRules: conversation.teamChatRules,
automatedSenderPolicies: AutomatedSenderPoliciesSchema.parse(
conversation.automatedSenderPolicies,
),
automatedSenders: [
...new Map(
conversation.messages.map(({ senderId, senderName }) => [
senderId,
{ id: senderId, name: senderName },
]),
).values(),
],
threadId: conversation.thread.id,
preview: previewFromBlocks(conversation.thread.messages[0]?.blocks),
unread: conversation.thread.unread,
updatedAt: conversation.updatedAt.toISOString(),
};
});
},
async updatePolicy(
actor: Actor,
externalConversationId: string,
policy: ExternalConversationPolicy,
): Promise<ExternalConversationPolicy> {
const result = await prisma.externalConversation.updateMany({
where: {
id: externalConversationId,
spaceId: actor.spaceId,
userId: actor.userId,
},
data: policy,
});
if (result.count !== 1) throw new IsolationError();
return policy;
},
};
}