Files
curriculum-project-hub/hub/src/org/status.ts
T

63 lines
2.2 KiB
TypeScript

import type { OrganizationStatus, Prisma } from "@prisma/client";
export class InactiveOrganizationError extends Error {
readonly organizationId: string;
readonly status: Exclude<OrganizationStatus, "ACTIVE">;
constructor(organizationId: string, status: Exclude<OrganizationStatus, "ACTIVE">) {
super(`organization ${organizationId} is ${status}; customer operations require ACTIVE`);
this.name = "InactiveOrganizationError";
this.organizationId = organizationId;
this.status = status;
}
}
export function inactiveOrganizationReason(
organizationId: string,
status: OrganizationStatus,
): string | undefined {
if (status === "ACTIVE") return undefined;
return `organization ${organizationId} is ${status}; customer operations require ACTIVE`;
}
export function requireActiveOrganizationStatus(
organizationId: string,
status: OrganizationStatus,
): void {
if (status !== "ACTIVE") throw new InactiveOrganizationError(organizationId, status);
}
/**
* Lock the Organization row for a customer operation and verify ACTIVE.
* PostgreSQL UPDATE takes a conflicting row lock, so lifecycle transitions
* serialize with the entire transaction that called this function.
*/
export async function lockActiveOrganization(
prisma: Prisma.TransactionClient,
organizationId: string,
): Promise<void> {
const rows = await prisma.$queryRaw<Array<{ readonly id: string; readonly status: OrganizationStatus }>>`
SELECT "id", "status"::text AS "status"
FROM "Organization"
WHERE "id" = ${organizationId}
FOR SHARE
`;
const organization = rows[0];
if (organization === undefined) throw new Error(`organization not found: ${organizationId}`);
requireActiveOrganizationStatus(organization.id, organization.status);
}
/** Resolve a Project's tenant inside the transaction, then take the lifecycle lock. */
export async function lockActiveProjectOrganization(
prisma: Prisma.TransactionClient,
projectId: string,
): Promise<string> {
const project = await prisma.project.findUnique({
where: { id: projectId },
select: { organizationId: true },
});
if (project === null) throw new Error(`project not found: ${projectId}`);
await lockActiveOrganization(prisma, project.organizationId);
return project.organizationId;
}