Files
ThreadCount 0910bc32c1 ThreadCount Community edition
Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from 38e16eb on 2026-09-15. Licensed under the Functional Source License (FSL-1.1-ALv2).
2026-09-16 07:57:54 +10:00

57 lines
2.9 KiB
TypeScript

import { prisma } from "./db";
import { COMMUNITY } from "./edition";
/* The two platform switches: are sign-ups open, is the demo in service.
*
* These used to be environment variables, and flipping one meant editing the secrets file and
* restarting. Now they live in one database row ThreadCount's own administration can change, and
* the environment is an override rather than the source: SIGNUPS_DISABLED=1 or DEMO_DISABLED=1
* closes the door whatever the row says, so a box can still be locked down by hand — and nothing
* can reopen it until the variable goes, which is the point of an override.
*
* A missing row means open. A database error also means open here rather than closed: the only
* callers are the sign-up route, the sign-in page, the demo page and the demo entry, all of which
* fail on their own if the database is really down, and a transient error must not turn the
* public site into "sign-ups are closed" for the length of a blip. */
export type Switches = {
signupsOpen: boolean;
demoOpen: boolean;
/** Plans are live: sign-ups land on Hosted Small and Settings shows the Plan tab. Until then
* every new facility is grandfathered, because the page still says free. PLANS_LIVE=1 turns it
* on from the box whatever the row says — the one switch whose override opens rather than closes,
* because "on" is the state that costs a customer something and must be deliberate either way. */
plansLive: boolean;
/** True when the environment forced the switch. */
signupsByEnv: boolean;
demoByEnv: boolean;
plansByEnv: boolean;
/** What the row says, before the environment has its say. */
row: { signupsDisabled: boolean; demoDisabled: boolean; plansLive: boolean; updatedAt: Date | null };
};
export const SWITCH_ROW = "platform";
export async function switches(): Promise<Switches> {
const signupsByEnv = process.env.SIGNUPS_DISABLED === "1";
// A Community instance has no demo facility and no plans, whatever its row says.
const demoByEnv = process.env.DEMO_DISABLED === "1" || COMMUNITY;
const plansByEnv = process.env.PLANS_LIVE === "1" && !COMMUNITY;
let row: { signupsDisabled: boolean; demoDisabled: boolean; plansLive: boolean; updatedAt: Date } | null = null;
try {
row = await prisma.platformSwitch.findUnique({ where: { id: SWITCH_ROW } });
} catch (e) {
console.error("[switches] read failed — treating as open:", (e as Error).message);
}
return {
signupsOpen: !signupsByEnv && !row?.signupsDisabled,
demoOpen: !demoByEnv && !row?.demoDisabled,
// A read failure means "not live": the failure mode is a facility grandfathered by mistake,
// which is a gift, never a room capped or a tab shown by mistake.
plansLive: !COMMUNITY && (plansByEnv || !!row?.plansLive),
signupsByEnv,
demoByEnv,
plansByEnv,
row: { signupsDisabled: !!row?.signupsDisabled, demoDisabled: !!row?.demoDisabled, plansLive: !!row?.plansLive, updatedAt: row?.updatedAt ?? null },
};
}