2025-08-22 17:02:49 +09:00
|
|
|
import { Injectable, Inject } from "@nestjs/common";
|
|
|
|
|
import { Logger } from "nestjs-pino";
|
2025-08-21 15:24:40 +09:00
|
|
|
import {
|
|
|
|
|
CreateMappingRequest,
|
|
|
|
|
UpdateMappingRequest,
|
2025-08-20 18:02:50 +09:00
|
|
|
MappingValidationResult,
|
2025-08-21 15:24:40 +09:00
|
|
|
UserIdMapping,
|
|
|
|
|
} from "../types/mapping.types";
|
2025-08-20 18:02:50 +09:00
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class MappingValidatorService {
|
2025-08-22 17:02:49 +09:00
|
|
|
constructor(@Inject(Logger) private readonly logger: Logger) {}
|
2025-08-20 18:02:50 +09:00
|
|
|
|
2025-08-22 17:02:49 +09:00
|
|
|
validateCreateRequest(request: CreateMappingRequest): MappingValidationResult {
|
2025-08-20 18:02:50 +09:00
|
|
|
const errors: string[] = [];
|
|
|
|
|
const warnings: string[] = [];
|
|
|
|
|
if (!request.userId) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("User ID is required");
|
2025-08-20 18:02:50 +09:00
|
|
|
} else if (!this.isValidUuid(request.userId)) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("User ID must be a valid UUID");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
if (!request.whmcsClientId) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("WHMCS client ID is required");
|
2025-08-22 17:02:49 +09:00
|
|
|
} else if (!Number.isInteger(request.whmcsClientId) || request.whmcsClientId < 1) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("WHMCS client ID must be a positive integer");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
if (request.sfAccountId) {
|
|
|
|
|
if (!this.isValidSalesforceId(request.sfAccountId)) {
|
2025-08-22 17:02:49 +09:00
|
|
|
errors.push("Salesforce account ID must be a valid 15 or 18 character ID");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-08-22 17:02:49 +09:00
|
|
|
warnings.push("Salesforce account ID not provided - mapping will be incomplete");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
2025-09-17 18:43:43 +09:00
|
|
|
return { isValid: errors.length === 0, errors, warnings };
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-22 17:02:49 +09:00
|
|
|
validateUpdateRequest(userId: string, request: UpdateMappingRequest): MappingValidationResult {
|
2025-08-20 18:02:50 +09:00
|
|
|
const errors: string[] = [];
|
|
|
|
|
const warnings: string[] = [];
|
|
|
|
|
if (!userId) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("User ID is required");
|
2025-08-20 18:02:50 +09:00
|
|
|
} else if (!this.isValidUuid(userId)) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("User ID must be a valid UUID");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
if (!request.whmcsClientId && !request.sfAccountId) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("At least one field must be provided for update");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
if (request.whmcsClientId !== undefined) {
|
2025-08-22 17:02:49 +09:00
|
|
|
if (!Number.isInteger(request.whmcsClientId) || request.whmcsClientId < 1) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("WHMCS client ID must be a positive integer");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (request.sfAccountId !== undefined) {
|
2025-08-22 17:02:49 +09:00
|
|
|
if (request.sfAccountId && !this.isValidSalesforceId(request.sfAccountId)) {
|
|
|
|
|
errors.push("Salesforce account ID must be a valid 15 or 18 character ID");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-17 18:43:43 +09:00
|
|
|
return { isValid: errors.length === 0, errors, warnings };
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validateExistingMapping(mapping: UserIdMapping): MappingValidationResult {
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
|
const warnings: string[] = [];
|
|
|
|
|
if (!mapping.userId || !this.isValidUuid(mapping.userId)) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("Invalid user ID in existing mapping");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
2025-09-17 18:43:43 +09:00
|
|
|
if (!mapping.whmcsClientId || !Number.isInteger(mapping.whmcsClientId) || mapping.whmcsClientId < 1) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("Invalid WHMCS client ID in existing mapping");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
if (mapping.sfAccountId && !this.isValidSalesforceId(mapping.sfAccountId)) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("Invalid Salesforce account ID in existing mapping");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
if (!mapping.sfAccountId) {
|
2025-08-21 15:24:40 +09:00
|
|
|
warnings.push("Mapping is missing Salesforce account ID");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
2025-09-17 18:43:43 +09:00
|
|
|
return { isValid: errors.length === 0, errors, warnings };
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-17 18:43:43 +09:00
|
|
|
validateBulkMappings(mappings: CreateMappingRequest[]): Array<{ index: number; validation: MappingValidationResult }> {
|
|
|
|
|
return mappings.map((mapping, index) => ({ index, validation: this.validateCreateRequest(mapping) }));
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-17 18:43:43 +09:00
|
|
|
validateNoConflicts(request: CreateMappingRequest, existingMappings: UserIdMapping[]): MappingValidationResult {
|
2025-08-20 18:02:50 +09:00
|
|
|
const errors: string[] = [];
|
|
|
|
|
const warnings: string[] = [];
|
2025-08-22 17:02:49 +09:00
|
|
|
const duplicateUser = existingMappings.find(m => m.userId === request.userId);
|
2025-08-20 18:02:50 +09:00
|
|
|
if (duplicateUser) {
|
|
|
|
|
errors.push(`User ${request.userId} already has a mapping`);
|
|
|
|
|
}
|
2025-08-22 17:02:49 +09:00
|
|
|
const duplicateWhmcs = existingMappings.find(m => m.whmcsClientId === request.whmcsClientId);
|
2025-08-20 18:02:50 +09:00
|
|
|
if (duplicateWhmcs) {
|
2025-09-17 18:43:43 +09:00
|
|
|
errors.push(`WHMCS client ${request.whmcsClientId} is already mapped to user ${duplicateWhmcs.userId}`);
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
if (request.sfAccountId) {
|
2025-08-22 17:02:49 +09:00
|
|
|
const duplicateSf = existingMappings.find(m => m.sfAccountId === request.sfAccountId);
|
2025-08-20 18:02:50 +09:00
|
|
|
if (duplicateSf) {
|
2025-09-17 18:43:43 +09:00
|
|
|
warnings.push(`Salesforce account ${request.sfAccountId} is already mapped to user ${duplicateSf.userId}`);
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-17 18:43:43 +09:00
|
|
|
return { isValid: errors.length === 0, errors, warnings };
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validateDeletion(mapping: UserIdMapping): MappingValidationResult {
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
|
const warnings: string[] = [];
|
|
|
|
|
if (!mapping) {
|
2025-08-21 15:24:40 +09:00
|
|
|
errors.push("Cannot delete non-existent mapping");
|
2025-08-20 18:02:50 +09:00
|
|
|
return { isValid: false, errors, warnings };
|
|
|
|
|
}
|
2025-09-17 18:43:43 +09:00
|
|
|
warnings.push("Deleting this mapping will prevent access to WHMCS/Salesforce data for this user");
|
2025-08-20 18:02:50 +09:00
|
|
|
if (mapping.sfAccountId) {
|
2025-09-17 18:43:43 +09:00
|
|
|
warnings.push("This mapping includes Salesforce integration - deletion will affect case management");
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
2025-09-17 18:43:43 +09:00
|
|
|
return { isValid: true, errors, warnings };
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sanitizeCreateRequest(request: CreateMappingRequest): CreateMappingRequest {
|
|
|
|
|
return {
|
|
|
|
|
userId: request.userId?.trim(),
|
|
|
|
|
whmcsClientId: Number(request.whmcsClientId),
|
|
|
|
|
sfAccountId: request.sfAccountId?.trim() || undefined,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sanitizeUpdateRequest(request: UpdateMappingRequest): UpdateMappingRequest {
|
|
|
|
|
const sanitized: UpdateMappingRequest = {};
|
|
|
|
|
if (request.whmcsClientId !== undefined) {
|
|
|
|
|
sanitized.whmcsClientId = Number(request.whmcsClientId);
|
|
|
|
|
}
|
|
|
|
|
if (request.sfAccountId !== undefined) {
|
|
|
|
|
sanitized.sfAccountId = request.sfAccountId?.trim() || undefined;
|
|
|
|
|
}
|
|
|
|
|
return sanitized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private isValidUuid(uuid: string): boolean {
|
2025-08-22 17:02:49 +09:00
|
|
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
2025-08-20 18:02:50 +09:00
|
|
|
return uuidRegex.test(uuid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private isValidSalesforceId(sfId: string): boolean {
|
|
|
|
|
if (!sfId) return false;
|
|
|
|
|
const sfIdRegex = /^[a-zA-Z0-9]{15}$|^[a-zA-Z0-9]{18}$/;
|
|
|
|
|
return sfIdRegex.test(sfId);
|
|
|
|
|
}
|
2025-09-17 18:43:43 +09:00
|
|
|
}
|
2025-08-20 18:02:50 +09:00
|
|
|
|
2025-08-21 15:24:40 +09:00
|
|
|
|