feat: add number library routing and cdr location support

This commit is contained in:
hectorzhao
2026-06-24 11:39:32 +08:00
parent 5fa1bd35e9
commit 7057fd3c42
63 changed files with 6361 additions and 566 deletions
@@ -1,4 +1,4 @@
import { Body, Controller, Get, Inject, Param, Patch, Post, Query } from '@nestjs/common';
import { Body, Controller, Delete, Get, Inject, Param, Patch, Post, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuditAction } from '../audit/audit.metadata.js';
import { CurrentUserParam, RequirePermissions, type CurrentUser } from '../security/security.metadata.js';
@@ -48,4 +48,11 @@ export class CustomerGatewaysController {
disable(@Param('id') id: string, @CurrentUserParam() currentUser?: CurrentUser) {
return this.customerGatewaysService.disable(id, currentUser?.id);
}
@Delete(':id')
@RequirePermissions('customer_gateways.manage')
@AuditAction({ module: 'customer_gateways', action: 'delete', objectType: 'customer_gateway', objectIdParam: 'id' })
remove(@Param('id') id: string, @CurrentUserParam() currentUser?: CurrentUser) {
return this.customerGatewaysService.remove(id, currentUser?.id);
}
}
@@ -97,6 +97,13 @@ class MemoryCustomerGatewaysRepository implements CustomerGatewaysRepository {
return updated;
}
async softDelete(gatewayId: string): Promise<CustomerGatewaySummary> {
const current = await this.get(gatewayId);
const deleted = { ...current, status: 'DISABLED' as CustomerGatewayStatus, policyCount: 0 };
this.gateways.delete(gatewayId);
return deleted;
}
private summary(input: {
id: string;
customerId?: string;
@@ -269,5 +276,7 @@ describe('S13 customer gateways API', () => {
await request(app.getHttpServer()).post('/api/v2/customer-gateways/cgw_created/disable').set('Authorization', `Bearer ${tokenFor('usr_ops')}`).expect(201);
await request(app.getHttpServer()).post('/api/v2/customer-gateways/cgw_created/enable').set('Authorization', `Bearer ${tokenFor('usr_ops')}`).expect(201);
await request(app.getHttpServer()).delete('/api/v2/customer-gateways/cgw_created').set('Authorization', `Bearer ${tokenFor('usr_ops')}`).expect(200);
expect(audit.entries.some((entry) => entry.module === 'customer_gateways' && entry.action === 'delete' && entry.result === 'SUCCESS')).toBe(true);
});
});
@@ -50,6 +50,7 @@ export interface CustomerGatewaysRepository {
create(input: CreateCustomerGatewayInput): Promise<CustomerGatewaySummary>;
update(gatewayId: string, input: UpdateCustomerGatewayInput): Promise<CustomerGatewaySummary>;
setStatus(gatewayId: string, status: CustomerGatewayStatus, actorId?: string): Promise<CustomerGatewaySummary>;
softDelete(gatewayId: string, actorId?: string): Promise<CustomerGatewaySummary>;
}
export const CUSTOMER_GATEWAYS_REPOSITORY = Symbol('CUSTOMER_GATEWAYS_REPOSITORY');
@@ -168,6 +169,35 @@ export class PrismaCustomerGatewaysRepository implements CustomerGatewaysReposit
return this.toSummary(gateway);
}
async softDelete(gatewayIdValue: string, actorId?: string): Promise<CustomerGatewaySummary> {
await this.findActiveOrThrow(gatewayIdValue);
const gateway = await this.prisma.$transaction(async (tx) => {
await tx.customerGatewayPolicy.updateMany({
where: { gatewayId: gatewayIdValue, deletedAt: null },
data: {
status: 'DISABLED',
deletedAt: new Date(),
updatedBy: actorId
}
});
const deleted = await tx.customerGateway.update({
where: { id: gatewayIdValue },
data: {
status: 'DISABLED',
deletedAt: new Date(),
updatedBy: actorId,
version: { increment: 1 }
},
include: this.includeSummary()
});
await this.enqueueConfigOutbox(tx, deleted.id, 'customer_gateway.deleted');
return deleted;
});
return this.toSummary(gateway);
}
private async enqueueConfigOutbox(tx: Prisma.TransactionClient, aggregateId: string, eventType: string): Promise<void> {
await tx.outboxEvent.create({
data: {
@@ -103,6 +103,10 @@ export class CustomerGatewaysService {
return this.gateways.setStatus(gatewayId, 'DISABLED', actorId);
}
remove(gatewayId: string, actorId?: string): Promise<CustomerGatewaySummary> {
return this.gateways.softDelete(gatewayId, actorId);
}
private limitedString(value: unknown, field: string, maxLength: number): string {
if (typeof value !== 'string' || value.trim().length === 0) {
throw new BadRequestException({ code: 'VALIDATION_ERROR', message: `${field} is required.` });