import { PageDataFetcher } from '@/lib/page/PageDataFetcher'; import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens'; import { DriverService } from '@/lib/services/drivers/DriverService'; import type { DriverProfileViewModelData } from '@/lib/view-models/DriverProfileViewModel'; // ============================================================================ // TYPES // ============================================================================ export type PageQueryResult = | { status: 'ok'; dto: DriverProfileViewModelData } | { status: 'notFound' } | { status: 'redirect'; to: string } | { status: 'error'; errorId: string }; // ============================================================================ // SERVER QUERY CLASS // ============================================================================ /** * ProfilePageQuery * * Server-side data fetcher for the profile page. * Returns a discriminated union with all possible page states. * Ensures JSON-serializable DTO with no null leakage. */ export class ProfilePageQuery { /** * Execute the profile page query * * @param driverId - The driver ID to fetch profile for * @returns PageQueryResult with discriminated union of states */ static async execute(driverId: string | null): Promise { // Handle missing driver ID if (!driverId) { return { status: 'notFound' }; } try { // Fetch using PageDataFetcher to avoid direct DI in page const driverService = await PageDataFetcher.fetchManual(async () => { const container = (await import('@/lib/di/container')).ContainerManager.getInstance().getContainer(); return container.get(DRIVER_SERVICE_TOKEN); }); if (!driverService) { return { status: 'error', errorId: 'SERVICE_UNAVAILABLE' }; } const viewModel = await driverService.getDriverProfile(driverId); // Convert to DTO and ensure JSON-serializable const dto = this.toSerializableDTO(viewModel.toDTO()); if (!dto.currentDriver) { return { status: 'notFound' }; } return { status: 'ok', dto }; } catch (error) { console.error('ProfilePageQuery failed:', error); return { status: 'error', errorId: 'FETCH_FAILED' }; } } /** * Convert DTO to ensure JSON-serializability * - Dates become ISO strings * - Undefined becomes null * - No Date objects remain */ private static toSerializableDTO(dto: DriverProfileViewModelData): DriverProfileViewModelData { return { currentDriver: dto.currentDriver ? { ...dto.currentDriver, joinedAt: dto.currentDriver.joinedAt, // Already ISO string } : null, stats: dto.stats ? { ...dto.stats, // Ensure all nullable numbers are properly handled avgFinish: dto.stats.avgFinish ?? null, bestFinish: dto.stats.bestFinish ?? null, worstFinish: dto.stats.worstFinish ?? null, finishRate: dto.stats.finishRate ?? null, winRate: dto.stats.winRate ?? null, podiumRate: dto.stats.podiumRate ?? null, percentile: dto.stats.percentile ?? null, rating: dto.stats.rating ?? null, consistency: dto.stats.consistency ?? null, overallRank: dto.stats.overallRank ?? null, } : null, finishDistribution: dto.finishDistribution ? { ...dto.finishDistribution } : null, teamMemberships: dto.teamMemberships.map(m => ({ ...m, joinedAt: m.joinedAt, // Already ISO string })), socialSummary: { friendsCount: dto.socialSummary.friendsCount, friends: dto.socialSummary.friends.map(f => ({ ...f, })), }, extendedProfile: dto.extendedProfile ? { ...dto.extendedProfile, achievements: dto.extendedProfile.achievements.map(a => ({ ...a, earnedAt: a.earnedAt, // Already ISO string })), } : null, }; } }