feat: add number library routing and cdr location support
This commit is contained in:
@@ -5,6 +5,8 @@ export const PASSWORD_ALGO_ARGON2ID = 'argon2id';
|
||||
|
||||
export type PermissionKey =
|
||||
| 'dashboard.view'
|
||||
| 'active_calls.view'
|
||||
| 'active_calls.manage'
|
||||
| 'customers.view'
|
||||
| 'customers.manage'
|
||||
| 'customer_gateways.view'
|
||||
@@ -15,6 +17,8 @@ export type PermissionKey =
|
||||
| 'vendor_gateways.manage'
|
||||
| 'line_groups.view'
|
||||
| 'line_groups.manage'
|
||||
| 'number_library.view'
|
||||
| 'number_library.manage'
|
||||
| 'recharges.view'
|
||||
| 'recharges.manage'
|
||||
| 'cdr.view'
|
||||
|
||||
@@ -5,6 +5,7 @@ import { join } from 'node:path';
|
||||
const schema = readFileSync(join(process.cwd(), 'prisma/schema.prisma'), 'utf8');
|
||||
const migration = readFileSync(join(process.cwd(), 'prisma/migrations/20260621090000_init_v2_schema/migration.sql'), 'utf8');
|
||||
const authMigration = readFileSync(join(process.cwd(), 'prisma/migrations/20260621093000_auth_sessions/migration.sql'), 'utf8');
|
||||
const numberLibraryMigration = readFileSync(join(process.cwd(), 'prisma/migrations/20260624095000_number_library/migration.sql'), 'utf8');
|
||||
|
||||
describe('Prisma schema contract', () => {
|
||||
it('defines every V2 core business table', () => {
|
||||
@@ -16,11 +17,16 @@ describe('Prisma schema contract', () => {
|
||||
'vendors',
|
||||
'vendor_recharges',
|
||||
'vendor_gateways',
|
||||
'vendor_gateway_blocked_regions',
|
||||
'vendor_gateway_forbidden_periods',
|
||||
'vendor_gateway_codecs',
|
||||
'vendor_gateway_prefix_rules',
|
||||
'landing_line_groups',
|
||||
'landing_line_group_items',
|
||||
'geo_cities',
|
||||
'phone_number_segments',
|
||||
'phone_area_codes',
|
||||
'carrier_prefix_rules',
|
||||
'raw_cdrs',
|
||||
'rated_cdrs',
|
||||
'recordings',
|
||||
@@ -39,7 +45,7 @@ describe('Prisma schema contract', () => {
|
||||
|
||||
for (const table of tables) {
|
||||
expect(schema).toContain(`@@map("${table}")`);
|
||||
expect(`${migration}\n${authMigration}`).toContain(`CREATE TABLE \`${table}\``);
|
||||
expect(`${migration}\n${authMigration}\n${numberLibraryMigration}`).toContain(`CREATE TABLE \`${table}\``);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -52,6 +58,16 @@ describe('Prisma schema contract', () => {
|
||||
expect(migration).toContain('UNIQUE INDEX `idempotency_keys_key_key`');
|
||||
});
|
||||
|
||||
it('captures number library and CDR callee attribution fields', () => {
|
||||
expect(schema).toContain('model GeoCity');
|
||||
expect(schema).toContain('model PhoneNumberSegment');
|
||||
expect(schema).toContain('model CarrierPrefixRule');
|
||||
expect(numberLibraryMigration).toContain('`segment7` CHAR(7) NOT NULL');
|
||||
expect(numberLibraryMigration).toContain('`callee_city_code` VARCHAR(12) NULL');
|
||||
expect(numberLibraryMigration).toContain('`callee_operator` ENUM');
|
||||
expect(numberLibraryMigration).toContain('CREATE INDEX `raw_cdrs_callee_operator_started_at_idx`');
|
||||
});
|
||||
|
||||
it('models audit columns and outbox publishing state', () => {
|
||||
expect(schema).toContain('createdAt');
|
||||
expect(schema).toContain('updatedAt');
|
||||
|
||||
@@ -142,6 +142,11 @@ function sampleEvent(overrides: Partial<CdrStreamPublishInput> = {}): CdrStreamP
|
||||
sourceIp: '100.93.185.30',
|
||||
caller: 's21-ip-1001',
|
||||
callee: '13800138000',
|
||||
calleeCityCode: '340100',
|
||||
calleeCityName: '合肥市',
|
||||
calleeProvinceName: '安徽省',
|
||||
calleeOperator: 'MOBILE',
|
||||
calleeNumberType: 'MOBILE',
|
||||
sipCode: 503,
|
||||
hangupReason: 'CONFIG_MISSING',
|
||||
configVersion: '0',
|
||||
@@ -162,9 +167,45 @@ describe('CDR Redis Stream contract', () => {
|
||||
expect(parsed.node_id).toBe('a1');
|
||||
expect(parsed.opensips_instance).toBe('opensips-a1');
|
||||
expect(parsed.rtpengine_node).toBe('a1');
|
||||
expect(parsed.callee_city_code).toBe('340100');
|
||||
expect(parsed.callee_city_name).toBe('合肥市');
|
||||
expect(parsed.callee_operator).toBe('MOBILE');
|
||||
expect(parsed.sipCode).toBe(503);
|
||||
});
|
||||
|
||||
it('keeps old stream entries compatible when number snapshot fields are absent', () => {
|
||||
const parsed = parseCdrStreamEvent([
|
||||
'schema_version',
|
||||
'1',
|
||||
'event_id',
|
||||
'evt-old',
|
||||
'idempotency_key',
|
||||
'call-old:1',
|
||||
'call_id',
|
||||
'call-old',
|
||||
'node_id',
|
||||
'a1',
|
||||
'opensips_instance',
|
||||
'opensips-a1',
|
||||
'ingress_a_ip',
|
||||
'100.90.90.90',
|
||||
'rtpengine_node',
|
||||
'a1',
|
||||
'source_ip',
|
||||
'100.93.185.30',
|
||||
'sip_code',
|
||||
'503',
|
||||
'hangup_reason',
|
||||
'CONFIG_MISSING',
|
||||
'created_at',
|
||||
'2026-06-21T07:30:00.000Z'
|
||||
]);
|
||||
|
||||
expect(parsed.callee_city_code).toBe('');
|
||||
expect(parsed.callee_operator).toBe('UNKNOWN');
|
||||
expect(parsed.callee_number_type).toBe('UNKNOWN');
|
||||
});
|
||||
|
||||
it('creates the consumer group idempotently', async () => {
|
||||
const redis = new FakeRedis();
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ export interface CdrStreamEvent {
|
||||
source_ip: string;
|
||||
caller: string;
|
||||
callee: string;
|
||||
callee_city_code: string;
|
||||
callee_city_name: string;
|
||||
callee_province_name: string;
|
||||
callee_operator: string;
|
||||
callee_number_type: string;
|
||||
vendor_id: string;
|
||||
vendor_gateway_id: string;
|
||||
line_group_id: string;
|
||||
@@ -69,6 +74,11 @@ export interface CdrStreamPublishInput {
|
||||
sourceIp: string;
|
||||
caller?: string;
|
||||
callee?: string;
|
||||
calleeCityCode?: string;
|
||||
calleeCityName?: string;
|
||||
calleeProvinceName?: string;
|
||||
calleeOperator?: string;
|
||||
calleeNumberType?: string;
|
||||
vendorId?: string;
|
||||
vendorGatewayId?: string;
|
||||
lineGroupId?: string;
|
||||
@@ -154,6 +164,11 @@ export function buildCdrStreamEvent(input: CdrStreamPublishInput): CdrStreamEven
|
||||
source_ip: input.sourceIp,
|
||||
caller: input.caller ?? '',
|
||||
callee: input.callee ?? '',
|
||||
callee_city_code: input.calleeCityCode ?? '',
|
||||
callee_city_name: input.calleeCityName ?? '',
|
||||
callee_province_name: input.calleeProvinceName ?? '',
|
||||
callee_operator: input.calleeOperator ?? 'UNKNOWN',
|
||||
callee_number_type: input.calleeNumberType ?? 'UNKNOWN',
|
||||
vendor_id: input.vendorId ?? '',
|
||||
vendor_gateway_id: input.vendorGatewayId ?? '',
|
||||
line_group_id: input.lineGroupId ?? '',
|
||||
@@ -220,6 +235,11 @@ export function parseCdrStreamEvent(fields: string[]): ParsedCdrStreamEvent {
|
||||
source_ip: parsed.source_ip,
|
||||
caller: parsed.caller ?? '',
|
||||
callee: parsed.callee ?? '',
|
||||
callee_city_code: parsed.callee_city_code ?? '',
|
||||
callee_city_name: parsed.callee_city_name ?? '',
|
||||
callee_province_name: parsed.callee_province_name ?? '',
|
||||
callee_operator: parsed.callee_operator ?? 'UNKNOWN',
|
||||
callee_number_type: parsed.callee_number_type ?? 'UNKNOWN',
|
||||
vendor_id: parsed.vendor_id ?? '',
|
||||
vendor_gateway_id: parsed.vendor_gateway_id ?? '',
|
||||
line_group_id: parsed.line_group_id ?? '',
|
||||
|
||||
Reference in New Issue
Block a user