Files
gridpilot.gg/apps/website/lib/page-queries/ProfilePageQuery.ts
2026-01-11 13:04:33 +01:00

114 lines
3.9 KiB
TypeScript

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<PageQueryResult> {
// 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<DriverService>(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,
};
}
}