feat: add access number routing and admin list improvements
This commit is contained in:
@@ -10,6 +10,9 @@ export interface CreateSmsApplicationDto {
|
||||
callbackUrl?: string;
|
||||
cmppAccount?: string;
|
||||
cmppEnterpriseCode?: string;
|
||||
cmppApplicationExtension?: string;
|
||||
cmppAccessNumberFillEnabled?: boolean;
|
||||
cmppAccessNumberFillPrefix?: string;
|
||||
passwordCipher?: string;
|
||||
interfaceEnabled?: boolean;
|
||||
interfaceType?: string;
|
||||
@@ -206,7 +209,9 @@ export class SmsConfigService {
|
||||
sentToday: todayTotal,
|
||||
deliveryRate: todayTotal > 0 ? Number(((delivered / todayTotal) * 100).toFixed(1)) : 0,
|
||||
};
|
||||
});
|
||||
}).sort((left, right) => right.sentToday - left.sentToday
|
||||
|| left.name.localeCompare(right.name, 'zh-CN')
|
||||
|| left.id.localeCompare(right.id));
|
||||
}
|
||||
|
||||
async getApplication(applicationId: string, tenantId?: string) {
|
||||
@@ -350,6 +355,8 @@ export class SmsConfigService {
|
||||
const interfaceType = normalizeApplicationInterfaceType(data.interfaceType);
|
||||
const cmppAccount = data.cmppAccount ? await this.validateAndReserveCmppAccount(data.cmppAccount) : await this.generateCmppAccount();
|
||||
const cmppEnterpriseCode = cmppAccount;
|
||||
const accessNumber = normalizeCmppAccessNumberConfig(data);
|
||||
await this.validateClientSrcIdAvailable(accessNumber.clientSrcId);
|
||||
return this.prisma.smsApplication.create({
|
||||
data: {
|
||||
tenantId: data.tenantId,
|
||||
@@ -358,6 +365,10 @@ export class SmsConfigService {
|
||||
callbackUrl: data.callbackUrl,
|
||||
cmppAccount,
|
||||
cmppEnterpriseCode,
|
||||
cmppApplicationExtension: accessNumber.applicationExtension,
|
||||
cmppAccessNumberFillEnabled: accessNumber.fillEnabled,
|
||||
cmppAccessNumberFillPrefix: accessNumber.fillPrefix,
|
||||
cmppClientSrcId: accessNumber.clientSrcId,
|
||||
secretHash: secret,
|
||||
interfaceEnabled: data.interfaceEnabled ?? true,
|
||||
interfaceType,
|
||||
@@ -396,6 +407,15 @@ export class SmsConfigService {
|
||||
const secretHash = data.passwordCipher === undefined
|
||||
? undefined
|
||||
: normalizeApplicationPassword(data.passwordCipher);
|
||||
const accessNumberChanged = data.cmppApplicationExtension !== undefined
|
||||
|| data.cmppAccessNumberFillEnabled !== undefined
|
||||
|| data.cmppAccessNumberFillPrefix !== undefined;
|
||||
const accessNumber = accessNumberChanged
|
||||
? normalizeCmppAccessNumberConfig(data, application)
|
||||
: undefined;
|
||||
if (accessNumber?.clientSrcId && accessNumber.clientSrcId !== application.cmppClientSrcId) {
|
||||
await this.validateClientSrcIdAvailable(accessNumber.clientSrcId, applicationId);
|
||||
}
|
||||
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
if (data.ipAllowlist) {
|
||||
@@ -409,6 +429,10 @@ export class SmsConfigService {
|
||||
callbackUrl: data.callbackUrl,
|
||||
cmppAccount,
|
||||
cmppEnterpriseCode,
|
||||
cmppApplicationExtension: accessNumber?.applicationExtension,
|
||||
cmppAccessNumberFillEnabled: accessNumber?.fillEnabled,
|
||||
cmppAccessNumberFillPrefix: accessNumber?.fillPrefix,
|
||||
cmppClientSrcId: accessNumber?.clientSrcId,
|
||||
secretHash,
|
||||
interfaceEnabled: data.interfaceEnabled,
|
||||
interfaceType,
|
||||
@@ -570,7 +594,10 @@ export class SmsConfigService {
|
||||
enterpriseCode: application.cmppEnterpriseCode,
|
||||
account: application.cmppAccount,
|
||||
passwordCipher: application.secretHash,
|
||||
srcId: channel?.srcId ?? '',
|
||||
srcId: application.cmppClientSrcId ?? '',
|
||||
applicationExtension: application.cmppApplicationExtension,
|
||||
accessNumberFillEnabled: application.cmppAccessNumberFillEnabled,
|
||||
accessNumberFillPrefix: application.cmppAccessNumberFillPrefix,
|
||||
interfaceEnabled: application.interfaceEnabled,
|
||||
interfaceType: application.interfaceType,
|
||||
maxConnections: application.cmppMaxConnections,
|
||||
@@ -591,6 +618,14 @@ export class SmsConfigService {
|
||||
return cmppAccount;
|
||||
}
|
||||
|
||||
private async validateClientSrcIdAvailable(clientSrcId: string | null, currentApplicationId?: string) {
|
||||
if (!clientSrcId) return;
|
||||
const exists = await this.prisma.smsApplication.findUnique({ where: { cmppClientSrcId: clientSrcId } });
|
||||
if (exists && exists.id !== currentApplicationId) {
|
||||
throw new BadRequestException('client CMPP Src_Id already exists');
|
||||
}
|
||||
}
|
||||
|
||||
private async generateCmppAccount() {
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
const cmppAccount = String(randomInt(100000, 1000000));
|
||||
@@ -1422,6 +1457,54 @@ function normalizeApplicationInterfaceType(value?: string): ApplicationInterface
|
||||
return interfaceType as ApplicationInterfaceType;
|
||||
}
|
||||
|
||||
function normalizeCmppAccessNumberConfig(
|
||||
data: Pick<CreateSmsApplicationDto, 'cmppApplicationExtension' | 'cmppAccessNumberFillEnabled' | 'cmppAccessNumberFillPrefix'>,
|
||||
current?: {
|
||||
cmppApplicationExtension?: string | null;
|
||||
cmppAccessNumberFillEnabled?: boolean | null;
|
||||
cmppAccessNumberFillPrefix?: string | null;
|
||||
},
|
||||
) {
|
||||
const applicationExtension = (
|
||||
data.cmppApplicationExtension === undefined
|
||||
? current?.cmppApplicationExtension
|
||||
: data.cmppApplicationExtension
|
||||
)?.trim() || null;
|
||||
const fillEnabled = data.cmppAccessNumberFillEnabled
|
||||
?? current?.cmppAccessNumberFillEnabled
|
||||
?? false;
|
||||
const configuredPrefix = (
|
||||
data.cmppAccessNumberFillPrefix === undefined
|
||||
? current?.cmppAccessNumberFillPrefix
|
||||
: data.cmppAccessNumberFillPrefix
|
||||
)?.trim() || null;
|
||||
|
||||
if (applicationExtension && !/^\d+$/.test(applicationExtension)) {
|
||||
throw new BadRequestException('cmppApplicationExtension must contain digits only');
|
||||
}
|
||||
if (applicationExtension && applicationExtension.length > 21) {
|
||||
throw new BadRequestException('cmppApplicationExtension must not exceed 21 digits');
|
||||
}
|
||||
if (fillEnabled && !applicationExtension) {
|
||||
throw new BadRequestException('cmppApplicationExtension is required when access number filling is enabled');
|
||||
}
|
||||
if (fillEnabled && !configuredPrefix) {
|
||||
throw new BadRequestException('cmppAccessNumberFillPrefix is required when access number filling is enabled');
|
||||
}
|
||||
if (configuredPrefix && !/^\d+$/.test(configuredPrefix)) {
|
||||
throw new BadRequestException('cmppAccessNumberFillPrefix must contain digits only');
|
||||
}
|
||||
|
||||
const fillPrefix = fillEnabled ? configuredPrefix : null;
|
||||
const clientSrcId = applicationExtension
|
||||
? `${fillPrefix ?? ''}${applicationExtension}`
|
||||
: null;
|
||||
if (clientSrcId && clientSrcId.length > 21) {
|
||||
throw new BadRequestException('client CMPP Src_Id must not exceed 21 digits');
|
||||
}
|
||||
return { applicationExtension, fillEnabled, fillPrefix, clientSrcId };
|
||||
}
|
||||
|
||||
function getPositiveInteger(value: number | undefined, fallback: number, fieldName: string) {
|
||||
if (value === undefined || value === null) {
|
||||
return fallback;
|
||||
|
||||
Reference in New Issue
Block a user