feat: complete phase2 baseline cdr quality rbac
This commit is contained in:
@@ -17,12 +17,18 @@ export interface PublishConfigResult {
|
||||
}
|
||||
|
||||
export interface ConfigManifest {
|
||||
schemaVersion: number;
|
||||
version: string;
|
||||
generatedAt: string;
|
||||
customerCount: number;
|
||||
businessPrefixCount: number;
|
||||
gatewayCount: number;
|
||||
customerGatewayIpCount: number;
|
||||
customerGatewayBusinessPrefixCount: number;
|
||||
customerGatewayCallerPrefixCount: number;
|
||||
policyCount: number;
|
||||
vendorGatewayCount: number;
|
||||
callerRewriteCount: number;
|
||||
lineGroupCount: number;
|
||||
cityCount: number;
|
||||
phoneSegmentCount: number;
|
||||
@@ -40,14 +46,29 @@ type ConfigSnapshot = {
|
||||
creditLimit: string;
|
||||
minBalance: string;
|
||||
}>;
|
||||
businessPrefixes: Array<{
|
||||
id: string;
|
||||
prefix: string;
|
||||
name: string;
|
||||
priority: number;
|
||||
status: string;
|
||||
}>;
|
||||
gateways: Array<{
|
||||
id: string;
|
||||
customerId: string;
|
||||
authMode: string;
|
||||
sourceIp: string | null;
|
||||
sourceIps: string[];
|
||||
sipUsername: string | null;
|
||||
sipDomain: string | null;
|
||||
sipHa1: string | null;
|
||||
lineGroupId: string | null;
|
||||
billingCycleSec: number;
|
||||
cycleRate: string;
|
||||
callerMatchMode: string;
|
||||
callerPrefixes: string[];
|
||||
calleeMatchMode: string;
|
||||
businessPrefixes: Array<{ id: string; prefix: string; name: string; priority: number; status: string }>;
|
||||
status: string;
|
||||
}>;
|
||||
policies: Array<{
|
||||
@@ -75,10 +96,12 @@ type ConfigSnapshot = {
|
||||
concurrencyLimit: number;
|
||||
billingCycleSec: number;
|
||||
cycleRate: string;
|
||||
landingCalleePrefix: string | null;
|
||||
status: string;
|
||||
forbiddenPeriods: Array<{ weekdayMask: number; startTime: string; endTime: string }>;
|
||||
codecs: Array<{ codec: string; priority: number }>;
|
||||
prefixRules: Array<{ direction: string; matchPrefix: string; replacePrefix: string; priority: number }>;
|
||||
callerRewritePool: Array<{ caller: string; weight: number; status: string }>;
|
||||
blockedRegions: Array<{
|
||||
regionScope: string;
|
||||
provinceCode: string | null;
|
||||
@@ -136,7 +159,7 @@ type ConfigSnapshot = {
|
||||
export async function publishPendingConfig(prisma: PrismaClient, redis: RedisClient, now = new Date()): Promise<PublishConfigResult> {
|
||||
const pending = await prisma.outboxEvent.findMany({
|
||||
where: {
|
||||
aggregateType: { in: ['customer_gateway_config', 'vendor_gateway_config', 'line_group_config', 'number_library_config'] },
|
||||
aggregateType: { in: ['business_prefix_config', 'customer_gateway_config', 'vendor_gateway_config', 'line_group_config', 'number_library_config'] },
|
||||
status: 'PENDING',
|
||||
availableAt: { lte: now }
|
||||
},
|
||||
@@ -217,14 +240,30 @@ export async function rollbackActiveConfig(redis: RedisClient): Promise<{ rolled
|
||||
}
|
||||
|
||||
async function loadSnapshot(prisma: PrismaClient): Promise<ConfigSnapshot> {
|
||||
const [customers, gateways, policies, vendorGateways, lineGroups, cities, phoneSegments, areaCodes, carrierPrefixRules] = await prisma.$transaction([
|
||||
const [customers, businessPrefixes, gateways, policies, vendorGateways, lineGroups, cities, phoneSegments, areaCodes, carrierPrefixRules] = await prisma.$transaction([
|
||||
prisma.customer.findMany({
|
||||
where: { deletedAt: null },
|
||||
orderBy: [{ id: 'asc' }]
|
||||
}),
|
||||
prisma.businessPrefix.findMany({
|
||||
where: { deletedAt: null, status: 'ENABLED' },
|
||||
orderBy: [{ priority: 'asc' }, { prefix: 'asc' }]
|
||||
}),
|
||||
prisma.customerGateway.findMany({
|
||||
where: { deletedAt: null },
|
||||
orderBy: [{ id: 'asc' }]
|
||||
orderBy: [{ id: 'asc' }],
|
||||
include: {
|
||||
ips: { where: { deletedAt: null, status: 'ENABLED' }, orderBy: [{ createdAt: 'asc' }] },
|
||||
callerPrefixes: { orderBy: [{ priority: 'asc' }, { prefix: 'asc' }] },
|
||||
businessPrefixes: {
|
||||
orderBy: [{ createdAt: 'asc' }],
|
||||
include: {
|
||||
businessPrefix: {
|
||||
select: { id: true, prefix: true, name: true, priority: true, status: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
prisma.customerGatewayPolicy.findMany({
|
||||
where: { deletedAt: null },
|
||||
@@ -237,6 +276,7 @@ async function loadSnapshot(prisma: PrismaClient): Promise<ConfigSnapshot> {
|
||||
forbiddenPeriods: { orderBy: [{ weekdayMask: 'asc' }, { startTime: 'asc' }] },
|
||||
codecs: { orderBy: [{ priority: 'asc' }] },
|
||||
prefixRules: { orderBy: [{ direction: 'asc' }, { priority: 'asc' }] },
|
||||
callerRewritePool: { where: { deletedAt: null, status: 'ENABLED' }, orderBy: [{ weight: 'desc' }, { caller: 'asc' }] },
|
||||
blockedRegions: { orderBy: [{ regionScope: 'asc' }, { provinceCode: 'asc' }, { cityCode: 'asc' }] }
|
||||
}
|
||||
}),
|
||||
@@ -275,14 +315,35 @@ async function loadSnapshot(prisma: PrismaClient): Promise<ConfigSnapshot> {
|
||||
creditLimit: customer.creditLimit.toFixed(6),
|
||||
minBalance: customer.minBalance.toFixed(6)
|
||||
})),
|
||||
businessPrefixes: businessPrefixes.map((prefixItem) => ({
|
||||
id: prefixItem.id,
|
||||
prefix: prefixItem.prefix,
|
||||
name: prefixItem.name,
|
||||
priority: prefixItem.priority,
|
||||
status: prefixItem.status
|
||||
})),
|
||||
gateways: gateways.map((gateway) => ({
|
||||
id: gateway.id,
|
||||
customerId: gateway.customerId,
|
||||
authMode: gateway.authMode,
|
||||
sourceIp: gateway.sourceIp,
|
||||
sourceIps: gateway.ips.length ? gateway.ips.map((ip) => ip.sourceIp) : gateway.sourceIp ? [gateway.sourceIp] : [],
|
||||
sipUsername: gateway.sipUsername,
|
||||
sipDomain: gateway.sipDomain,
|
||||
sipHa1: gateway.sipHa1,
|
||||
lineGroupId: gateway.lineGroupId,
|
||||
billingCycleSec: gateway.billingCycleSec,
|
||||
cycleRate: gateway.cycleRate.toFixed(6),
|
||||
callerMatchMode: gateway.callerMatchMode,
|
||||
callerPrefixes: gateway.callerPrefixes.map((item) => item.prefix),
|
||||
calleeMatchMode: gateway.calleeMatchMode,
|
||||
businessPrefixes: gateway.businessPrefixes.map((item) => ({
|
||||
id: item.businessPrefix.id,
|
||||
prefix: item.businessPrefix.prefix,
|
||||
name: item.businessPrefix.name,
|
||||
priority: item.businessPrefix.priority,
|
||||
status: item.businessPrefix.status
|
||||
})),
|
||||
status: gateway.status
|
||||
})),
|
||||
policies: policies.map((policy) => ({
|
||||
@@ -310,6 +371,7 @@ async function loadSnapshot(prisma: PrismaClient): Promise<ConfigSnapshot> {
|
||||
concurrencyLimit: gateway.concurrencyLimit,
|
||||
billingCycleSec: gateway.billingCycleSec,
|
||||
cycleRate: gateway.cycleRate.toFixed(6),
|
||||
landingCalleePrefix: gateway.landingCalleePrefix,
|
||||
status: gateway.status,
|
||||
forbiddenPeriods: gateway.forbiddenPeriods.map((period) => ({
|
||||
weekdayMask: period.weekdayMask,
|
||||
@@ -326,6 +388,11 @@ async function loadSnapshot(prisma: PrismaClient): Promise<ConfigSnapshot> {
|
||||
replacePrefix: rule.replacePrefix,
|
||||
priority: rule.priority
|
||||
})),
|
||||
callerRewritePool: gateway.callerRewritePool.map((caller) => ({
|
||||
caller: caller.caller,
|
||||
weight: caller.weight,
|
||||
status: caller.status
|
||||
})),
|
||||
blockedRegions: gateway.blockedRegions.map((region) => ({
|
||||
regionScope: region.regionScope,
|
||||
provinceCode: region.provinceCode,
|
||||
@@ -391,12 +458,18 @@ async function writeSnapshot(
|
||||
const prefix = configVersionPrefix(version);
|
||||
const checksum = crypto.createHash('sha256').update(stableJson(snapshot)).digest('hex');
|
||||
const manifest: ConfigManifest = {
|
||||
schemaVersion: 2,
|
||||
version,
|
||||
generatedAt: now.toISOString(),
|
||||
customerCount: snapshot.customers.length,
|
||||
businessPrefixCount: snapshot.businessPrefixes.length,
|
||||
gatewayCount: snapshot.gateways.length,
|
||||
customerGatewayIpCount: snapshot.gateways.reduce((sum, gateway) => sum + gateway.sourceIps.length, 0),
|
||||
customerGatewayBusinessPrefixCount: snapshot.gateways.reduce((sum, gateway) => sum + gateway.businessPrefixes.length, 0),
|
||||
customerGatewayCallerPrefixCount: snapshot.gateways.reduce((sum, gateway) => sum + gateway.callerPrefixes.length, 0),
|
||||
policyCount: snapshot.policies.length,
|
||||
vendorGatewayCount: snapshot.vendorGateways.length,
|
||||
callerRewriteCount: snapshot.vendorGateways.reduce((sum, gateway) => sum + gateway.callerRewritePool.length, 0),
|
||||
lineGroupCount: snapshot.lineGroups.length,
|
||||
cityCount: snapshot.cities.length,
|
||||
phoneSegmentCount: snapshot.phoneSegments.length,
|
||||
@@ -412,10 +485,31 @@ async function writeSnapshot(
|
||||
for (const customer of snapshot.customers) {
|
||||
multi.set(`${prefix}:customer:${customer.id}`, JSON.stringify(customer));
|
||||
}
|
||||
for (const businessPrefix of snapshot.businessPrefixes) {
|
||||
multi.set(`${prefix}:business_prefix:${businessPrefix.id}`, JSON.stringify(businessPrefix));
|
||||
multi.set(`${prefix}:business_prefix_value:${businessPrefix.prefix}`, businessPrefix.id);
|
||||
multi.rpush(`${prefix}:business_prefixes`, JSON.stringify(businessPrefix));
|
||||
}
|
||||
for (const gateway of snapshot.gateways) {
|
||||
multi.set(`${prefix}:customer_gateway:${gateway.id}`, JSON.stringify(gateway));
|
||||
if ((gateway.authMode === 'IP' || gateway.authMode === 'MIXED') && gateway.sourceIp) {
|
||||
multi.set(`${prefix}:auth:ip:${gateway.sourceIp}`, gateway.id);
|
||||
if (gateway.callerMatchMode === 'PREFIXES') {
|
||||
for (const callerPrefix of gateway.callerPrefixes) {
|
||||
multi.rpush(`${prefix}:customer_gateway:${gateway.id}:caller_prefixes`, callerPrefix);
|
||||
}
|
||||
}
|
||||
if (gateway.calleeMatchMode === 'BUSINESS_PREFIXES') {
|
||||
for (const businessPrefix of gateway.businessPrefixes) {
|
||||
multi.rpush(`${prefix}:customer_gateway:${gateway.id}:business_prefixes`, JSON.stringify(businessPrefix));
|
||||
}
|
||||
}
|
||||
if (gateway.lineGroupId) {
|
||||
multi.set(`${prefix}:customer_gateway:${gateway.id}:line_group`, gateway.lineGroupId);
|
||||
}
|
||||
if (gateway.authMode === 'IP' || gateway.authMode === 'MIXED') {
|
||||
for (const sourceIp of gateway.sourceIps) {
|
||||
multi.set(`${prefix}:auth:ip:${sourceIp}`, gateway.id);
|
||||
multi.rpush(`${prefix}:auth:ip:${sourceIp}:gateways`, gateway.id);
|
||||
}
|
||||
}
|
||||
if ((gateway.authMode === 'SIP_DIGEST' || gateway.authMode === 'MIXED') && gateway.sipUsername && gateway.sipDomain) {
|
||||
multi.set(`${prefix}:auth:sip:${gateway.sipUsername}@${gateway.sipDomain}`, gateway.id);
|
||||
@@ -426,6 +520,9 @@ async function writeSnapshot(
|
||||
}
|
||||
for (const gateway of snapshot.vendorGateways) {
|
||||
multi.set(`${prefix}:vendor_gateway:${gateway.id}`, JSON.stringify(gateway));
|
||||
for (const callerRewrite of gateway.callerRewritePool) {
|
||||
multi.rpush(`${prefix}:vendor_gateway:${gateway.id}:caller_rewrite_pool`, JSON.stringify(callerRewrite));
|
||||
}
|
||||
for (const region of gateway.blockedRegions) {
|
||||
if (region.regionScope === 'CITY' && region.cityCode) {
|
||||
multi.sadd(`${prefix}:vendor_gateway:${gateway.id}:blocked_city_codes`, region.cityCode);
|
||||
|
||||
Reference in New Issue
Block a user