import { ORPCError } from "@orpc/server"; import type { Actor, MainConversationRef } from "@rakazo/contracts"; import { hasMentionToken } from "@rakazo/core"; import { ensureMainConversation, findMainBotId, joinMainConversation, type PrismaClient, } from "@rakazo/db"; /** * Where the main conversation lives, repaired or created on the way. Null until onboarding has * created the main bot: reading navigation must never create a bot by itself. */ export async function loadMainConversationRef( prisma: PrismaClient, userId: string, ): Promise { if (!(await findMainBotId(prisma, userId))) return null; const conversation = await ensureMainConversation(prisma, userId); return { botId: conversation.leadBotId, groupId: conversation.groupId, spaceId: conversation.spaceId, }; } export function assertNotMainBot(bot: { kind: string }, message: string) { if (bot.kind === "main") throw new ORPCError("BAD_REQUEST", { message }); } /** Unknown or foreign groups fall through so the caller's own lookup reports them as before. */ export async function assertNotMainConversation( prisma: PrismaClient, actor: Pick, groupId: string, message: string, ) { const group = await prisma.chatGroup.findFirst({ where: { id: groupId, spaceId: actor.spaceId, userId: actor.userId }, select: { kind: true }, }); if (group?.kind === "main") throw new ORPCError("BAD_REQUEST", { message }); } /** A main conversation carries its main bot's name. */ export async function renameMainConversation( prisma: PrismaClient, main: { userId: string; spaceId: string }, name: string, ) { await prisma.chatGroup.updateMany({ where: { userId: main.userId, spaceId: main.spaceId, kind: "main" }, data: { name }, }); } /** * Same-space bots the user addresses in a main conversation, by `@Name` or a mention chip, * join it first, so the member mention rules route the message to them. */ export async function joinAddressedBots( prisma: PrismaClient, actor: Pick, input: { memberBotIds: string[]; text: string; botMentionIds: string[] }, ) { const candidates = await prisma.bot.findMany({ where: { userId: actor.userId, spaceId: actor.spaceId, archivedAt: null, id: { notIn: input.memberBotIds }, }, select: { id: true, name: true }, }); const addressed = candidates .filter((bot) => input.botMentionIds.includes(bot.id) || hasMentionToken(input.text, bot.name)) .map((bot) => bot.id); if (addressed.length === 0) return; // Subscribers pick up the group.updated event with the send that follows. await joinMainConversation(prisma, { userId: actor.userId, spaceId: actor.spaceId, botIds: addressed, }); }