Assist_Design/apps/bff/src/mappings/validation/mapping-validator.service.ts

298 lines
8.1 KiB
TypeScript
Raw Normal View History

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,
MappingValidationResult,
2025-08-21 15:24:40 +09:00
UserIdMapping,
} from "../types/mapping.types";
@Injectable()
export class MappingValidatorService {
2025-08-22 17:02:49 +09:00
constructor(@Inject(Logger) private readonly logger: Logger) {}
/**
* Validate create mapping request
*/
2025-08-22 17:02:49 +09:00
validateCreateRequest(request: CreateMappingRequest): MappingValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Validate user ID
if (!request.userId) {
2025-08-21 15:24:40 +09:00
errors.push("User ID is required");
} else if (!this.isValidUuid(request.userId)) {
2025-08-21 15:24:40 +09:00
errors.push("User ID must be a valid UUID");
}
// Validate WHMCS client ID
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");
}
// Validate Salesforce account ID (optional)
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");
}
} else {
2025-08-22 17:02:49 +09:00
warnings.push("Salesforce account ID not provided - mapping will be incomplete");
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate update mapping request
*/
2025-08-22 17:02:49 +09:00
validateUpdateRequest(userId: string, request: UpdateMappingRequest): MappingValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Validate user ID
if (!userId) {
2025-08-21 15:24:40 +09:00
errors.push("User ID is required");
} else if (!this.isValidUuid(userId)) {
2025-08-21 15:24:40 +09:00
errors.push("User ID must be a valid UUID");
}
// Check if there's something to update
if (!request.whmcsClientId && !request.sfAccountId) {
2025-08-21 15:24:40 +09:00
errors.push("At least one field must be provided for update");
}
// Validate WHMCS client ID (if provided)
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");
}
}
// Validate Salesforce account ID (if provided)
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");
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate existing mapping for consistency
*/
validateExistingMapping(mapping: UserIdMapping): MappingValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Validate user ID
if (!mapping.userId || !this.isValidUuid(mapping.userId)) {
2025-08-21 15:24:40 +09:00
errors.push("Invalid user ID in existing mapping");
}
// Validate WHMCS client ID
2025-08-21 15:24:40 +09:00
if (
!mapping.whmcsClientId ||
!Number.isInteger(mapping.whmcsClientId) ||
mapping.whmcsClientId < 1
) {
errors.push("Invalid WHMCS client ID in existing mapping");
}
// Validate Salesforce account ID (if present)
if (mapping.sfAccountId && !this.isValidSalesforceId(mapping.sfAccountId)) {
2025-08-21 15:24:40 +09:00
errors.push("Invalid Salesforce account ID in existing mapping");
}
// Check completeness
if (!mapping.sfAccountId) {
2025-08-21 15:24:40 +09:00
warnings.push("Mapping is missing Salesforce account ID");
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate array of mappings for bulk operations
*/
validateBulkMappings(mappings: CreateMappingRequest[]): Array<{
index: number;
validation: MappingValidationResult;
}> {
return mappings.map((mapping, index) => ({
index,
validation: this.validateCreateRequest(mapping),
}));
}
/**
* Check for potential conflicts
*/
async validateNoConflicts(
request: CreateMappingRequest,
2025-08-22 17:02:49 +09:00
existingMappings: UserIdMapping[]
): Promise<MappingValidationResult> {
const errors: string[] = [];
const warnings: string[] = [];
// Check for duplicate user ID
2025-08-22 17:02:49 +09:00
const duplicateUser = existingMappings.find(m => m.userId === request.userId);
if (duplicateUser) {
errors.push(`User ${request.userId} already has a mapping`);
}
// Check for duplicate WHMCS client ID
2025-08-22 17:02:49 +09:00
const duplicateWhmcs = existingMappings.find(m => m.whmcsClientId === request.whmcsClientId);
if (duplicateWhmcs) {
2025-08-21 15:24:40 +09:00
errors.push(
2025-08-22 17:02:49 +09:00
`WHMCS client ${request.whmcsClientId} is already mapped to user ${duplicateWhmcs.userId}`
2025-08-21 15:24:40 +09:00
);
}
// Check for duplicate Salesforce account ID
if (request.sfAccountId) {
2025-08-22 17:02:49 +09:00
const duplicateSf = existingMappings.find(m => m.sfAccountId === request.sfAccountId);
if (duplicateSf) {
2025-08-21 15:24:40 +09:00
warnings.push(
2025-08-22 17:02:49 +09:00
`Salesforce account ${request.sfAccountId} is already mapped to user ${duplicateSf.userId}`
2025-08-21 15:24:40 +09:00
);
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate mapping before deletion
*/
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");
return { isValid: false, errors, warnings };
}
// Warning about data impact
2025-08-21 15:24:40 +09:00
warnings.push(
2025-08-22 17:02:49 +09:00
"Deleting this mapping will prevent access to WHMCS/Salesforce data for this user"
2025-08-21 15:24:40 +09:00
);
if (mapping.sfAccountId) {
2025-08-21 15:24:40 +09:00
warnings.push(
2025-08-22 17:02:49 +09:00
"This mapping includes Salesforce integration - deletion will affect case management"
2025-08-21 15:24:40 +09:00
);
}
return {
isValid: true,
errors,
warnings,
};
}
/**
* Sanitize mapping data for safe storage
*/
sanitizeCreateRequest(request: CreateMappingRequest): CreateMappingRequest {
return {
userId: request.userId?.trim(),
whmcsClientId: Number(request.whmcsClientId),
sfAccountId: request.sfAccountId?.trim() || undefined,
};
}
/**
* Sanitize update request
*/
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 validation helpers
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;
return uuidRegex.test(uuid);
}
private isValidSalesforceId(sfId: string): boolean {
// Salesforce IDs are 15 or 18 characters long
// 15-character: case-sensitive
// 18-character: case-insensitive (includes checksum)
if (!sfId) return false;
2025-08-21 15:24:40 +09:00
const sfIdRegex = /^[a-zA-Z0-9]{15}$|^[a-zA-Z0-9]{18}$/;
return sfIdRegex.test(sfId);
}
/**
* Get validation summary for logging
*/
getValidationSummary(validation: MappingValidationResult): string {
const parts: string[] = [];
2025-08-21 15:24:40 +09:00
if (validation.isValid) {
2025-08-21 15:24:40 +09:00
parts.push("✓ Valid");
} else {
2025-08-21 15:24:40 +09:00
parts.push("✗ Invalid");
}
if (validation.errors.length > 0) {
parts.push(`${validation.errors.length} error(s)`);
}
if (validation.warnings.length > 0) {
parts.push(`${validation.warnings.length} warning(s)`);
}
2025-08-21 15:24:40 +09:00
return parts.join(", ");
}
/**
* Log validation result
*/
2025-08-22 17:02:49 +09:00
logValidationResult(operation: string, validation: MappingValidationResult, context?: any): void {
const summary = this.getValidationSummary(validation);
2025-08-21 15:24:40 +09:00
if (validation.isValid) {
this.logger.debug(`${operation} validation: ${summary}`, context);
} else {
this.logger.warn(`${operation} validation failed: ${summary}`, {
...context,
errors: validation.errors,
warnings: validation.warnings,
});
}
}
}