Files
PaperClipAI/server/src/lib/approval-records.ts
T

70 lines
2.4 KiB
TypeScript

import { writeApprovalChange } from "../services/retention-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<ResolutionResult> {
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, tx => 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));
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 };
}