/** * Residential module gate. Port-scoped on/off switch for the entire * Residential surface (sidebar "Residential" section, the * /residential/clients + /residential/interests pages, the admin * residential-stages page, the global-search residential buckets, and * the public residential-inquiry intake endpoint). * * Defaults to ENABLED so existing ports keep the feature on deploy — * residential is in active use, unlike Tenancies / Invoices which are * opt-in. When an admin turns it off: * - the sidebar "Residential" section + mobile "Residential" entry * disappear via the port-resolved residentialModuleByPort prop * - the /residential/* and admin/residential-stages routes render a * "module disabled" page instead of the real content, so bookmarks * land somewhere meaningful and direct API hits are rejected at the * layout boundary * - the public /api/public/residential-inquiries endpoint hard-fails * so a disabled port stops accepting residential leads it can't see * - previously-recorded residential clients/interests are preserved * (no destructive cleanup) so re-enabling restores everything * * Mirrors the Tenancies / Expenses / Invoices module-gate pattern. */ import { and, eq, isNull, or } from 'drizzle-orm'; import { db } from '@/lib/db'; import { systemSettings } from '@/lib/db/schema/system'; import { NotFoundError } from '@/lib/errors'; const SETTING_KEY = 'residential_module_enabled'; /** * Resolve whether the Residential module is currently active for the * given port. Reads from `system_settings.residential_module_enabled` * (port-scoped row first, then global row, then registry default = true). * * Defaulting to enabled mirrors how residential behaved before the * toggle existed: deploying this change to a port that has never * configured the setting leaves the feature visible. */ export async function isResidentialModuleEnabled(portId: string): Promise { const settingRow = await db .select({ value: systemSettings.value }) .from(systemSettings) .where( and( eq(systemSettings.key, SETTING_KEY), or(eq(systemSettings.portId, portId), isNull(systemSettings.portId)), ), ) .limit(1); // Stored JSONB shape is the raw boolean (`true` / `false`); the admin- // settings PUT handler writes the primitive directly. Only an explicit // `false` disables — a missing row / true / unrecognized shape means // enabled, matching the registry default. if (settingRow[0]?.value === false) return false; return true; } /** * Admin-driven enable. Idempotent — safe to call when already enabled * (UPSERT on key+port). */ export async function enableResidentialModule(portId: string): Promise { await db .insert(systemSettings) .values({ key: SETTING_KEY, portId, value: true, }) .onConflictDoUpdate({ target: [systemSettings.key, systemSettings.portId], set: { value: true, updatedAt: new Date() }, }); } /** * Admin-driven disable. Does NOT delete any residential rows — those * remain in the database and surface again when the module is re-enabled. * The frontend warns the operator about the row count before calling this. */ export async function disableResidentialModule(portId: string): Promise { await db .insert(systemSettings) .values({ key: SETTING_KEY, portId, value: false, }) .onConflictDoUpdate({ target: [systemSettings.key, systemSettings.portId], set: { value: false, updatedAt: new Date() }, }); } /** * Convenience throw-on-disabled helper for route handlers and services * that should hard-fail (404 / NotFound) when the module is off. */ export async function assertResidentialModuleEnabled(portId: string): Promise { const enabled = await isResidentialModuleEnabled(portId); if (!enabled) { throw new NotFoundError('Residential module is not enabled for this port.'); } }