mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 18:08:16 +00:00
feat: multi-agent SIP with Redis session lockout
- SessionService: Redis-backed session lock/unlock/refresh with 1hr TTL - AgentConfigService: queries Agent entity, caches per-member config - Auth login: resolves agent config, locks Redis session, returns SIP credentials - Auth logout: unlocks Redis session, Ozonetel logout, clears cache - Auth heartbeat: refreshes Redis TTL every 5 minutes - Added ioredis dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
70
src/auth/agent-config.service.ts
Normal file
70
src/auth/agent-config.service.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
|
||||
|
||||
export type AgentConfig = {
|
||||
id: string;
|
||||
ozonetelAgentId: string;
|
||||
sipExtension: string;
|
||||
sipPassword: string;
|
||||
campaignName: string;
|
||||
sipUri: string;
|
||||
sipWsServer: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class AgentConfigService {
|
||||
private readonly logger = new Logger(AgentConfigService.name);
|
||||
private readonly cache = new Map<string, AgentConfig>();
|
||||
private readonly sipDomain: string;
|
||||
private readonly sipWsPort: string;
|
||||
|
||||
constructor(
|
||||
private platform: PlatformGraphqlService,
|
||||
private config: ConfigService,
|
||||
) {
|
||||
this.sipDomain = config.get<string>('sip.domain', 'blr-pub-rtc4.ozonetel.com');
|
||||
this.sipWsPort = config.get<string>('sip.wsPort', '444');
|
||||
}
|
||||
|
||||
async getByMemberId(memberId: string): Promise<AgentConfig | null> {
|
||||
const cached = this.cache.get(memberId);
|
||||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
const data = await this.platform.query<any>(
|
||||
`{ agents(first: 1, filter: { wsmemberId: { eq: "${memberId}" } }) { edges { node {
|
||||
id ozonetelagentid sipextension sippassword campaignname
|
||||
} } } }`,
|
||||
);
|
||||
|
||||
const node = data?.agents?.edges?.[0]?.node;
|
||||
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 ?? process.env.OZONETEL_CAMPAIGN_NAME ?? 'Inbound_918041763265',
|
||||
sipUri: `sip:${node.sipextension}@${this.sipDomain}`,
|
||||
sipWsServer: `wss://${this.sipDomain}:${this.sipWsPort}`,
|
||||
};
|
||||
|
||||
this.cache.set(memberId, agentConfig);
|
||||
this.logger.log(`Loaded agent config for member ${memberId}: ${agentConfig.ozonetelAgentId} / ${agentConfig.sipExtension}`);
|
||||
return agentConfig;
|
||||
} catch (err) {
|
||||
this.logger.warn(`Failed to fetch agent config: ${err}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getFromCache(memberId: string): AgentConfig | null {
|
||||
return this.cache.get(memberId) ?? null;
|
||||
}
|
||||
|
||||
clearCache(memberId: string): void {
|
||||
this.cache.delete(memberId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user