mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-12 02:18:18 +00:00
feat: team module, multi-stage Dockerfile, doctor utils, AI config overhaul
- Team module: POST /api/team/members (in-place employee creation with temp password + Redis cache), PUT /api/team/members/:id, GET temp password endpoint. Uses signUpInWorkspace — no email invites. - Dockerfile: rewritten as multi-stage build (builder + runtime) so native modules compile for target arch. Fixes darwin→linux crash. - .dockerignore: exclude dist, node_modules, .env, .git, data/ - package-lock.json: regenerated against public npmjs.org (was pointing at localhost:4873 Verdaccio — broke docker builds) - Doctor utils: shared DOCTOR_VISIT_SLOTS_FRAGMENT + normalizeDoctors helper for visit-slot-aware queries across 6 consumers - AI config: full admin CRUD (GET/PUT/POST reset), workspace-scoped setup-state with workspace ID isolation, AI prompt defaults overhaul - Agent config: camelCase field fix for SDK-synced workspaces - Session service: workspace-scoped Redis key prefixing for setup state - Recordings/supervisor/widget services: updated to use doctor-utils shared fragments instead of inline visitingHours queries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,22 +37,29 @@ export class AgentConfigService {
|
||||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
// Note: platform GraphQL field names are derived from the SDK
|
||||
// `label`, not `name` — so the filter/column is
|
||||
// `workspaceMemberId` and the SIP fields are camelCase. The
|
||||
// legacy staging workspace was synced from an older SDK that
|
||||
// exposed `wsmemberId`/`ozonetelagentid`/etc., but any fresh
|
||||
// sync (and all new hospitals going forward) uses these
|
||||
// label-derived names. Re-sync staging if it drifts.
|
||||
const data = await this.platform.query<any>(
|
||||
`{ agents(first: 1, filter: { wsmemberId: { eq: "${memberId}" } }) { edges { node {
|
||||
id ozonetelagentid sipextension sippassword campaignname
|
||||
`{ agents(first: 1, filter: { workspaceMemberId: { eq: "${memberId}" } }) { edges { node {
|
||||
id ozonetelAgentId sipExtension sipPassword campaignName
|
||||
} } } }`,
|
||||
);
|
||||
|
||||
const node = data?.agents?.edges?.[0]?.node;
|
||||
if (!node || !node.ozonetelagentid || !node.sipextension) return null;
|
||||
if (!node || !node.ozonetelAgentId || !node.sipExtension) return null;
|
||||
|
||||
const agentConfig: AgentConfig = {
|
||||
id: node.id,
|
||||
ozonetelAgentId: node.ozonetelagentid,
|
||||
sipExtension: node.sipextension,
|
||||
sipPassword: node.sippassword ?? node.sipextension,
|
||||
campaignName: node.campaignname ?? this.defaultCampaignName,
|
||||
sipUri: `sip:${node.sipextension}@${this.sipDomain}`,
|
||||
ozonetelAgentId: node.ozonetelAgentId,
|
||||
sipExtension: node.sipExtension,
|
||||
sipPassword: node.sipPassword ?? node.sipExtension,
|
||||
campaignName: node.campaignName ?? this.defaultCampaignName,
|
||||
sipUri: `sip:${node.sipExtension}@${this.sipDomain}`,
|
||||
sipWsServer: `wss://${this.sipDomain}:${this.sipWsPort}`,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const SESSION_TTL = 3600; // 1 hour
|
||||
|
||||
@Injectable()
|
||||
export class SessionService implements OnModuleInit {
|
||||
export class SessionService {
|
||||
private readonly logger = new Logger(SessionService.name);
|
||||
private redis: Redis;
|
||||
private readonly redis: Redis;
|
||||
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
onModuleInit() {
|
||||
// Redis client is constructed eagerly (not in onModuleInit) so
|
||||
// other services can call cache methods from THEIR onModuleInit
|
||||
// hooks. NestJS instantiates all providers before running any
|
||||
// onModuleInit callback, so the client is guaranteed ready even
|
||||
// when an earlier-firing module's init path touches the cache
|
||||
// (e.g. WidgetConfigService → WidgetKeysService → setCachePersistent).
|
||||
constructor(private config: ConfigService) {
|
||||
const url = this.config.get<string>('redis.url', 'redis://localhost:6379');
|
||||
this.redis = new Redis(url);
|
||||
this.redis = new Redis(url, { lazyConnect: false });
|
||||
this.redis.on('connect', () => this.logger.log('Redis connected'));
|
||||
this.redis.on('error', (err) => this.logger.error(`Redis error: ${err.message}`));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user