forked from EduCraft/curriculum-project-hub
52 lines
1.7 KiB
TypeScript
52 lines
1.7 KiB
TypeScript
import type { PrismaClient } from "@prisma/client";
|
|
|
|
export interface SiloOrganization {
|
|
readonly id: string;
|
|
readonly slug: string;
|
|
readonly name: string;
|
|
}
|
|
|
|
/**
|
|
* Read the deployment-pinned Organization identity. A Silo process must never
|
|
* infer its tenant from request data or from whichever row happens to exist.
|
|
*/
|
|
export function readSiloOrganizationId(
|
|
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
): string {
|
|
const organizationId = env["HUB_SILO_ORGANIZATION_ID"]?.trim();
|
|
if (organizationId === undefined || organizationId === "") {
|
|
throw new Error("HUB_SILO_ORGANIZATION_ID is required");
|
|
}
|
|
return organizationId;
|
|
}
|
|
|
|
/**
|
|
* Prove the database is a single-tenant Silo before accepting traffic.
|
|
* Archived rows count too: pointing a Silo process at a pooled or reused
|
|
* database is a deployment error, not a condition to paper over.
|
|
*/
|
|
export async function requireSiloOrganization(
|
|
prisma: PrismaClient,
|
|
organizationId: string,
|
|
): Promise<SiloOrganization> {
|
|
const [count, organization] = await Promise.all([
|
|
prisma.organization.count(),
|
|
prisma.organization.findUnique({
|
|
where: { id: organizationId },
|
|
select: { id: true, slug: true, name: true, status: true },
|
|
}),
|
|
]);
|
|
if (count !== 1) {
|
|
throw new Error(`Silo database must contain exactly one Organization; found ${count}`);
|
|
}
|
|
if (organization === null) {
|
|
throw new Error(
|
|
`Silo Organization mismatch: configured ${organizationId} is not the sole database Organization`,
|
|
);
|
|
}
|
|
if (organization.status !== "ACTIVE") {
|
|
throw new Error(`Silo Organization ${organizationId} is ${organization.status}`);
|
|
}
|
|
return { id: organization.id, slug: organization.slug, name: organization.name };
|
|
}
|