import { Body, Controller, Get, Param, Post, Query, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { CurrentSessionUserId } from '../auth/current-session-user.decorator'; import { RequireRecentAuthentication } from '../auth/require-recent-authentication.decorator'; import { SendChainService } from '../send-chain/send-chain.service'; import { OperationsService } from './operations.service'; import { ProtocolLogsService } from '../protocol-logs/protocol-logs.service'; type DownloadResponse = { setHeader(name: string, value: number | string): void; send(content: string | Buffer): void; }; @ApiTags('operations') @Controller('admin/operations') export class AdminOperationsController { constructor( private readonly operations: OperationsService, private readonly sendChain: SendChainService, ) {} @Get('monitor') monitor(@Query('tenantId') tenantId?: string, @Query('channelId') channelId?: string) { return this.operations.monitor({ tenantId, channelId }); } @Get('task-progress') taskProgress(@Query('tenantId') tenantId?: string, @Query('status') status?: string) { return this.operations.listBatchTasks({ tenantId, status }); } @Get('messages') listMessages( @Query('tenantId') tenantId?: string, @Query('applicationId') applicationId?: string, @Query('channelId') channelId?: string, @Query('taskId') taskId?: string, @Query('messageId') messageId?: string, @Query('phoneNumber') phoneNumber?: string, @Query('contentKeyword') contentKeyword?: string, @Query('channelKeyword') channelKeyword?: string, @Query('carrier') carrier?: string, @Query('queuedAtFrom') queuedAtFrom?: string, @Query('queuedAtTo') queuedAtTo?: string, @Query('status') status?: string, @Query('hasDrainage') hasDrainage?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, @Query('monitorSnapshotId') monitorSnapshotId?: string, ) { return this.operations.listMessagesPage({ monitorSnapshotId, tenantId, applicationId, channelId, taskId, messageId, phoneNumber, contentKeyword, channelKeyword, carrier, queuedAtFrom, queuedAtTo, status, hasDrainage, page: Number(page), pageSize: Number(pageSize), }); } @Get('messages/export') async exportMessages( @Query('tenantId') tenantId: string | undefined, @Query('applicationId') applicationId: string | undefined, @Query('channelId') channelId: string | undefined, @Query('phoneNumber') phoneNumber: string | undefined, @Query('contentKeyword') contentKeyword: string | undefined, @Query('channelKeyword') channelKeyword: string | undefined, @Query('carrier') carrier: string | undefined, @Query('queuedAtFrom') queuedAtFrom: string | undefined, @Query('queuedAtTo') queuedAtTo: string | undefined, @Query('status') status: string | undefined, @Query('hasDrainage') hasDrainage: string | undefined, @Res() response: DownloadResponse, @Query('monitorSnapshotId') monitorSnapshotId?: string, ) { const exported = await this.operations.exportMessages({ monitorSnapshotId, tenantId, applicationId, channelId, phoneNumber, contentKeyword, channelKeyword, carrier, queuedAtFrom, queuedAtTo, status, hasDrainage, }); response.setHeader('Content-Type', 'text/csv; charset=utf-8'); response.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(exported.fileName)}`); response.send(`\uFEFF${exported.content}`); } @Get('messages/:id') getMessage(@Param('id') id: string) { return this.operations.getMessage(id); } @Get('message-segment-audits') messageSegmentAudits(@Query('messageId') messageId?: string, @Query('messageRecordId') messageRecordId?: string) { return this.operations.listMessageSegmentAudits({ messageId, messageRecordId }); } @Get('uplink-messages') listUplinkMessages( @Query('tenantId') tenantId?: string, @Query('channelId') channelId?: string, @Query('phoneNumber') phoneNumber?: string, @Query('keyword') keyword?: string, @Query('startTime') startTime?: string, @Query('endTime') endTime?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return page || pageSize ? this.operations.listUplinkMessagesPage({ tenantId, channelId, phoneNumber, keyword, startTime, endTime, page: Number(page), pageSize: Number(pageSize), }) : this.operations.listUplinkMessages({ tenantId, channelId, phoneNumber, keyword, startTime, endTime }); } @Post('uplink-messages/:id/claim') claimUplinkMatchCandidate(@Param('id') id: string, @Body() body: { candidateId?: string; operatorId?: string }) { return this.sendChain.claimUplinkMatchCandidate(id, String(body.candidateId ?? ''), body.operatorId); } @Get('dashboard') dashboard(@Query('tenantId') tenantId?: string) { return this.operations.dashboard({ tenantId }); } @Get('pending-audits') pendingAudits(@Query('tenantId') tenantId?: string) { return this.operations.pendingAudits(tenantId); } @Get('dashboard/statistics') dashboardStatistics(@Query('tenantId') tenantId?: string) { return this.operations.dashboard({ tenantId }); } @Get('statistics') statistics(@Query('tenantId') tenantId?: string, @Query('groupBy') groupBy?: string) { return this.operations.statistics({ tenantId, groupBy }); } @Get('send-quality') sendQuality(@Query('date') date?: string) { return this.operations.sendQuality(date); } @Get('signature-quality') signatureQuality( @Query('date') date?: string, @Query('keyword') keyword?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.operations.signatureQuality({ date, keyword, page: page === undefined ? 1 : Number(page), pageSize: pageSize === undefined ? 25 : Number(pageSize), }); } @Get('audit-logs') auditLogs( @Query('tenantId') tenantId?: string, @Query('userId') userId?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.operations.auditLogs({ tenantId, userId, page: Number(page), pageSize: Number(pageSize) }); } @Get('audit-summary') auditSummary(@Query('tenantId') tenantId?: string) { return this.operations.auditSummary({ tenantId }); } @Get('trace') trace( @Query('tenantId') tenantId?: string, @Query('applicationId') applicationId?: string, @Query('channelId') channelId?: string, @Query('taskId') taskId?: string, @Query('phoneNumber') phoneNumber?: string, @Query('messageId') messageId?: string, ) { return this.operations.trace({ tenantId, applicationId, channelId, taskId, phoneNumber, messageId }); } @Get('reconciliation') reconciliation(@Query('tenantId') tenantId?: string, @Query('taskId') taskId?: string) { return this.operations.reconciliation({ tenantId, taskId }); } @Get('gateway-submit-dead-letters') gatewaySubmitDeadLetters( @Query('tenantId') tenantId?: string, @Query('applicationId') applicationId?: string, @Query('channelId') channelId?: string, @Query('status') status?: string, @Query('keyword') keyword?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.operations.listGatewaySubmitDeadLetters({ tenantId, applicationId, channelId, status, keyword, page: Number(page), pageSize: Number(pageSize), }); } @Post('gateway-submit-dead-letters/:id/requeue') @RequireRecentAuthentication() requeueGatewaySubmitDeadLetter( @Param('id') id: string, @Body() body: { confirmedNotSubmitted?: boolean; reason?: string }, @CurrentSessionUserId() operatorId?: string, ) { return this.sendChain.requeueGatewaySubmitDeadLetter(id, { ...body, operatorId }); } @Post('gateway-submit-dead-letters/:id/resolve') resolveGatewaySubmitDeadLetter(@Param('id') id: string, @CurrentSessionUserId() operatorId?: string) { return this.sendChain.resolveGatewaySubmitDeadLetter(id, operatorId); } @Get('receipt-anomalies') receiptAnomalies( @Query('tenantId') tenantId?: string, @Query('applicationId') applicationId?: string, @Query('channelId') channelId?: string, @Query('anomalyType') anomalyType?: string, @Query('status') status?: string, @Query('keyword') keyword?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.operations.listReceiptAnomalies({ tenantId, applicationId, channelId, anomalyType, status, keyword, page: Number(page), pageSize: Number(pageSize), }); } @Get('downstream-deliveries') downstreamDeliveries( @Query('tenantId') tenantId?: string, @Query('applicationId') applicationId?: string, @Query('deliveryType') deliveryType?: string, @Query('createdAtFrom') createdAtFrom?: string, @Query('createdAtTo') createdAtTo?: string, @Query('status') status?: string, @Query('keyword') keyword?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.operations.listDownstreamDeliveries({ tenantId, applicationId, deliveryType, createdAtFrom, createdAtTo, status, keyword, page: Number(page), pageSize: Number(pageSize), }); } @Get('downstream-deliveries/dashboard') downstreamDeliveryDashboard( @Query('tenantId') tenantId?: string, @Query('applicationId') applicationId?: string, @Query('deliveryType') deliveryType?: string, @Query('createdAtFrom') createdAtFrom?: string, @Query('createdAtTo') createdAtTo?: string, ) { return this.operations.downstreamDeliveryDashboard({ tenantId, applicationId, deliveryType, createdAtFrom, createdAtTo, }); } @Get('downstream-recovery-statuses') downstreamRecoveryStatuses( @Query('tenantId') tenantId?: string, @Query('applicationId') applicationId?: string, @Query('state') state?: string, @Query('failureCategory') failureCategory?: string, @Query('keyword') keyword?: string, @Query('updatedAtFrom') updatedAtFrom?: string, @Query('updatedAtTo') updatedAtTo?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.operations.listDownstreamRecoveryStatuses({ tenantId, applicationId, state, failureCategory, keyword, updatedAtFrom, updatedAtTo, page: Number(page), pageSize: Number(pageSize), }); } @Get('downstream-recovery-statuses/export') async exportDownstreamRecoveryStatuses( @Query('tenantId') tenantId: string | undefined, @Query('applicationId') applicationId: string | undefined, @Query('state') state: string | undefined, @Query('failureCategory') failureCategory: string | undefined, @Query('keyword') keyword: string | undefined, @Query('updatedAtFrom') updatedAtFrom: string | undefined, @Query('updatedAtTo') updatedAtTo: string | undefined, @Res() response: DownloadResponse, ) { const exported = await this.operations.exportDownstreamRecoveryStatuses({ tenantId, applicationId, state, failureCategory, keyword, updatedAtFrom, updatedAtTo, }); response.setHeader('Content-Type', 'text/csv; charset=utf-8'); response.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(exported.fileName)}`); response.send(`\uFEFF${exported.content}`); } @Get('downstream-recovery-statuses/:id') downstreamRecoveryStatusDetail(@Param('id') id: string) { return this.operations.getDownstreamRecoveryStatus(id); } @Post('downstream-deliveries/:id/requeue') requeueDownstreamDelivery(@Param('id') id: string) { return this.sendChain.requeueDownstreamDelivery(id); } @Post('downstream-deliveries/requeue') batchRequeueDownstreamDeliveries(@Body() body: { ids?: string[] }) { return this.sendChain.batchRequeueDownstreamDeliveries(body.ids ?? []); } @Post('downstream-requeue-tasks/preview') previewDownstreamRequeueTask( @Body() body: { filter?: Record }, @CurrentSessionUserId() operatorId?: string, ) { return this.sendChain.previewDownstreamRequeueTask(body.filter ?? {}, operatorId); } @Post('downstream-requeue-tasks') createDownstreamRequeueTask( @Body() body: { previewToken?: string; reason?: string; ratePerSecond?: number; consecutiveFailureLimit?: number }, @CurrentSessionUserId() operatorId?: string, ) { return this.sendChain.createDownstreamRequeueTask( { previewToken: body.previewToken ?? '', reason: body.reason ?? '', ratePerSecond: body.ratePerSecond, consecutiveFailureLimit: body.consecutiveFailureLimit, }, operatorId, ); } @Get('downstream-requeue-tasks') listDownstreamRequeueTasks( @Query('status') status?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.sendChain.listDownstreamRequeueTasks({ status, page: Number(page), pageSize: Number(pageSize) }); } @Get('downstream-requeue-tasks/:id') getDownstreamRequeueTask(@Param('id') id: string) { return this.sendChain.getDownstreamRequeueTask(id); } @Get('downstream-requeue-tasks/:id/items') listDownstreamRequeueTaskItems( @Param('id') id: string, @Query('status') status?: string, @Query('keyword') keyword?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.sendChain.listDownstreamRequeueTaskItems(id, { status, keyword, page: Number(page), pageSize: Number(pageSize), }); } @Post('downstream-requeue-tasks/:id/:action') changeDownstreamRequeueTaskStatus( @Param('id') id: string, @Param('action') action: 'pause' | 'resume' | 'terminate', @CurrentSessionUserId() operatorId?: string, ) { return this.sendChain.changeDownstreamRequeueTaskStatus(id, action, operatorId); } } @ApiTags('admin-system-logs') @Controller('admin/system-logs') export class AdminSystemLogsController { constructor( private readonly operations: OperationsService, private readonly protocolLogs: ProtocolLogsService, ) {} @Get('protocol-interactions') protocolInteractions( @Query('protocol') protocol?: string, @Query('direction') direction?: string, @Query('eventType') eventType?: string, @Query('status') status?: string, @Query('keyword') keyword?: string, @Query('range') range?: string, @Query('createdAtFrom') createdAtFrom?: string, @Query('createdAtTo') createdAtTo?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.protocolLogs.list({ protocol, direction, eventType, status, keyword, range, createdAtFrom, createdAtTo, page: Number(page), pageSize: Number(pageSize), }); } @Get() list( @Query('tenantId') tenantId?: string, @Query('userId') userId?: string, @Query('keyword') keyword?: string, @Query('level') level?: string, @Query('module') module?: string, @Query('range') range?: string, @Query('createdAtFrom') createdAtFrom?: string, @Query('createdAtTo') createdAtTo?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.operations.systemLogs({ tenantId, userId, keyword, level, module, range, createdAtFrom, createdAtTo, page: Number(page), pageSize: Number(pageSize), }); } @Post('exports') export( @Body() body: { tenantId?: string; userId?: string; keyword?: string; level?: string; module?: string; range?: string; createdAtFrom?: string; createdAtTo?: string; }, ) { return this.operations.exportSystemLogs(body); } }