/** * Tenant Mapper * * Handles bidirectional transformations between Tenant domain entities and DTOs. * Uses Zod schemas from @archer/api-interface for validation. * * Responsibilities: * - Transform API DTOs to domain entities * - Transform domain entities to API DTOs * - Transform form requests to domain entities * - Validate all transformations using Zod schemas * * @layer Application */ import { IMapper } from './IMapper'; import { Tenant, TenantStatus } from '../../domain/entities/Tenant'; import { createTenantRequestSchema, updateTenantRequestSchema, type CreateTenantRequest, type UpdateTenantRequest, } from '@archer/api-interface/schemas/customer-api/request'; /** * TenantDTO * * Data Transfer Object for tenant persistence. * Matches localStorage storage format. */ export interface TenantDTO { id: string; companyId: string; name: string; domain: string; status: TenantStatus; integrationKeysCount: number; createdAt: string; updatedAt: string; deletedAt?: string; } /** * TenantMapper * * Static mapper for Tenant entity transformations. * Implements IMapper interface for consistency. */ export class TenantMapper implements IMapper { /** * Converts DTO to domain entity * * Transforms persisted data into Tenant domain entity. * Uses Tenant.fromPersistence factory method. * * @param dto - Data Transfer Object from storage * @returns Tenant domain entity * @throws Error if DTO is invalid or domain validation fails */ toDomain(dto: TenantDTO): Tenant { try { return Tenant.fromPersistence({ id: dto.id, companyId: dto.companyId, name: dto.name, domain: dto.domain, status: dto.status, integrationKeysCount: dto.integrationKeysCount, createdAt: dto.createdAt, updatedAt: dto.updatedAt, deletedAt: dto.deletedAt, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`Failed to map DTO to Tenant domain entity: ${message}`); } } /** * Converts domain entity to DTO * * Transforms Tenant domain entity to persistence format. * Uses entity's toPersistence method. * * @param entity - Tenant domain entity * @returns Data Transfer Object for storage */ toDTO(entity: Tenant): TenantDTO { return entity.toPersistence(); } /** * Converts create request to domain entity * * Validates and transforms tenant creation request to domain entity. * Uses Zod schema validation from @archer/api-interface. * * @param request - Tenant creation request data * @returns Tenant domain entity * @throws Error if validation fails */ fromCreateRequest(request: unknown): Tenant { try { // Validate request using Zod schema const validated = createTenantRequestSchema.parse(request) as CreateTenantRequest; // Generate new ID const id = crypto.randomUUID(); // Create domain entity using factory method return Tenant.create({ id, companyId: validated.companyId ?? '', // Will be set from URL path parameter name: validated.name, domain: validated.domain, status: (validated.status ?? 'ACTIVE') as TenantStatus, }); } catch (error) { if (error instanceof Error && 'issues' in error) { // Zod validation error const zodError = error as { issues: Array<{ path: string[]; message: string }> }; const messages = zodError.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`); throw new Error(`Validation failed: ${messages.join(', ')}`); } const message = error instanceof Error ? error.message : String(error); throw new Error(`Failed to create Tenant from request: ${message}`); } } /** * Converts update request to partial domain data * * Validates and transforms tenant update request. * Returns only the fields that should be updated. * Uses Zod schema validation from @archer/api-interface. * * @param request - Tenant update request data * @returns Partial tenant update data * @throws Error if validation fails */ fromUpdateRequest(request: unknown): { name?: string; status?: TenantStatus; } { try { // Validate request using Zod schema const validated = updateTenantRequestSchema.parse(request) as UpdateTenantRequest; // Return only provided fields const updates: { name?: string; status?: TenantStatus; } = {}; if (validated.name !== undefined) { updates.name = validated.name; } if (validated.status !== undefined) { updates.status = validated.status as TenantStatus; } return updates; } catch (error) { if (error instanceof Error && 'issues' in error) { // Zod validation error const zodError = error as { issues: Array<{ path: string[]; message: string }> }; const messages = zodError.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`); throw new Error(`Validation failed: ${messages.join(', ')}`); } const message = error instanceof Error ? error.message : String(error); throw new Error(`Failed to validate update request: ${message}`); } } /** * Batch conversion: DTOs to domain entities * * @param dtos - Array of DTOs * @returns Array of Tenant domain entities */ toDomainArray(dtos: TenantDTO[]): Tenant[] { return dtos.map((dto) => this.toDomain(dto)); } /** * Batch conversion: domain entities to DTOs * * @param entities - Array of Tenant domain entities * @returns Array of DTOs */ toDTOArray(entities: Tenant[]): TenantDTO[] { return entities.map((entity) => this.toDTO(entity)); } } /** * Singleton instance for reuse */ export const tenantMapper = new TenantMapper();