feat: add phone frequency controls and modularize codebase
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
import { BillingService } from '../billing/billing.service';
|
||||
import type { OpenApiService } from '../open-api/open-api.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import type { GatewaySubmitResultDto, GatewaySubmitSegmentResultDto, GatewayReceiptEventDto, GatewayUplinkEventDto, UplinkMatchCandidateInput, GatewayPendingDeliveryQueryDto, GatewayDownstreamSentDto, GatewayDownstreamAcknowledgedDto, GatewayDownstreamFailureType, GatewaySubmitDeadLetterDto, RequeueGatewaySubmitExceptionDto, GatewayDownstreamRecoveryStatusDto, TimeoutUnknownDto } from './send-chain.contracts';
|
||||
import { downstreamPendingTimeoutHours } from './send-chain.helpers';
|
||||
import type { SendSubmissionService } from './send-submission.service';
|
||||
import { SendAccountingService } from './send-accounting.service';
|
||||
import { SendDownstreamDeliveryService } from './send-downstream-delivery.service';
|
||||
import { SendDownstreamStateService } from './send-downstream-state.service';
|
||||
import { SendGatewayResultService } from './send-gateway-result.service';
|
||||
import { SendReceiptService } from './send-receipt.service';
|
||||
import { SendRetryService } from './send-retry.service';
|
||||
import { SendTimeoutService } from './send-timeout.service';
|
||||
|
||||
|
||||
export type SendCompletionCallbacks = Record<string, never>;
|
||||
export type SendCompletionFacade = SendCompletionService & SendSubmissionService;
|
||||
|
||||
/**
|
||||
* R10 internal compatibility facade. SendChainService remains the only public NestJS provider.
|
||||
*/
|
||||
export class SendCompletionService {
|
||||
private readonly gatewayResult: SendGatewayResultService;
|
||||
private readonly receipt: SendReceiptService;
|
||||
private readonly retry: SendRetryService;
|
||||
private readonly accounting: SendAccountingService;
|
||||
private readonly downstreamState: SendDownstreamStateService;
|
||||
private readonly downstreamDelivery: SendDownstreamDeliveryService;
|
||||
private readonly timeout: SendTimeoutService;
|
||||
|
||||
constructor(
|
||||
prisma: PrismaService,
|
||||
billing: BillingService,
|
||||
openApi: OpenApiService | undefined,
|
||||
facade: SendCompletionFacade,
|
||||
callbacks: SendCompletionCallbacks = {},
|
||||
) {
|
||||
this.gatewayResult = new SendGatewayResultService(prisma, billing, openApi, facade, callbacks);
|
||||
this.receipt = new SendReceiptService(prisma, billing, openApi, facade, callbacks);
|
||||
this.retry = new SendRetryService(prisma, billing, openApi, facade, callbacks);
|
||||
this.accounting = new SendAccountingService(prisma, billing, openApi, facade, callbacks);
|
||||
this.downstreamState = new SendDownstreamStateService(prisma, billing, openApi, facade, callbacks);
|
||||
this.downstreamDelivery = new SendDownstreamDeliveryService(prisma, billing, openApi, facade, callbacks);
|
||||
this.timeout = new SendTimeoutService(prisma, billing, openApi, facade, callbacks);
|
||||
}
|
||||
|
||||
async handleSubmitSegmentResult(data: GatewaySubmitSegmentResultDto) {
|
||||
return this.gatewayResult.handleSubmitSegmentResult(data);
|
||||
}
|
||||
|
||||
async resolveSubmitRecordForGatewaySegmentResult(
|
||||
messageRecordId: string,
|
||||
data: GatewaySubmitSegmentResultDto,
|
||||
) {
|
||||
return this.gatewayResult.resolveSubmitRecordForGatewaySegmentResult(messageRecordId, data);
|
||||
}
|
||||
|
||||
async handleSubmitResult(data: GatewaySubmitResultDto) {
|
||||
return this.gatewayResult.handleSubmitResult(data);
|
||||
}
|
||||
|
||||
async resolveSubmitRecordForGatewayResult(messageRecordId: string, data: GatewaySubmitResultDto) {
|
||||
return this.gatewayResult.resolveSubmitRecordForGatewayResult(messageRecordId, data);
|
||||
}
|
||||
|
||||
smsMessageSegmentAuditDelegate() {
|
||||
return this.gatewayResult.smsMessageSegmentAuditDelegate();
|
||||
}
|
||||
|
||||
async recordSubmitSegments(
|
||||
message: {
|
||||
id: string;
|
||||
tenantId?: string | null;
|
||||
batchTaskId?: string | null;
|
||||
channelId?: string | null;
|
||||
submitId?: string | null;
|
||||
billingUnits?: number | null;
|
||||
},
|
||||
data: GatewaySubmitResultDto,
|
||||
submittedAt: Date,
|
||||
) {
|
||||
return this.gatewayResult.recordSubmitSegments(message, data, submittedAt);
|
||||
}
|
||||
|
||||
async findMessageByGatewayEvent(messageId?: string, gatewayMessageId?: string) {
|
||||
return this.gatewayResult.findMessageByGatewayEvent(messageId, gatewayMessageId);
|
||||
}
|
||||
|
||||
async requireMessageByGatewayEvent(messageId?: string, gatewayMessageId?: string) {
|
||||
return this.gatewayResult.requireMessageByGatewayEvent(messageId, gatewayMessageId);
|
||||
}
|
||||
|
||||
async intakeReceipt(data: GatewayReceiptEventDto) {
|
||||
return this.receipt.intakeReceipt(data);
|
||||
}
|
||||
|
||||
async processPendingUpstreamReceiptInbox(limit = 100) {
|
||||
return this.receipt.processPendingUpstreamReceiptInbox(limit);
|
||||
}
|
||||
|
||||
async processUpstreamReceiptInboxRecord(id: string) {
|
||||
return this.receipt.processUpstreamReceiptInboxRecord(id);
|
||||
}
|
||||
|
||||
async runUpstreamReceiptInboxScan() {
|
||||
return this.receipt.runUpstreamReceiptInboxScan();
|
||||
}
|
||||
|
||||
async handleReceipt(
|
||||
data: GatewayReceiptEventDto,
|
||||
incomingIdentity?: { account: string; gatewayHost: string; gatewayPort: number; protocol: string; cmppVersion: string },
|
||||
) {
|
||||
return this.receipt.handleReceipt(data, incomingIdentity);
|
||||
}
|
||||
|
||||
async recordReceiptSegment(
|
||||
message: {
|
||||
id: string;
|
||||
tenantId?: string | null;
|
||||
batchTaskId?: string | null;
|
||||
channelId?: string | null;
|
||||
submitId?: string | null;
|
||||
billingUnits?: number | null;
|
||||
},
|
||||
data: GatewayReceiptEventDto,
|
||||
deliveredAt: Date,
|
||||
submitRecordId?: string,
|
||||
) {
|
||||
return this.receipt.recordReceiptSegment(message, data, deliveredAt, submitRecordId);
|
||||
}
|
||||
|
||||
async aggregateReceiptSegments(
|
||||
message: {
|
||||
id: string;
|
||||
billingUnits?: number | null;
|
||||
},
|
||||
data: GatewayReceiptEventDto,
|
||||
deliveredAt: Date,
|
||||
submitRecordId?: string,
|
||||
submitId?: string,
|
||||
) {
|
||||
return this.receipt.aggregateReceiptSegments(message, data, deliveredAt, submitRecordId, submitId);
|
||||
}
|
||||
|
||||
async resolveReceiptMessage(
|
||||
data: GatewayReceiptEventDto,
|
||||
incomingIdentity?: { account: string; gatewayHost: string; gatewayPort: number; protocol: string; cmppVersion: string },
|
||||
) {
|
||||
return this.receipt.resolveReceiptMessage(data, incomingIdentity);
|
||||
}
|
||||
|
||||
async recordGatewaySubmitDeadLetter(data: GatewaySubmitDeadLetterDto) {
|
||||
return this.retry.recordGatewaySubmitDeadLetter(data);
|
||||
}
|
||||
|
||||
async requeueGatewaySubmitDeadLetter(id: string, data: RequeueGatewaySubmitExceptionDto = {}) {
|
||||
return this.retry.requeueGatewaySubmitDeadLetter(id, data);
|
||||
}
|
||||
|
||||
async recoverStaleGatewaySubmitRequeues(now = new Date()) {
|
||||
return this.retry.recoverStaleGatewaySubmitRequeues(now);
|
||||
}
|
||||
|
||||
async retryMessageIfAllowed(
|
||||
message: {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
batchTaskId: string;
|
||||
applicationId?: string | null;
|
||||
templateId?: string | null;
|
||||
signatureId?: string | null;
|
||||
submitId?: string | null;
|
||||
messageId: string;
|
||||
phoneNumber: string;
|
||||
content: string;
|
||||
billingUnits: number;
|
||||
queuedAt?: Date;
|
||||
clientSrcId?: string | null;
|
||||
applicationExtension?: string | null;
|
||||
carrier?: string | null;
|
||||
province?: string | null;
|
||||
},
|
||||
reason: string,
|
||||
sourceSubmitRecordId?: string,
|
||||
) {
|
||||
return this.retry.retryMessageIfAllowed(message, reason, sourceSubmitRecordId);
|
||||
}
|
||||
|
||||
async chargeAcceptedMessage(message: {
|
||||
tenantId: string;
|
||||
applicationId?: string | null;
|
||||
batchTaskId: string;
|
||||
messageId: string;
|
||||
phoneNumber: string;
|
||||
content: string;
|
||||
billingUnits: number;
|
||||
unitPrice: number | bigint;
|
||||
amountCents: number | bigint;
|
||||
}) {
|
||||
return this.accounting.chargeAcceptedMessage(message);
|
||||
}
|
||||
|
||||
async releaseMessageReservation(
|
||||
message: { tenantId: string; batchTaskId: string; messageId: string; amountCents: number | bigint; billingUnits: number },
|
||||
remark: string,
|
||||
) {
|
||||
return this.accounting.releaseMessageReservation(message, remark);
|
||||
}
|
||||
|
||||
async refundMessage(
|
||||
message: { tenantId: string; messageId: string; amountCents: number | bigint; billingUnits: number },
|
||||
remark: string,
|
||||
) {
|
||||
return this.accounting.refundMessage(message, remark);
|
||||
}
|
||||
|
||||
async listPendingDownstreamDeliveries(data: GatewayPendingDeliveryQueryDto) {
|
||||
return this.downstreamState.listPendingDownstreamDeliveries(data);
|
||||
}
|
||||
|
||||
async markDownstreamDeliveryDelivered(id: string) {
|
||||
return this.downstreamState.markDownstreamDeliveryDelivered(id);
|
||||
}
|
||||
|
||||
async markDownstreamDeliverySent(data: GatewayDownstreamSentDto) {
|
||||
return this.downstreamState.markDownstreamDeliverySent(data);
|
||||
}
|
||||
|
||||
async acknowledgeDownstreamDelivery(data: GatewayDownstreamAcknowledgedDto) {
|
||||
return this.downstreamState.acknowledgeDownstreamDelivery(data);
|
||||
}
|
||||
|
||||
async markDownstreamDeliveryFailed(
|
||||
id: string,
|
||||
errorMessage?: string,
|
||||
failureType: GatewayDownstreamFailureType = 'send_failed',
|
||||
attempt?: GatewayDownstreamSentDto,
|
||||
) {
|
||||
return this.downstreamState.markDownstreamDeliveryFailed(id, errorMessage, failureType, attempt);
|
||||
}
|
||||
|
||||
async recordGatewayDownstreamRecoveryStatus(data: GatewayDownstreamRecoveryStatusDto) {
|
||||
return this.downstreamState.recordGatewayDownstreamRecoveryStatus(data);
|
||||
}
|
||||
|
||||
async requeueDownstreamDelivery(id: string) {
|
||||
return this.downstreamState.requeueDownstreamDelivery(id);
|
||||
}
|
||||
|
||||
async recoverStaleDownstreamManualRequeues(now = new Date()) {
|
||||
return this.downstreamState.recoverStaleDownstreamManualRequeues(now);
|
||||
}
|
||||
|
||||
async batchRequeueDownstreamDeliveries(ids: string[]) {
|
||||
return this.downstreamState.batchRequeueDownstreamDeliveries(ids);
|
||||
}
|
||||
|
||||
async handleUplink(data: GatewayUplinkEventDto) {
|
||||
return this.downstreamDelivery.handleUplink(data);
|
||||
}
|
||||
|
||||
async claimUplinkMatchCandidate(uplinkMessageId: string, candidateId: string, operatorId?: string) {
|
||||
return this.downstreamDelivery.claimUplinkMatchCandidate(uplinkMessageId, candidateId, operatorId);
|
||||
}
|
||||
|
||||
async queueAndTryDownstreamDelivery(data: {
|
||||
tenantId: string;
|
||||
applicationId?: string | null;
|
||||
messageRecordId?: string | null;
|
||||
messageId?: string | null;
|
||||
deliveryType: 'receipt' | 'uplink';
|
||||
payload: Record<string, unknown>;
|
||||
}) {
|
||||
return this.downstreamDelivery.queueAndTryDownstreamDelivery(data);
|
||||
}
|
||||
|
||||
async resolveUplinkMatch(
|
||||
data: GatewayUplinkEventDto,
|
||||
channel: { id: string; srcId?: string | null },
|
||||
): Promise<{
|
||||
tenantId?: string;
|
||||
applicationId?: string;
|
||||
messageRecordId?: string;
|
||||
matchStatus: string;
|
||||
matchReason: string;
|
||||
candidates: UplinkMatchCandidateInput[];
|
||||
}> {
|
||||
return this.downstreamDelivery.resolveUplinkMatch(data, channel);
|
||||
}
|
||||
|
||||
async recordCmppFailureReceipt(
|
||||
message: {
|
||||
id: string;
|
||||
tenantId?: string | null;
|
||||
batchTaskId?: string | null;
|
||||
applicationId?: string | null;
|
||||
messageId: string;
|
||||
phoneNumber: string;
|
||||
cmppSubmitSequenceId?: string | null;
|
||||
cmppSubmitGroupMessageId?: string | null;
|
||||
},
|
||||
errorCode: string,
|
||||
reason: string,
|
||||
) {
|
||||
return this.downstreamDelivery.recordCmppFailureReceipt(message, errorCode, reason);
|
||||
}
|
||||
|
||||
async postGatewayControl(path: string, payload: unknown) {
|
||||
return this.downstreamDelivery.postGatewayControl(path, payload);
|
||||
}
|
||||
|
||||
async markUnknownTimeout(data: TimeoutUnknownDto) {
|
||||
return this.timeout.markUnknownTimeout(data);
|
||||
}
|
||||
|
||||
async markExpiredDownstreamDeliveries(olderThanHours = downstreamPendingTimeoutHours()) {
|
||||
return this.timeout.markExpiredDownstreamDeliveries(olderThanHours);
|
||||
}
|
||||
|
||||
async runReceiptTimeoutScan() {
|
||||
return this.timeout.runReceiptTimeoutScan();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user