diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0cb46c4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + whisper-web: + build: . + ports: + - "3000:3000" + environment: + # Runtime feature flags (Decisions.md D-015). + AUTH_ENABLED: "true" # set to "false" to disable login/register + volumes: + # Persists the SQLite database (users + sessions). + - whisper-data:/app/data + +volumes: + whisper-data: diff --git a/src/server/flags.ts b/src/server/flags.ts new file mode 100644 index 0000000..571678a --- /dev/null +++ b/src/server/flags.ts @@ -0,0 +1,23 @@ +/** + * Server feature flags (Decisions.md D-015) — read from the process + * environment at startup, so one Docker image serves every configuration + * (e.g. `AUTH_ENABLED=false` in docker-compose.yml). The client is never + * rebuilt for a flag: it discovers auth availability at runtime from the + * `GET /api/auth/me` probe it already makes (404 → feature off). + */ + +/** + * Interpret an env-var flag. Unset or empty → `fallback`; + * "false", "0", "no", "off" (any case) → false; anything else → true. + */ +export function envFlag(value: string | undefined, fallback: boolean): boolean { + if (value === undefined || value === "") return fallback; + return !["false", "0", "no", "off"].includes(value.toLowerCase()); +} + +/** + * Login/register (M4): the /api/auth/* endpoints, and thereby the Sign in + * UI (src/components/AuthForms.tsx renders nothing when /me 404s). + * Enabled by default; set AUTH_ENABLED=false to disable. + */ +export const AUTH_ENABLED = envFlag(process.env.AUTH_ENABLED, true); diff --git a/tests/flags.test.ts b/tests/flags.test.ts new file mode 100644 index 0000000..96d9951 --- /dev/null +++ b/tests/flags.test.ts @@ -0,0 +1,21 @@ +import { expect, test } from "bun:test"; +import { envFlag } from "../src/server/flags"; + +test("unset or empty falls back", () => { + expect(envFlag(undefined, true)).toBe(true); + expect(envFlag(undefined, false)).toBe(false); + expect(envFlag("", true)).toBe(true); + expect(envFlag("", false)).toBe(false); +}); + +test("disabling spellings, any case", () => { + for (const value of ["false", "FALSE", "False", "0", "no", "off", "OFF"]) { + expect(envFlag(value, true)).toBe(false); + } +}); + +test("anything else enables", () => { + for (const value of ["true", "1", "yes", "on", "enabled"]) { + expect(envFlag(value, false)).toBe(true); + } +});