mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 18:08:16 +00:00
Phase 1 of hospital onboarding & self-service plan
(docs/superpowers/plans/2026-04-06-hospital-onboarding-self-service.md).
Backend foundations to support the upcoming staff-portal Settings hub and
6-step setup wizard. No frontend in this phase.
New config services (mirroring ThemeService / WidgetConfigService):
- SetupStateService — tracks completion of 6 wizard steps; isWizardRequired()
drives the post-login redirect
- TelephonyConfigService — Ozonetel + Exotel + SIP, replaces 8 env vars,
seeds from env on first boot, masks secrets on GET,
'***masked***' sentinel on PUT means "keep existing"
- AiConfigService — provider, model, temperature, system prompt addendum;
API keys remain in env
New endpoints under /api/config:
- GET /api/config/setup-state returns state + wizardRequired flag
- PUT /api/config/setup-state/steps/:step mark step complete/incomplete
- POST /api/config/setup-state/dismiss dismiss wizard
- POST /api/config/setup-state/reset
- GET /api/config/telephony masked
- PUT /api/config/telephony
- POST /api/config/telephony/reset
- GET /api/config/ai
- PUT /api/config/ai
- POST /api/config/ai/reset
ConfigThemeModule is now @Global() so the new sidecar config services are
injectable from AuthModule, OzonetelAgentModule, MaintModule without creating
a circular dependency (ConfigThemeModule already imports AuthModule for
SessionService).
Migrated 11 env-var read sites to use the new services:
- ozonetel-agent.service: exotel API + ozonetel did/sipId via read-through getters
- ozonetel-agent.controller: defaultAgentId/Password/SipId via getters
- kookoo-ivr.controller: sipId/callerId via getters
- auth.controller: OZONETEL_AGENT_PASSWORD (login + logout)
- agent-config.service: sipDomain/wsPort/campaignName via getters
- maint.controller: forceReady + unlockAgent
- ai-provider: createAiModel and isAiConfigured refactored to pure factories
taking AiProviderOpts; no more ConfigService dependency
- widget-chat.service, recordings.service, ai-enrichment.service,
ai-chat.controller, ai-insight.consumer, call-assist.service: each builds
the AI model from AiConfigService.getConfig() + ConfigService API keys
Hot-reload guarantee: every consumer reads via a getter or builds per-call,
so admin updates take effect without sidecar restart. WidgetChatService
specifically rebuilds the model on each streamReply().
Bug fix bundled: dropped widget.json.hospitalName field (the original
duplicate that started this whole thread). WidgetConfigService now reads
brand.hospitalName from ThemeService at the 2 generateKey call sites.
Single source of truth for hospital name is workspace branding.
First-boot env seeding: TelephonyConfigService and AiConfigService both
copy their respective env vars into a fresh data/*.json on onModuleInit if
the file doesn't exist. Existing deployments auto-migrate without manual
intervention.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
47 lines
1.6 KiB
TypeScript
47 lines
1.6 KiB
TypeScript
// Shape of the website-widget configuration, stored in data/widget.json.
|
|
// Mirrors the theme config pattern — file-backed, versioned, admin-editable.
|
|
export type WidgetConfig = {
|
|
// Master feature flag. When false, the widget does not render anywhere.
|
|
enabled: boolean;
|
|
|
|
// HMAC-signed site key the embed script passes as data-key. Auto-generated
|
|
// on first boot if empty. Rotate via POST /api/config/widget/rotate-key.
|
|
key: string;
|
|
|
|
// Stable site identifier derived from the key. Used for Redis lookup and
|
|
// revocation. Populated alongside `key`.
|
|
siteId: string;
|
|
|
|
// Public base URL where widget.js is hosted. Typically the sidecar host.
|
|
// If empty, the embed page falls back to its own VITE_API_URL at fetch time.
|
|
url: string;
|
|
|
|
// Origin allowlist. Empty array means any origin is accepted (test mode).
|
|
// Set tight values in production: ['https://hospital.com'].
|
|
allowedOrigins: string[];
|
|
|
|
// Embed toggles — where the widget should render. Kept as an object so we
|
|
// can add other surfaces (public landing page, portal, etc.) without a
|
|
// breaking schema change.
|
|
embed: {
|
|
// Show on the staff login page. Useful for testing without a public
|
|
// landing page; turn off in production.
|
|
loginPage: boolean;
|
|
};
|
|
|
|
// Bookkeeping — incremented on every update, like the theme config.
|
|
version?: number;
|
|
updatedAt?: string;
|
|
};
|
|
|
|
export const DEFAULT_WIDGET_CONFIG: WidgetConfig = {
|
|
enabled: true,
|
|
key: '',
|
|
siteId: '',
|
|
url: '',
|
|
allowedOrigins: [],
|
|
embed: {
|
|
loginPage: true,
|
|
},
|
|
};
|