api client refactor
This commit is contained in:
@@ -9,7 +9,7 @@ import Breadcrumbs from '@/components/layout/Breadcrumbs';
|
||||
import ResultsTable from '@/components/races/ResultsTable';
|
||||
import ImportResultsForm from '@/components/races/ImportResultsForm';
|
||||
import QuickPenaltyModal from '@/components/leagues/QuickPenaltyModal';
|
||||
import { getRaceResults, getRaceSOF, importRaceResults } from '@/lib/services/races/RaceResultsService';
|
||||
import { raceResultsService } from '@/lib/services/races/RaceResultsService';
|
||||
import { useEffectiveDriverId } from '@/lib/currentDriver';
|
||||
import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles';
|
||||
import type { RaceResultsDetailViewModel } from '@/lib/view-models';
|
||||
@@ -32,12 +32,12 @@ export default function RaceResultsPage() {
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const raceData = await getRaceResults(raceId, currentDriverId);
|
||||
const raceData = await raceResultsService.getResultsDetail(raceId, currentDriverId);
|
||||
setRaceData(raceData);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const sofData = await getRaceSOF(raceId);
|
||||
const sofData = await raceResultsService.getWithSOF(raceId);
|
||||
setRaceSOF(sofData.strengthOfField);
|
||||
} catch (sofErr) {
|
||||
console.error('Failed to load SOF:', sofErr);
|
||||
@@ -70,7 +70,7 @@ export default function RaceResultsPage() {
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await importRaceResults(raceId, {
|
||||
await raceResultsService.importRaceResults(raceId, {
|
||||
resultsFileContent: JSON.stringify(importedResults), // Assuming the API expects JSON string
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
RegisterForRaceInputDto,
|
||||
ImportRaceResultsInputDto,
|
||||
ImportRaceResultsSummaryDto,
|
||||
WithdrawFromRaceInputDto,
|
||||
} from '../../dtos';
|
||||
|
||||
/**
|
||||
@@ -50,4 +51,19 @@ export class RacesApiClient extends BaseApiClient {
|
||||
importResults(raceId: string, input: ImportRaceResultsInputDto): Promise<ImportRaceResultsSummaryDto> {
|
||||
return this.post<ImportRaceResultsSummaryDto>(`/races/${raceId}/import-results`, input);
|
||||
}
|
||||
|
||||
/** Withdraw from race */
|
||||
withdraw(raceId: string, input: WithdrawFromRaceInputDto): Promise<void> {
|
||||
return this.post<void>(`/races/${raceId}/withdraw`, input);
|
||||
}
|
||||
|
||||
/** Cancel race */
|
||||
cancel(raceId: string): Promise<void> {
|
||||
return this.post<void>(`/races/${raceId}/cancel`, {});
|
||||
}
|
||||
|
||||
/** Complete race */
|
||||
complete(raceId: string): Promise<void> {
|
||||
return this.post<void>(`/races/${raceId}/complete`, {});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
import { RaceDetailDto } from '../dtos';
|
||||
import { RaceDetailViewModel } from '../view-models';
|
||||
|
||||
export class RaceDetailPresenter {
|
||||
present(dto: RaceDetailDto): RaceDetailViewModel {
|
||||
return new RaceDetailViewModel(dto);
|
||||
}
|
||||
}
|
||||
|
||||
export const presentRaceDetail = (dto: RaceDetailDto): RaceDetailViewModel => {
|
||||
return new RaceDetailViewModel(dto);
|
||||
const presenter = new RaceDetailPresenter();
|
||||
return presenter.present(dto);
|
||||
};
|
||||
43
apps/website/lib/presenters/RacePresenter.ts
Normal file
43
apps/website/lib/presenters/RacePresenter.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { RaceDetailViewModel } from '../view-models/RaceDetailViewModel';
|
||||
import type { RaceDetailDto } from '../dtos/RaceDetailDto';
|
||||
import type { RacesPageDataDto } from '../dtos/RacesPageDataDto';
|
||||
import type { RacesPageViewModel } from '../view-models/RacesPageViewModel';
|
||||
|
||||
/**
|
||||
* Race Presenter
|
||||
*
|
||||
* Stateless presenter that transforms race DTOs into view models.
|
||||
* All methods are pure functions with no side effects.
|
||||
*/
|
||||
export class RacePresenter {
|
||||
presentRaceDetail(dto: RaceDetailDto): RaceDetailViewModel {
|
||||
return new RaceDetailViewModel(dto);
|
||||
}
|
||||
|
||||
presentRacesPage(dto: RacesPageDataDto): RacesPageViewModel {
|
||||
return {
|
||||
upcomingRaces: dto.races.filter(r => r.isUpcoming).map(r => this.presentRaceCard(r)),
|
||||
completedRaces: dto.races.filter(r => r.status === 'completed').map(r => this.presentRaceCard(r)),
|
||||
totalCount: dto.races.length,
|
||||
};
|
||||
}
|
||||
|
||||
private presentRaceCard(race: any): any {
|
||||
return {
|
||||
id: race.id,
|
||||
title: race.title || race.track,
|
||||
scheduledTime: race.scheduledTime || race.scheduledAt,
|
||||
status: this.formatStatus(race.status),
|
||||
};
|
||||
}
|
||||
|
||||
private formatStatus(status: string): string {
|
||||
const statusMap: Record<string, string> = {
|
||||
scheduled: 'Scheduled',
|
||||
running: 'Live',
|
||||
completed: 'Finished',
|
||||
cancelled: 'Cancelled',
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
import { RaceResultsDetailDto } from '../dtos';
|
||||
import { RaceResultsDetailViewModel } from '../view-models';
|
||||
|
||||
export const presentRaceResultsDetail = (dto: RaceResultsDetailDto, currentUserId: string): RaceResultsDetailViewModel => {
|
||||
return new RaceResultsDetailViewModel(dto, currentUserId);
|
||||
export class RaceResultsDetailPresenter {
|
||||
present(dto: RaceResultsDetailDto, currentUserId?: string): RaceResultsDetailViewModel {
|
||||
return new RaceResultsDetailViewModel(dto, currentUserId);
|
||||
}
|
||||
}
|
||||
|
||||
export const presentRaceResultsDetail = (dto: RaceResultsDetailDto, currentUserId?: string): RaceResultsDetailViewModel => {
|
||||
const presenter = new RaceResultsDetailPresenter();
|
||||
return presenter.present(dto, currentUserId);
|
||||
};
|
||||
@@ -5,10 +5,8 @@ import type {
|
||||
} from '@core/racing/application/presenters/IRaceWithSOFPresenter';
|
||||
|
||||
export class RaceWithSOFPresenter implements IRaceWithSOFPresenter {
|
||||
private viewModel: RaceWithSOFViewModel | null = null;
|
||||
|
||||
present(dto: RaceWithSOFResultDTO): void {
|
||||
this.viewModel = {
|
||||
present(dto: RaceWithSOFResultDTO): RaceWithSOFViewModel {
|
||||
return {
|
||||
id: dto.raceId,
|
||||
leagueId: dto.leagueId,
|
||||
scheduledAt: dto.scheduledAt.toISOString(),
|
||||
@@ -24,12 +22,4 @@ export class RaceWithSOFPresenter implements IRaceWithSOFPresenter {
|
||||
participantCount: dto.participantCount,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): RaceWithSOFViewModel | null {
|
||||
return this.viewModel;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@ export { presentWalletTransaction } from './WalletTransactionPresenter';
|
||||
export { presentRaceDetail } from './RaceDetailPresenter';
|
||||
export { presentRaceListItem } from './RaceListItemPresenter';
|
||||
export { presentRaceResult } from './RaceResultsPresenter';
|
||||
export { presentRaceResultsDetail } from './RaceResultsDetailPresenter';
|
||||
export { presentRaceResultsDetail, RaceResultsDetailPresenter } from './RaceResultsDetailPresenter';
|
||||
export { RaceWithSOFPresenter } from './RaceWithSOFPresenter';
|
||||
export { ImportRaceResultsPresenter } from './ImportRaceResultsPresenter';
|
||||
|
||||
// Sponsor Presenters
|
||||
export { presentSponsor } from './SponsorPresenter';
|
||||
|
||||
@@ -2,15 +2,42 @@ import { api as api } from '../../api';
|
||||
import { presentDriversLeaderboard } from '../../presenters';
|
||||
import { DriverLeaderboardViewModel } from '../../view-models';
|
||||
|
||||
/**
|
||||
* Driver Service
|
||||
*
|
||||
* Handles driver-related operations including profiles, leaderboards, and onboarding.
|
||||
*/
|
||||
export class DriverService {
|
||||
constructor(
|
||||
private readonly apiClient = api.drivers
|
||||
) {}
|
||||
|
||||
async getDriverLeaderboard(): Promise<DriverLeaderboardViewModel> {
|
||||
const dto = await this.apiClient.getLeaderboard();
|
||||
return presentDriversLeaderboard(dto);
|
||||
}
|
||||
|
||||
async completeDriverOnboarding(input: any): Promise<any> {
|
||||
return await this.apiClient.completeOnboarding(input);
|
||||
}
|
||||
|
||||
async getCurrentDriver(): Promise<any> {
|
||||
return await this.apiClient.getCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const driverService = new DriverService();
|
||||
|
||||
// Backward compatibility functional exports
|
||||
export async function getDriverLeaderboard(): Promise<DriverLeaderboardViewModel> {
|
||||
const dto = await api.drivers.getLeaderboard();
|
||||
return presentDriversLeaderboard(dto);
|
||||
return driverService.getDriverLeaderboard();
|
||||
}
|
||||
|
||||
export async function completeDriverOnboarding(input: any): Promise<any> {
|
||||
return await api.drivers.completeOnboarding(input);
|
||||
return driverService.completeDriverOnboarding(input);
|
||||
}
|
||||
|
||||
export async function getCurrentDriver(): Promise<any> {
|
||||
return await api.drivers.getCurrent();
|
||||
return driverService.getCurrentDriver();
|
||||
}
|
||||
6
apps/website/lib/services/drivers/index.ts
Normal file
6
apps/website/lib/services/drivers/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// Export the class-based service
|
||||
export { DriverService, driverService } from './DriverService';
|
||||
|
||||
// Export backward compatibility functions
|
||||
export { getDriverLeaderboard, completeDriverOnboarding, getCurrentDriver } from './DriverService';
|
||||
export { registerDriver, getDriverRegistrationStatus } from './DriverRegistrationService';
|
||||
@@ -1,23 +1,46 @@
|
||||
import { api as api } from '../../api';
|
||||
import { presentRaceResultsDetail } from '../../presenters';
|
||||
import { RaceResultsDetailPresenter, RaceWithSOFPresenter, ImportRaceResultsPresenter } from '../../presenters';
|
||||
import { RaceResultsDetailViewModel } from '../../view-models';
|
||||
|
||||
export class RaceResultsService {
|
||||
constructor(
|
||||
private readonly apiClient = api.races,
|
||||
private readonly resultsDetailPresenter = new RaceResultsDetailPresenter(),
|
||||
private readonly sofPresenter = new RaceWithSOFPresenter(),
|
||||
private readonly importPresenter = new ImportRaceResultsPresenter()
|
||||
) {}
|
||||
|
||||
async importRaceResults(raceId: string, input: any): Promise<any> {
|
||||
const dto = await this.apiClient.importResults(raceId, input);
|
||||
return this.importPresenter.present(dto);
|
||||
}
|
||||
|
||||
async getResultsDetail(raceId: string, currentUserId?: string): Promise<RaceResultsDetailViewModel> {
|
||||
const dto = await this.apiClient.getResultsDetail(raceId);
|
||||
return this.resultsDetailPresenter.present(dto, currentUserId);
|
||||
}
|
||||
|
||||
async getWithSOF(raceId: string): Promise<any> {
|
||||
const dto = await this.apiClient.getWithSOF(raceId);
|
||||
return this.sofPresenter.present(dto);
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const raceResultsService = new RaceResultsService();
|
||||
|
||||
// Backward compatibility functions
|
||||
export async function getRaceResults(
|
||||
raceId: string,
|
||||
currentUserId?: string
|
||||
): Promise<RaceResultsDetailViewModel> {
|
||||
const dto = await api.races.getResultsDetail(raceId);
|
||||
return presentRaceResultsDetail(dto, currentUserId);
|
||||
return raceResultsService.getResultsDetail(raceId, currentUserId);
|
||||
}
|
||||
|
||||
export async function getRaceSOF(raceId: string): Promise<any> {
|
||||
const dto = await api.races.getWithSOF(raceId);
|
||||
// TODO: use presenter
|
||||
return dto;
|
||||
return raceResultsService.getWithSOF(raceId);
|
||||
}
|
||||
|
||||
export async function importRaceResults(raceId: string, input: any): Promise<any> {
|
||||
const dto = await api.races.importResults(raceId, input);
|
||||
// TODO: use presenter
|
||||
return dto;
|
||||
return raceResultsService.importRaceResults(raceId, input);
|
||||
}
|
||||
@@ -1,22 +1,48 @@
|
||||
import { api as api } from '../../api';
|
||||
import { presentRaceDetail } from '../../presenters';
|
||||
import { RaceDetailPresenter } from '../../presenters';
|
||||
import { RaceDetailViewModel } from '../../view-models';
|
||||
|
||||
export class RaceService {
|
||||
constructor(
|
||||
private readonly apiClient = api.races,
|
||||
private readonly presenter = new RaceDetailPresenter()
|
||||
) {}
|
||||
|
||||
async getRaceDetail(
|
||||
raceId: string,
|
||||
driverId: string
|
||||
): Promise<RaceDetailViewModel> {
|
||||
const dto = await this.apiClient.getDetail(raceId, driverId);
|
||||
return this.presenter.present(dto);
|
||||
}
|
||||
|
||||
async getRacesPageData(): Promise<any> {
|
||||
const dto = await this.apiClient.getPageData();
|
||||
// TODO: use presenter
|
||||
return dto;
|
||||
}
|
||||
|
||||
async getRacesTotal(): Promise<any> {
|
||||
const dto = await this.apiClient.getTotal();
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const raceService = new RaceService();
|
||||
|
||||
// Backward compatibility functions
|
||||
export async function getRaceDetail(
|
||||
raceId: string,
|
||||
driverId: string
|
||||
): Promise<RaceDetailViewModel> {
|
||||
const dto = await api.races.getDetail(raceId, driverId);
|
||||
return presentRaceDetail(dto);
|
||||
return raceService.getRaceDetail(raceId, driverId);
|
||||
}
|
||||
|
||||
export async function getRacesPageData(): Promise<any> {
|
||||
const dto = await api.races.getPageData();
|
||||
// TODO: use presenter
|
||||
return dto;
|
||||
return raceService.getRacesPageData();
|
||||
}
|
||||
|
||||
export async function getRacesTotal(): Promise<any> {
|
||||
const dto = await api.races.getTotal();
|
||||
return dto;
|
||||
return raceService.getRacesTotal();
|
||||
}
|
||||
7
apps/website/lib/services/races/index.ts
Normal file
7
apps/website/lib/services/races/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// Export the class-based service
|
||||
export { RaceService, raceService } from './RaceService';
|
||||
export { RaceResultsService, raceResultsService } from './RaceResultsService';
|
||||
|
||||
// Export backward compatibility functions
|
||||
export { getRaceDetail, getRacesPageData, getRacesTotal } from './RaceService';
|
||||
export { getRaceResults, getRaceSOF, importRaceResults } from './RaceResultsService';
|
||||
12
apps/website/lib/view-models/RacesPageViewModel.ts
Normal file
12
apps/website/lib/view-models/RacesPageViewModel.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface RaceCardViewModel {
|
||||
id: string;
|
||||
title: string;
|
||||
scheduledTime: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface RacesPageViewModel {
|
||||
upcomingRaces: RaceCardViewModel[];
|
||||
completedRaces: RaceCardViewModel[];
|
||||
totalCount: number;
|
||||
}
|
||||
Reference in New Issue
Block a user