import { Body, Controller, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { GatewayInboundAuthDto, GatewayInboundSubmitDto, GatewayPendingDeliveryQueryDto, GatewayDownstreamAcknowledgedDto, GatewayDownstreamFailureType, GatewayDownstreamSentDto, GatewayDownstreamRecoveryStatusDto, GatewayReceiptEventDto, GatewaySubmitDeadLetterDto, GatewaySubmitResultDto, GatewayUplinkEventDto, SendChainService, } from './send-chain.service'; import { GatewayDownstreamConnectionEventDto, SmsConfigService } from '../sms-config/sms-config.service'; import { ProtocolLogsService, type ProtocolLogInput } from '../protocol-logs/protocol-logs.service'; @ApiTags('gateway-events') @Controller('gateway/events') export class GatewayEventsController { constructor( private readonly sendChain: SendChainService, private readonly smsConfig: SmsConfigService, private readonly protocolLogs: ProtocolLogsService, ) {} @Post('submit-result') submitResult(@Body() body: GatewaySubmitResultDto) { return this.trackGatewayEvent('submit_resp', body, () => this.sendChain.handleSubmitResult(body)); } @Post('receipt') receipt(@Body() body: GatewayReceiptEventDto) { return this.trackGatewayEvent('deliver_receipt', body, () => this.sendChain.handleReceipt(body)); } @Post('uplink') uplink(@Body() body: GatewayUplinkEventDto) { return this.trackGatewayEvent('deliver_uplink', body, () => this.sendChain.handleUplink(body)); } @Post('dead-letter') deadLetter(@Body() body: GatewaySubmitDeadLetterDto) { return this.sendChain.recordGatewaySubmitDeadLetter(body); } @Post('inbound/authenticate') authenticateInbound(@Body() body: GatewayInboundAuthDto) { return this.trackGatewayEvent('connect', body, () => this.sendChain.authenticateInboundApplication(body), 'client_to_platform'); } @Post('inbound/submit') submitInbound(@Body() body: GatewayInboundSubmitDto) { return this.trackGatewayEvent('submit', body, () => this.sendChain.submitInboundMessage(body), 'client_to_platform'); } @Post('inbound/connection') inboundConnection(@Body() body: GatewayDownstreamConnectionEventDto) { return this.smsConfig.recordDownstreamConnectionEvent(body); } @Post('downstream/pending') pendingDownstream(@Body() body: GatewayPendingDeliveryQueryDto) { return this.sendChain.listPendingDownstreamDeliveries(body); } @Post('downstream/delivered') downstreamDelivered(@Body() body: { id: string }) { return this.sendChain.markDownstreamDeliveryDelivered(body.id); } @Post('downstream/sent') downstreamSent(@Body() body: GatewayDownstreamSentDto) { return this.sendChain.markDownstreamDeliverySent(body); } @Post('downstream/acknowledged') downstreamAcknowledged(@Body() body: GatewayDownstreamAcknowledgedDto) { return this.sendChain.acknowledgeDownstreamDelivery(body); } @Post('downstream/failed') downstreamFailed(@Body() body: { id: string; errorMessage?: string; failureType?: GatewayDownstreamFailureType }) { return this.sendChain.markDownstreamDeliveryFailed(body.id, body.errorMessage, body.failureType); } @Post('downstream/recovery-status') downstreamRecoveryStatus(@Body() body: GatewayDownstreamRecoveryStatusDto) { return this.sendChain.recordGatewayDownstreamRecoveryStatus(body); } private async trackGatewayEvent( eventType: string, body: object, action: () => Promise | T, direction: ProtocolLogInput['direction'] = 'channel_to_platform', ) { const startedAt = Date.now(); const value = body as Record; const common: Omit = { protocol: 'cmpp', direction, eventType, tenantId: value.tenantId as string, applicationId: value.applicationId as string, channelId: value.channelId as string, account: (value.account ?? value.loginAccount) as string, messageId: (value.messageId ?? value.platformMessageId) as string, gatewayMessageId: (value.gatewayMessageId ?? value.msgId ?? value.upstreamMessageId) as string, phone: (value.phoneNumber ?? value.srcTerminalId ?? value.destinationId) as string, resultCode: (value.result ?? value.status ?? value.stat) as string, detail: { sequenceId: value.sequenceId, connectionId: value.connectionId }, }; this.protocolLogs.record({ ...common, status: 'received', durationMs: 0 }); try { const result = await action(); this.protocolLogs.record({ ...common, status: 'success', durationMs: Date.now() - startedAt, }); return result; } catch (error) { this.protocolLogs.record({ ...common, status: 'failed', durationMs: Date.now() - startedAt, detail: { error: error instanceof Error ? error.message : 'unknown error' }, }); throw error; } } }