204 lines
5.7 KiB
TypeScript
Raw Normal View History

/**
* ID Mapping Domain - Validation
*
* Pure business validation functions for ID mappings.
* These functions contain no infrastructure dependencies (no DB, no HTTP, no logging).
*/
import { z } from "zod";
import {
createMappingRequestSchema,
updateMappingRequestSchema,
userIdMappingSchema,
} from "./schema";
import type {
CreateMappingRequest,
UpdateMappingRequest,
UserIdMapping,
MappingValidationResult,
} from "./contract";
/**
* Validate a create mapping request format
*/
export function validateCreateRequest(request: CreateMappingRequest): MappingValidationResult {
const validationResult = createMappingRequestSchema.safeParse(request);
if (validationResult.success) {
const warnings: string[] = [];
if (!request.sfAccountId) {
warnings.push("Salesforce account ID not provided - mapping will be incomplete");
}
return { isValid: true, errors: [], warnings };
}
const errors = validationResult.error.issues.map(issue => issue.message);
return { isValid: false, errors, warnings: [] };
}
/**
* Validate an update mapping request format
*/
export function validateUpdateRequest(
userId: string,
request: UpdateMappingRequest
): MappingValidationResult {
// First validate userId
const userIdValidation = z.string().uuid().safeParse(userId);
if (!userIdValidation.success) {
return {
isValid: false,
errors: ["User ID must be a valid UUID"],
warnings: [],
};
}
// Then validate the update request
const validationResult = updateMappingRequestSchema.safeParse(request);
if (validationResult.success) {
return { isValid: true, errors: [], warnings: [] };
}
const errors = validationResult.error.issues.map(issue => issue.message);
return { isValid: false, errors, warnings: [] };
}
/**
* Validate an existing mapping
*/
export function validateExistingMapping(mapping: UserIdMapping): MappingValidationResult {
const validationResult = userIdMappingSchema.safeParse(mapping);
if (validationResult.success) {
const warnings: string[] = [];
if (!mapping.sfAccountId) {
warnings.push("Mapping is missing Salesforce account ID");
}
return { isValid: true, errors: [], warnings };
}
const errors = validationResult.error.issues.map(issue => issue.message);
return { isValid: false, errors, warnings: [] };
}
/**
* Validate bulk mappings
*/
export function validateBulkMappings(
mappings: CreateMappingRequest[]
): Array<{ index: number; validation: MappingValidationResult }> {
return mappings.map((mapping, index) => ({
index,
validation: validateCreateRequest(mapping),
}));
}
/**
* Validate no conflicts exist with existing mappings
* Business rule: Each userId, whmcsClientId should be unique
*/
export function validateNoConflicts(
request: CreateMappingRequest,
existingMappings: UserIdMapping[]
): MappingValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// First validate the request format
const formatValidation = validateCreateRequest(request);
if (!formatValidation.isValid) {
return formatValidation;
}
// Check for conflicts
const duplicateUser = existingMappings.find(m => m.userId === request.userId);
if (duplicateUser) {
errors.push(`User ${request.userId} already has a mapping`);
}
const duplicateWhmcs = existingMappings.find(m => m.whmcsClientId === request.whmcsClientId);
if (duplicateWhmcs) {
errors.push(
`WHMCS client ${request.whmcsClientId} is already mapped to user ${duplicateWhmcs.userId}`
);
}
if (request.sfAccountId) {
const duplicateSf = existingMappings.find(m => m.sfAccountId === request.sfAccountId);
if (duplicateSf) {
warnings.push(
`Salesforce account ${request.sfAccountId} is already mapped to user ${duplicateSf.userId}`
);
}
}
return { isValid: errors.length === 0, errors, warnings };
}
/**
* Validate deletion constraints
* Business rule: Warn about data access impacts
*/
export function validateDeletion(mapping: UserIdMapping | null | undefined): MappingValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!mapping) {
errors.push("Cannot delete non-existent mapping");
return { isValid: false, errors, warnings };
}
// Validate the mapping format
const formatValidation = validateExistingMapping(mapping);
if (!formatValidation.isValid) {
return formatValidation;
}
warnings.push(
"Deleting this mapping will prevent access to WHMCS/Salesforce data for this user"
);
if (mapping.sfAccountId) {
warnings.push(
"This mapping includes Salesforce integration - deletion will affect case management"
);
}
return { isValid: true, errors, warnings };
}
/**
* Sanitize and normalize a create mapping request
*
* Note: This performs basic string trimming before validation.
* The schema handles validation; this is purely for data cleanup.
*/
export function sanitizeCreateRequest(request: CreateMappingRequest): CreateMappingRequest {
return {
userId: request.userId?.trim(),
whmcsClientId: request.whmcsClientId,
sfAccountId: request.sfAccountId?.trim() || undefined,
};
}
/**
* Sanitize and normalize an update mapping request
*
* Note: This performs basic string trimming before validation.
* The schema handles validation; this is purely for data cleanup.
*/
export function sanitizeUpdateRequest(request: UpdateMappingRequest): UpdateMappingRequest {
const sanitized: Partial<UpdateMappingRequest> = {};
if (request.whmcsClientId !== undefined) {
sanitized.whmcsClientId = request.whmcsClientId;
}
if (request.sfAccountId !== undefined) {
sanitized.sfAccountId = request.sfAccountId?.trim() || undefined;
}
return sanitized;
}