import { notifyToolReviewChange } from "../services/tool-review-work-notifications.js"; import { writeApprovalChange } from "../services/approval-work-notifications.js"; import { and, eq, inArray } from "drizzle-orm"; import { approvals, type Db } from "@paperclipai/db"; import { notFound, unprocessable } from "../errors.js"; export const resolvableApprovalStatuses = ["pending", "revision_requested"]; export function approvalResolutionRecords(db: Db) { const canResolveStatuses = new Set(resolvableApprovalStatuses); const resolvableStatuses = resolvableApprovalStatuses; type ApprovalRecord = typeof approvals.$inferSelect; type ResolutionResult = { approval: ApprovalRecord; applied: boolean }; async function getExistingApproval(id: string, database: Db = db) { const existing = await database .select() .from(approvals) .where(eq(approvals.id, id)) .then((rows) => rows[0] ?? null); if (!existing) throw notFound("Approval not found"); return existing; } async function resolveApproval( id: string, targetStatus: "approved" | "rejected", decidedByUserId: string, decisionNote: string | null | undefined, database: Db = db, ): Promise { const existing = await getExistingApproval(id, database); if (!canResolveStatuses.has(existing.status)) { if (existing.status === targetStatus) { return { approval: existing, applied: false }; } throw unprocessable( `Only pending or revision requested approvals can be ${targetStatus === "approved" ? "approved" : "rejected"}`, ); } const now = new Date(); const updated = await writeApprovalChange(database, existing.companyId, async tx => { await notifyToolReviewChange(tx, existing.companyId); return tx.update(approvals) .set({ status: targetStatus, decidedByUserId, decisionNote: decisionNote ?? null, decidedAt: now, updatedAt: now, }) .where(and(eq(approvals.id, id), inArray(approvals.status, resolvableStatuses))) .returning() .then((rows) => rows[0] ?? null); }, { approvalId: id }); if (updated) { return { approval: updated, applied: true }; } const latest = await getExistingApproval(id, database); if (latest.status === targetStatus) { return { approval: latest, applied: false }; } throw unprocessable( `Only pending or revision requested approvals can be ${targetStatus === "approved" ? "approved" : "rejected"}`, ); } return { getExistingApproval, resolveApproval }; }