feat: add call events orchestrator with WebSocket gateway, wire Exotel → lookup → enrich → push flow

- CallEventsService orchestrates: Exotel webhook → lead lookup → AI enrichment → WebSocket push
- CallEventsGateway (Socket.IO /call-events namespace) with agent room registration and disposition handling
- EnrichedCallEvent/DispositionPayload types for frontend contract
- Disposition flow: creates Call record, updates lead status, logs lead activity
- Wired ExotelController to forward answered/ended events to CallEventsService
- forwardRef used to resolve circular dependency between gateway and service

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-17 09:08:57 +05:30
parent 3e0d9a4351
commit d488d551ed
7 changed files with 372 additions and 4 deletions

View File

@@ -1,12 +1,16 @@
import { Controller, Post, Body, Logger, HttpCode } from '@nestjs/common';
import { ExotelService } from './exotel.service';
import { CallEventsService } from '../call-events/call-events.service';
import type { ExotelWebhookPayload } from './exotel.types';
@Controller('webhooks/exotel')
export class ExotelController {
private readonly logger = new Logger(ExotelController.name);
constructor(private readonly exotelService: ExotelService) {}
constructor(
private readonly exotelService: ExotelService,
private readonly callEventsService: CallEventsService,
) {}
@Post('call-status')
@HttpCode(200)
@@ -15,9 +19,11 @@ export class ExotelController {
const callEvent = this.exotelService.parseWebhook(payload);
// TODO: Forward to CallEventsService (Task 4)
// For now, just log
this.logger.log(`Call event: ${JSON.stringify(callEvent)}`);
if (callEvent.eventType === 'answered') {
await this.callEventsService.handleIncomingCall(callEvent);
} else if (callEvent.eventType === 'ended') {
await this.callEventsService.handleCallEnded(callEvent);
}
return { status: 'received' };
}