mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 21:49:18 +01:00
70 lines
2.4 KiB
TypeScript
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 };
|
|
}
|