website refactor
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { isProductionEnvironment } from '@/lib/config/env';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import type { Service } from '@/lib/contracts/services/Service';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
|
||||
|
||||
type DriverProfileReadServiceError = 'notFound' | 'unauthorized' | 'serverError' | 'unknown';
|
||||
|
||||
export class DriverProfileReadService implements Service {
|
||||
async getDriverProfile(driverId: string): Promise<Result<GetDriverProfileOutputDTO, DriverProfileReadServiceError>> {
|
||||
const logger = new ConsoleLogger();
|
||||
|
||||
try {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: isProductionEnvironment(),
|
||||
});
|
||||
|
||||
const apiClient = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
const dto = await apiClient.getDriverProfile(driverId);
|
||||
|
||||
if (!dto.currentDriver) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
return Result.ok(dto);
|
||||
} catch (error) {
|
||||
const errorAny = error as { statusCode?: number; message?: string };
|
||||
|
||||
if (errorAny.statusCode === 401 || errorAny.message?.toLowerCase().includes('unauthorized')) {
|
||||
return Result.err('unauthorized');
|
||||
}
|
||||
|
||||
if (errorAny.statusCode === 404 || errorAny.message?.toLowerCase().includes('not found')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
logger.error(
|
||||
'DriverProfileReadService failed',
|
||||
error instanceof Error ? error : undefined,
|
||||
{ error: errorAny }
|
||||
);
|
||||
|
||||
if (errorAny.statusCode && errorAny.statusCode >= 500) {
|
||||
return Result.err('serverError');
|
||||
}
|
||||
|
||||
return Result.err('unknown');
|
||||
}
|
||||
}
|
||||
}
|
||||
52
apps/website/lib/services/drivers/DriverProfileService.ts
Normal file
52
apps/website/lib/services/drivers/DriverProfileService.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { isProductionEnvironment } from '@/lib/config/env';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import type { Service } from '@/lib/contracts/services/Service';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
|
||||
|
||||
type DriverProfileServiceError = 'notFound' | 'unauthorized' | 'serverError' | 'unknown';
|
||||
|
||||
export class DriverProfileService implements Service {
|
||||
async getDriverProfile(driverId: string): Promise<Result<GetDriverProfileOutputDTO, DriverProfileServiceError>> {
|
||||
const logger = new ConsoleLogger();
|
||||
|
||||
try {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: isProductionEnvironment(),
|
||||
});
|
||||
|
||||
const apiClient = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
const dto = await apiClient.getDriverProfile(driverId);
|
||||
|
||||
if (!dto.currentDriver) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
return Result.ok(dto);
|
||||
} catch (error) {
|
||||
const errorAny = error as { statusCode?: number; message?: string };
|
||||
|
||||
if (errorAny.statusCode === 401 || errorAny.message?.toLowerCase().includes('unauthorized')) {
|
||||
return Result.err('unauthorized');
|
||||
}
|
||||
|
||||
if (errorAny.statusCode === 404 || errorAny.message?.toLowerCase().includes('not found')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
logger.error('DriverProfileService failed', error instanceof Error ? error : undefined, { error: errorAny });
|
||||
|
||||
if (errorAny.statusCode && errorAny.statusCode >= 500) {
|
||||
return Result.err('serverError');
|
||||
}
|
||||
|
||||
return Result.err('unknown');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { isProductionEnvironment } from '@/lib/config/env';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import type { DomainError, Service } from '@/lib/contracts/services/Service';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
|
||||
|
||||
export class DriverProfileUpdateService implements Service {
|
||||
private readonly apiClient: DriversApiClient;
|
||||
|
||||
constructor() {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: isProductionEnvironment(),
|
||||
});
|
||||
|
||||
this.apiClient = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
}
|
||||
|
||||
async updateProfile(
|
||||
updates: { bio?: string; country?: string },
|
||||
): Promise<Result<DriverDTO, DomainError>> {
|
||||
try {
|
||||
const dto = await this.apiClient.updateProfile(updates);
|
||||
return Result.ok(dto);
|
||||
} catch {
|
||||
return Result.err({ type: 'unknown', message: 'DRIVER_PROFILE_UPDATE_FAILED' });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import type { CompleteOnboardingInputDTO } from '@/lib/types/generated/CompleteOnboardingInputDTO';
|
||||
import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO';
|
||||
import { CompleteOnboardingViewModel } from "@/lib/view-models/CompleteOnboardingViewModel";
|
||||
import { DriverLeaderboardViewModel } from "@/lib/view-models/DriverLeaderboardViewModel";
|
||||
import { DriverViewModel } from "@/lib/view-models/DriverViewModel";
|
||||
import { DriverProfileViewModel } from "@/lib/view-models/DriverProfileViewModel";
|
||||
import type { CompleteOnboardingOutputDTO } from '@/lib/types/generated/CompleteOnboardingOutputDTO';
|
||||
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
|
||||
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
|
||||
|
||||
/**
|
||||
* Driver Service
|
||||
* Driver Service - DTO Only
|
||||
*
|
||||
* Orchestrates driver operations by coordinating API calls and view model creation.
|
||||
* All dependencies are injected via constructor.
|
||||
* Returns raw API DTOs. No ViewModels or UX logic.
|
||||
* All client-side presentation logic must be handled by hooks/components.
|
||||
*/
|
||||
export class DriverService {
|
||||
constructor(
|
||||
@@ -18,143 +17,49 @@ export class DriverService {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get driver leaderboard with view model transformation
|
||||
* Get driver leaderboard (returns DTO)
|
||||
*/
|
||||
async getDriverLeaderboard(): Promise<DriverLeaderboardViewModel> {
|
||||
const dto = await this.apiClient.getLeaderboard();
|
||||
return new DriverLeaderboardViewModel(dto);
|
||||
async getDriverLeaderboard() {
|
||||
return this.apiClient.getLeaderboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete driver onboarding with view model transformation
|
||||
* Complete driver onboarding (returns DTO)
|
||||
*/
|
||||
async completeDriverOnboarding(input: CompleteOnboardingInputDTO): Promise<CompleteOnboardingViewModel> {
|
||||
const dto = await this.apiClient.completeOnboarding(input);
|
||||
return new CompleteOnboardingViewModel(dto);
|
||||
async completeDriverOnboarding(input: CompleteOnboardingInputDTO): Promise<CompleteOnboardingOutputDTO> {
|
||||
return this.apiClient.completeOnboarding(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current driver with view model transformation
|
||||
*/
|
||||
async getCurrentDriver(): Promise<DriverViewModel | null> {
|
||||
const dto = await this.apiClient.getCurrent();
|
||||
if (!dto) {
|
||||
return null;
|
||||
}
|
||||
return new DriverViewModel({ ...dto, avatarUrl: dto.avatarUrl ?? null });
|
||||
* Get current driver (returns DTO)
|
||||
*/
|
||||
async getCurrentDriver(): Promise<DriverDTO | null> {
|
||||
return this.apiClient.getCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driver profile with full details and view model transformation
|
||||
*/
|
||||
async getDriverProfile(driverId: string): Promise<DriverProfileViewModel> {
|
||||
const dto = await this.apiClient.getDriverProfile(driverId);
|
||||
return new DriverProfileViewModel({
|
||||
currentDriver: dto.currentDriver
|
||||
? {
|
||||
id: dto.currentDriver.id,
|
||||
name: dto.currentDriver.name,
|
||||
country: dto.currentDriver.country,
|
||||
avatarUrl: dto.currentDriver.avatarUrl || '',
|
||||
iracingId: dto.currentDriver.iracingId ?? null,
|
||||
joinedAt: dto.currentDriver.joinedAt,
|
||||
rating: dto.currentDriver.rating ?? null,
|
||||
globalRank: dto.currentDriver.globalRank ?? null,
|
||||
consistency: dto.currentDriver.consistency ?? null,
|
||||
bio: dto.currentDriver.bio ?? null,
|
||||
totalDrivers: dto.currentDriver.totalDrivers ?? null,
|
||||
}
|
||||
: null,
|
||||
stats: dto.stats
|
||||
? {
|
||||
totalRaces: dto.stats.totalRaces,
|
||||
wins: dto.stats.wins,
|
||||
podiums: dto.stats.podiums,
|
||||
dnfs: dto.stats.dnfs,
|
||||
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
|
||||
? {
|
||||
totalRaces: dto.finishDistribution.totalRaces,
|
||||
wins: dto.finishDistribution.wins,
|
||||
podiums: dto.finishDistribution.podiums,
|
||||
topTen: dto.finishDistribution.topTen,
|
||||
dnfs: dto.finishDistribution.dnfs,
|
||||
other: dto.finishDistribution.other,
|
||||
}
|
||||
: null,
|
||||
teamMemberships: dto.teamMemberships.map((m) => ({
|
||||
teamId: m.teamId,
|
||||
teamName: m.teamName,
|
||||
teamTag: m.teamTag ?? null,
|
||||
role: m.role,
|
||||
joinedAt: m.joinedAt,
|
||||
isCurrent: m.isCurrent,
|
||||
})),
|
||||
socialSummary: {
|
||||
friendsCount: dto.socialSummary.friendsCount,
|
||||
friends: dto.socialSummary.friends.map((f) => ({
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
country: f.country,
|
||||
avatarUrl: f.avatarUrl || '',
|
||||
})),
|
||||
},
|
||||
extendedProfile: dto.extendedProfile
|
||||
? {
|
||||
socialHandles: dto.extendedProfile.socialHandles.map((h) => ({
|
||||
platform: h.platform as 'twitter' | 'youtube' | 'twitch' | 'discord',
|
||||
handle: h.handle,
|
||||
url: h.url,
|
||||
})),
|
||||
achievements: dto.extendedProfile.achievements.map((a) => ({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
description: a.description,
|
||||
icon: a.icon as 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap',
|
||||
rarity: a.rarity as 'common' | 'rare' | 'epic' | 'legendary',
|
||||
earnedAt: a.earnedAt,
|
||||
})),
|
||||
racingStyle: dto.extendedProfile.racingStyle,
|
||||
favoriteTrack: dto.extendedProfile.favoriteTrack,
|
||||
favoriteCar: dto.extendedProfile.favoriteCar,
|
||||
timezone: dto.extendedProfile.timezone,
|
||||
availableHours: dto.extendedProfile.availableHours,
|
||||
lookingForTeam: dto.extendedProfile.lookingForTeam,
|
||||
openToRequests: dto.extendedProfile.openToRequests,
|
||||
}
|
||||
: null,
|
||||
});
|
||||
* Get driver profile (returns DTO)
|
||||
*/
|
||||
async getDriverProfile(driverId: string): Promise<GetDriverProfileOutputDTO> {
|
||||
return this.apiClient.getDriverProfile(driverId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current driver profile with view model transformation
|
||||
*/
|
||||
async updateProfile(updates: { bio?: string; country?: string }): Promise<DriverProfileViewModel> {
|
||||
const dto = await this.apiClient.updateProfile(updates);
|
||||
// After updating, get the full profile again to return updated view model
|
||||
return this.getDriverProfile(dto.id);
|
||||
}
|
||||
* Update current driver profile (returns DTO)
|
||||
*/
|
||||
async updateProfile(updates: { bio?: string; country?: string }): Promise<DriverDTO> {
|
||||
return this.apiClient.updateProfile(updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find driver by ID
|
||||
* Find driver by ID (returns DTO)
|
||||
*/
|
||||
async findById(id: string): Promise<GetDriverOutputDTO | null> {
|
||||
return this.apiClient.getDriver(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find multiple drivers by IDs
|
||||
* Find multiple drivers by IDs (returns DTOs)
|
||||
*/
|
||||
async findByIds(ids: string[]): Promise<GetDriverOutputDTO[]> {
|
||||
const drivers = await Promise.all(ids.map(id => this.apiClient.getDriver(id)));
|
||||
|
||||
Reference in New Issue
Block a user