import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { CUSTOMER_GATEWAY_POLICIES_REPOSITORY, type CreatePolicyInput, type CustomerGatewayPoliciesRepository, type CustomerGatewayPolicySummary, type PolicyMatchMode, type PolicyStatus, type UpdatePolicyInput } from './customer-gateway-policies.repository.js'; interface CreatePolicyDto { lineGroupId?: unknown; name?: unknown; priority?: unknown; callerMode?: unknown; callerValue?: unknown; calleeMode?: unknown; calleeValue?: unknown; status?: unknown; } interface UpdatePolicyDto { lineGroupId?: unknown; name?: unknown; priority?: unknown; callerMode?: unknown; callerValue?: unknown; calleeMode?: unknown; calleeValue?: unknown; status?: unknown; } interface ReorderDto { policyIds?: unknown; } @Injectable() export class CustomerGatewayPoliciesService { constructor(@Inject(CUSTOMER_GATEWAY_POLICIES_REPOSITORY) private readonly policies: CustomerGatewayPoliciesRepository) {} list(gatewayId: string): Promise { return this.policies.list(gatewayId); } create(gatewayId: string, body: CreatePolicyDto, actorId?: string): Promise { const callerMode = body.callerMode === undefined ? 'ANY' : this.matchMode(body.callerMode, 'callerMode'); const calleeMode = body.calleeMode === undefined ? 'ANY' : this.matchMode(body.calleeMode, 'calleeMode'); const input: CreatePolicyInput = { gatewayId, lineGroupId: this.limitedString(body.lineGroupId, 'lineGroupId', 32), name: this.limitedString(body.name, 'name', 120), priority: body.priority === undefined ? undefined : this.priority(body.priority), callerMode, callerValue: this.matchValue(callerMode, body.callerValue, 'callerValue'), calleeMode, calleeValue: this.matchValue(calleeMode, body.calleeValue, 'calleeValue'), status: body.status === undefined ? 'ENABLED' : this.status(body.status), actorId }; return this.policies.create(input); } update(policyId: string, body: UpdatePolicyDto, actorId?: string): Promise { const callerMode = body.callerMode === undefined ? undefined : this.matchMode(body.callerMode, 'callerMode'); const calleeMode = body.calleeMode === undefined ? undefined : this.matchMode(body.calleeMode, 'calleeMode'); const input: UpdatePolicyInput = { lineGroupId: body.lineGroupId === undefined ? undefined : this.limitedString(body.lineGroupId, 'lineGroupId', 32), name: body.name === undefined ? undefined : this.limitedString(body.name, 'name', 120), priority: body.priority === undefined ? undefined : this.priority(body.priority), callerMode, callerValue: callerMode === undefined ? (body.callerValue === undefined ? undefined : this.nullableString(body.callerValue, 'callerValue', 64)) : this.matchValue(callerMode, body.callerValue, 'callerValue'), calleeMode, calleeValue: calleeMode === undefined ? (body.calleeValue === undefined ? undefined : this.nullableString(body.calleeValue, 'calleeValue', 64)) : this.matchValue(calleeMode, body.calleeValue, 'calleeValue'), status: body.status === undefined ? undefined : this.status(body.status), actorId }; return this.policies.update(policyId, input); } remove(policyId: string, actorId?: string): Promise { return this.policies.softDelete(policyId, actorId); } reorder(gatewayId: string, body: ReorderDto, actorId?: string): Promise { if (!Array.isArray(body.policyIds) || body.policyIds.length === 0) { throw new BadRequestException({ code: 'POLICY_IDS_INVALID', message: 'policyIds must be a non-empty array.' }); } return this.policies.reorder( gatewayId, body.policyIds.map((id) => this.limitedString(id, 'policyIds', 32)), 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.` }); } const trimmed = value.trim(); if (trimmed.length > maxLength) { throw new BadRequestException({ code: 'VALIDATION_ERROR', message: `${field} is too long.` }); } return trimmed; } private nullableString(value: unknown, field: string, maxLength: number): string | null { if (value === null) { return null; } return this.limitedString(value, field, maxLength); } private matchMode(value: unknown, field: string): PolicyMatchMode { if (value !== 'ANY' && value !== 'EQUALS' && value !== 'PREFIX') { throw new BadRequestException({ code: 'MATCH_MODE_INVALID', message: `${field} is invalid.` }); } return value; } private matchValue(mode: PolicyMatchMode, value: unknown, field: string): string | null { if (mode === 'ANY') { return null; } const text = this.limitedString(value, field, 64); if (!/^[0-9A-Za-z+*#.-]+$/.test(text)) { throw new BadRequestException({ code: 'MATCH_VALUE_INVALID', message: `${field} contains invalid characters.` }); } return text; } private priority(value: unknown): number { if (typeof value !== 'number' || !Number.isInteger(value) || value < 1 || value > 10000) { throw new BadRequestException({ code: 'PRIORITY_INVALID', message: 'priority must be an integer from 1 to 10000.' }); } return value; } private status(value: unknown): PolicyStatus { if (value !== 'ENABLED' && value !== 'DISABLED') { throw new BadRequestException({ code: 'STATUS_INVALID', message: 'Status is invalid.' }); } return value; } }