96d5c10537
Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from 49da3a4 on 2026-09-13. Licensed under the Functional Source License (FSL-1.1-ALv2).
57 lines
2.9 KiB
TypeScript
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 },
|
|
};
|
|
}
|