import type { CardBlock, MessageBlock } from "@rakazo/contracts"; import type { Prisma } from "./client.js"; import { appendEventInTransaction } from "./events.js"; /** * Change one card in a bot message and tell the thread. The thread row is locked so two taps on * the same card apply one after the other. `change` returns null to leave the card as it is; * the result is null when the message, the card or a change is missing. */ export async function updateCardInTransaction( tx: Prisma.TransactionClient, input: { threadId: string; messageId: string; cardId: string }, change: (card: CardBlock) => CardBlock | null, ): Promise<{ seq: number; card: CardBlock } | null> { await tx.$queryRaw`SELECT id FROM threads WHERE id = ${input.threadId} FOR UPDATE`; const message = await tx.message.findFirst({ where: { id: input.messageId, threadId: input.threadId, role: "bot" }, select: { id: true, blocks: true, botId: true, thread: { select: { spaceId: true } } }, }); if (!message?.botId) return null; const blocks = (Array.isArray(message.blocks) ? message.blocks : []) as MessageBlock[]; const index = blocks.findIndex((block) => block.kind === "card" && block.id === input.cardId); const card = blocks[index]; if (card?.kind !== "card") return null; const next = change(card); if (!next) return null; const updated = blocks.map((block, position) => (position === index ? next : block)); await tx.message.update({ where: { id: message.id }, data: { blocks: updated } }); const event = await appendEventInTransaction(tx, { spaceId: message.thread.spaceId, threadId: input.threadId, botId: message.botId, type: "thread.message.updated", payload: { messageId: message.id, role: "bot", blocks: updated }, }); return { seq: event.seq, card: next }; }