refactor page to use services
This commit is contained in:
@@ -7,6 +7,8 @@ import { PaymentsApiClient } from '../api/payments/PaymentsApiClient';
|
||||
import { AuthApiClient } from '../api/auth/AuthApiClient';
|
||||
import { AnalyticsApiClient } from '../api/analytics/AnalyticsApiClient';
|
||||
import { MediaApiClient } from '../api/media/MediaApiClient';
|
||||
import { DashboardApiClient } from '../api/dashboard/DashboardApiClient';
|
||||
import { ProtestsApiClient } from '../api/protests/ProtestsApiClient';
|
||||
import { ConsoleErrorReporter } from '../infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '../infrastructure/logging/ConsoleLogger';
|
||||
|
||||
@@ -19,17 +21,20 @@ import { TeamService } from './teams/TeamService';
|
||||
import { TeamJoinService } from './teams/TeamJoinService';
|
||||
import { LeagueService } from './leagues/LeagueService';
|
||||
import { LeagueMembershipService } from './leagues/LeagueMembershipService';
|
||||
import { LeagueSettingsService } from './leagues/LeagueSettingsService';
|
||||
import { SponsorService } from './sponsors/SponsorService';
|
||||
import { SponsorshipService } from './sponsors/SponsorshipService';
|
||||
import { PaymentService } from './payments/PaymentService';
|
||||
import { AnalyticsService } from './analytics/AnalyticsService';
|
||||
import { DashboardService } from './analytics/DashboardService';
|
||||
import { DashboardService as AnalyticsDashboardService } from './analytics/DashboardService';
|
||||
import { DashboardService } from './dashboard/DashboardService';
|
||||
import { MediaService } from './media/MediaService';
|
||||
import { AvatarService } from './media/AvatarService';
|
||||
import { WalletService } from './payments/WalletService';
|
||||
import { MembershipFeeService } from './payments/MembershipFeeService';
|
||||
import { AuthService } from './auth/AuthService';
|
||||
import { SessionService } from './auth/SessionService';
|
||||
import { ProtestService } from './protests/ProtestService';
|
||||
|
||||
/**
|
||||
* ServiceFactory - Composition root for all services
|
||||
@@ -52,6 +57,8 @@ export class ServiceFactory {
|
||||
auth: AuthApiClient;
|
||||
analytics: AnalyticsApiClient;
|
||||
media: MediaApiClient;
|
||||
dashboard: DashboardApiClient;
|
||||
protests: ProtestsApiClient;
|
||||
};
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
@@ -66,6 +73,8 @@ export class ServiceFactory {
|
||||
auth: new AuthApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
analytics: new AnalyticsApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
media: new MediaApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
dashboard: new DashboardApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
protests: new ProtestsApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,15 +124,22 @@ export class ServiceFactory {
|
||||
* Create LeagueService instance
|
||||
*/
|
||||
createLeagueService(): LeagueService {
|
||||
return new LeagueService(this.apiClients.leagues);
|
||||
return new LeagueService(this.apiClients.leagues, this.apiClients.drivers, this.apiClients.sponsors, this.apiClients.races);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create LeagueMembershipService instance
|
||||
*/
|
||||
createLeagueMembershipService(): LeagueMembershipService {
|
||||
return new LeagueMembershipService(this.apiClients.leagues);
|
||||
}
|
||||
* Create LeagueMembershipService instance
|
||||
*/
|
||||
createLeagueMembershipService(): LeagueMembershipService {
|
||||
return new LeagueMembershipService(this.apiClients.leagues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create LeagueSettingsService instance
|
||||
*/
|
||||
createLeagueSettingsService(): LeagueSettingsService {
|
||||
return new LeagueSettingsService(this.apiClients.leagues, this.apiClients.drivers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SponsorService instance
|
||||
@@ -154,11 +170,18 @@ export class ServiceFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DashboardService instance
|
||||
*/
|
||||
createDashboardService(): DashboardService {
|
||||
return new DashboardService(this.apiClients.analytics);
|
||||
}
|
||||
* Create Analytics DashboardService instance
|
||||
*/
|
||||
createAnalyticsDashboardService(): AnalyticsDashboardService {
|
||||
return new AnalyticsDashboardService(this.apiClients.analytics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DashboardService instance
|
||||
*/
|
||||
createDashboardService(): DashboardService {
|
||||
return new DashboardService(this.apiClients.dashboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create MediaService instance
|
||||
@@ -201,4 +224,11 @@ export class ServiceFactory {
|
||||
createSessionService(): SessionService {
|
||||
return new SessionService(this.apiClients.auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ProtestService instance
|
||||
*/
|
||||
createProtestService(): ProtestService {
|
||||
return new ProtestService(this.apiClients.protests);
|
||||
}
|
||||
}
|
||||
22
apps/website/lib/services/dashboard/DashboardService.ts
Normal file
22
apps/website/lib/services/dashboard/DashboardService.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { DashboardOverviewViewModel } from '../../view-models/DashboardOverviewViewModel';
|
||||
import { DashboardApiClient } from '../../api/dashboard/DashboardApiClient';
|
||||
|
||||
/**
|
||||
* Dashboard Service
|
||||
*
|
||||
* Orchestrates dashboard operations by coordinating API calls and view model creation.
|
||||
* All dependencies are injected via constructor.
|
||||
*/
|
||||
export class DashboardService {
|
||||
constructor(
|
||||
private readonly apiClient: DashboardApiClient
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get dashboard overview data with view model transformation
|
||||
*/
|
||||
async getDashboardOverview(): Promise<DashboardOverviewViewModel> {
|
||||
const dto = await this.apiClient.getDashboardOverview();
|
||||
return new DashboardOverviewViewModel(dto);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
|
||||
import { CompleteOnboardingInputDTO } from "@/lib/types/generated/CompleteOnboardingInputDTO";
|
||||
import { DriverProfileDTO } from "@/lib/types/generated/DriverProfileDTO";
|
||||
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";
|
||||
|
||||
// TODO: Create proper DriverDTO in generated types
|
||||
type DriverDTO = {
|
||||
@@ -41,8 +43,8 @@ export class DriverService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current driver with view model transformation
|
||||
*/
|
||||
* Get current driver with view model transformation
|
||||
*/
|
||||
async getCurrentDriver(): Promise<DriverViewModel | null> {
|
||||
const dto = await this.apiClient.getCurrent();
|
||||
if (!dto) {
|
||||
@@ -50,4 +52,12 @@ export class DriverService {
|
||||
}
|
||||
return new DriverViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(dto);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
|
||||
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
|
||||
import { SponsorsApiClient } from "@/lib/api/sponsors/SponsorsApiClient";
|
||||
import { RacesApiClient } from "@/lib/api/races/RacesApiClient";
|
||||
import { CreateLeagueInputDTO } from "@/lib/types/generated/CreateLeagueInputDTO";
|
||||
import { LeagueWithCapacityDTO } from "@/lib/types/generated/LeagueWithCapacityDTO";
|
||||
import { CreateLeagueViewModel } from "@/lib/view-models/CreateLeagueViewModel";
|
||||
@@ -8,7 +11,14 @@ import { LeagueStandingsViewModel } from "@/lib/view-models/LeagueStandingsViewM
|
||||
import { LeagueStatsViewModel } from "@/lib/view-models/LeagueStatsViewModel";
|
||||
import { LeagueSummaryViewModel } from "@/lib/view-models/LeagueSummaryViewModel";
|
||||
import { RemoveMemberViewModel } from "@/lib/view-models/RemoveMemberViewModel";
|
||||
import { LeagueDetailViewModel } from "@/lib/view-models/LeagueDetailViewModel";
|
||||
import { LeagueDetailPageViewModel, SponsorInfo } from "@/lib/view-models/LeagueDetailPageViewModel";
|
||||
import { SubmitBlocker, ThrottleBlocker } from "@/lib/blockers";
|
||||
import { DriverDTO } from "@/lib/types/DriverDTO";
|
||||
import { RaceDTO } from "@/lib/types/generated/RaceDTO";
|
||||
import { LeagueScoringConfigDTO } from "@/lib/types/LeagueScoringConfigDTO";
|
||||
import { LeagueStatsDTO } from "@/lib/types/generated/LeagueStatsDTO";
|
||||
import { LeagueMembershipsDTO } from "@/lib/types/generated/LeagueMembershipsDTO";
|
||||
|
||||
|
||||
/**
|
||||
@@ -22,7 +32,10 @@ export class LeagueService {
|
||||
private readonly throttle = new ThrottleBlocker(500);
|
||||
|
||||
constructor(
|
||||
private readonly apiClient: LeaguesApiClient
|
||||
private readonly apiClient: LeaguesApiClient,
|
||||
private readonly driversApiClient: DriversApiClient,
|
||||
private readonly sponsorsApiClient: SponsorsApiClient,
|
||||
private readonly racesApiClient: RacesApiClient
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -93,4 +106,156 @@ export class LeagueService {
|
||||
const dto = await this.apiClient.removeMember(leagueId, performerDriverId, targetDriverId);
|
||||
return new RemoveMemberViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get league detail with owner, membership, and sponsor info
|
||||
*/
|
||||
async getLeagueDetail(leagueId: string, currentDriverId: string): Promise<LeagueDetailViewModel | null> {
|
||||
// For now, assume league data comes from getAllWithCapacity or a new endpoint
|
||||
// Since API may not have detailed league, we'll mock or assume
|
||||
// In real implementation, add getLeagueDetail to API
|
||||
const allLeagues = await this.apiClient.getAllWithCapacity();
|
||||
const leagueDto = allLeagues.leagues.find(l => l.id === leagueId);
|
||||
if (!leagueDto) return null;
|
||||
|
||||
// Assume league has description, ownerId - need to update DTO
|
||||
const league = {
|
||||
id: leagueDto.id,
|
||||
name: leagueDto.name,
|
||||
description: 'Description not available', // TODO: add to API
|
||||
ownerId: 'owner-id', // TODO: add to API
|
||||
};
|
||||
|
||||
// Get owner
|
||||
const owner = await this.driversApiClient.getDriver(league.ownerId);
|
||||
const ownerName = owner ? owner.name : `${league.ownerId.slice(0, 8)}...`;
|
||||
|
||||
// Get membership
|
||||
const membershipsDto = await this.apiClient.getMemberships(leagueId);
|
||||
const membership = membershipsDto.members.find(m => m.driverId === currentDriverId);
|
||||
const isAdmin = membership ? ['admin', 'owner'].includes(membership.role) : false;
|
||||
|
||||
// Get main sponsor
|
||||
let mainSponsor = null;
|
||||
try {
|
||||
const seasonsDto = await this.apiClient.getSeasons(leagueId);
|
||||
const activeSeason = seasonsDto.seasons.find((s: any) => s.status === 'active') ?? seasonsDto.seasons[0];
|
||||
if (activeSeason) {
|
||||
const sponsorshipsDto = await this.apiClient.getSeasonSponsorships(activeSeason.id);
|
||||
const mainSponsorship = sponsorshipsDto.sponsorships.find((s: any) => s.tier === 'main' && s.status === 'active');
|
||||
if (mainSponsorship) {
|
||||
const sponsor = await this.sponsorsApiClient.getSponsor(mainSponsorship.sponsorId);
|
||||
if (sponsor) {
|
||||
mainSponsor = {
|
||||
name: sponsor.name,
|
||||
logoUrl: sponsor.logoUrl ?? '',
|
||||
websiteUrl: sponsor.websiteUrl ?? '',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load main sponsor:', error);
|
||||
}
|
||||
|
||||
return new LeagueDetailViewModel(
|
||||
league.id,
|
||||
league.name,
|
||||
league.description,
|
||||
league.ownerId,
|
||||
ownerName,
|
||||
mainSponsor,
|
||||
isAdmin
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comprehensive league detail page data
|
||||
*/
|
||||
async getLeagueDetailPageData(leagueId: string): Promise<LeagueDetailPageViewModel | null> {
|
||||
try {
|
||||
// Get league basic info
|
||||
const allLeagues = await this.apiClient.getAllWithCapacity();
|
||||
const league = allLeagues.leagues.find(l => l.id === leagueId);
|
||||
if (!league) return null;
|
||||
|
||||
// Get owner
|
||||
const owner = await this.driversApiClient.getDriver(league.ownerId);
|
||||
|
||||
// Get scoring config - TODO: implement API endpoint
|
||||
const scoringConfig: LeagueScoringConfigDTO | null = null; // TODO: fetch from API
|
||||
|
||||
// Get all drivers - TODO: implement API endpoint for all drivers
|
||||
const drivers: DriverDTO[] = []; // TODO: fetch from API
|
||||
|
||||
// Get memberships
|
||||
const memberships = await this.apiClient.getMemberships(leagueId);
|
||||
|
||||
// Get all races for this league - TODO: implement API endpoint
|
||||
const allRaces: RaceDTO[] = []; // TODO: fetch from API
|
||||
|
||||
// Get league stats
|
||||
const leagueStats = await this.apiClient.getTotal(); // TODO: get stats for specific league
|
||||
|
||||
// Get sponsors
|
||||
const sponsors = await this.getLeagueSponsors(leagueId);
|
||||
|
||||
return new LeagueDetailPageViewModel(
|
||||
league,
|
||||
owner,
|
||||
scoringConfig,
|
||||
drivers,
|
||||
memberships,
|
||||
allRaces,
|
||||
leagueStats,
|
||||
sponsors
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to load league detail page data:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sponsors for a league
|
||||
*/
|
||||
private async getLeagueSponsors(leagueId: string): Promise<SponsorInfo[]> {
|
||||
try {
|
||||
const seasons = await this.apiClient.getSeasons(leagueId);
|
||||
const activeSeason = seasons.seasons.find((s: any) => s.status === 'active') ?? seasons.seasons[0];
|
||||
|
||||
if (!activeSeason) return [];
|
||||
|
||||
const sponsorships = await this.apiClient.getSeasonSponsorships(activeSeason.id);
|
||||
const activeSponsorships = sponsorships.sponsorships.filter((s: any) => s.status === 'active');
|
||||
|
||||
const sponsorInfos: SponsorInfo[] = [];
|
||||
for (const sponsorship of activeSponsorships) {
|
||||
const sponsor = await this.sponsorsApiClient.getSponsor(sponsorship.sponsorId);
|
||||
if (sponsor) {
|
||||
// TODO: Get tagline from testing support or API
|
||||
sponsorInfos.push({
|
||||
id: sponsor.id,
|
||||
name: sponsor.name,
|
||||
logoUrl: sponsor.logoUrl ?? '',
|
||||
websiteUrl: sponsor.websiteUrl ?? '',
|
||||
tier: sponsorship.tier,
|
||||
tagline: '', // TODO: fetch tagline
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort: main sponsors first, then secondary
|
||||
sponsorInfos.sort((a, b) => {
|
||||
if (a.tier === 'main' && b.tier !== 'main') return -1;
|
||||
if (a.tier !== 'main' && b.tier === 'main') return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sponsorInfos;
|
||||
} catch (error) {
|
||||
console.warn('Failed to load sponsors:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
95
apps/website/lib/services/leagues/LeagueSettingsService.ts
Normal file
95
apps/website/lib/services/leagues/LeagueSettingsService.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
|
||||
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
|
||||
import type { LeagueConfigFormModel } from "@/lib/types/LeagueConfigFormModel";
|
||||
import type { LeagueScoringPresetDTO } from "@/lib/types/LeagueScoringPresetDTO";
|
||||
import type { DriverDTO } from "@/lib/types/DriverDTO";
|
||||
import { LeagueSettingsViewModel } from "@/lib/view-models/LeagueSettingsViewModel";
|
||||
import { DriverSummaryViewModel } from "@/lib/view-models/DriverSummaryViewModel";
|
||||
|
||||
/**
|
||||
* League Settings Service
|
||||
*
|
||||
* Orchestrates league settings operations by coordinating API calls and view model creation.
|
||||
* All dependencies are injected via constructor.
|
||||
*/
|
||||
export class LeagueSettingsService {
|
||||
constructor(
|
||||
private readonly leaguesApiClient: LeaguesApiClient,
|
||||
private readonly driversApiClient: DriversApiClient
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get league settings with view model transformation
|
||||
*/
|
||||
async getLeagueSettings(leagueId: string): Promise<LeagueSettingsViewModel | null> {
|
||||
try {
|
||||
// Get league basic info
|
||||
const allLeagues = await this.leaguesApiClient.getAllWithCapacity();
|
||||
const leagueDto = allLeagues.leagues.find(l => l.id === leagueId);
|
||||
if (!leagueDto) return null;
|
||||
|
||||
// Assume league has ownerId - need to update API
|
||||
const league = {
|
||||
id: leagueDto.id,
|
||||
name: leagueDto.name,
|
||||
ownerId: 'owner-id', // TODO: add to API
|
||||
};
|
||||
|
||||
// Get config
|
||||
const configDto = await this.leaguesApiClient.getLeagueConfig(leagueId);
|
||||
const config: LeagueConfigFormModel = configDto.config;
|
||||
|
||||
// Get presets
|
||||
const presetsDto = await this.leaguesApiClient.getScoringPresets();
|
||||
const presets: LeagueScoringPresetDTO[] = presetsDto.presets;
|
||||
|
||||
// Get owner
|
||||
const ownerDriver = await this.driversApiClient.getDriver(league.ownerId);
|
||||
let owner: DriverSummaryViewModel | null = null;
|
||||
if (ownerDriver) {
|
||||
// TODO: get rating and rank from API
|
||||
owner = new DriverSummaryViewModel({
|
||||
driver: ownerDriver,
|
||||
rating: ownerDriver.rating ?? null,
|
||||
rank: null, // TODO: get from API
|
||||
});
|
||||
}
|
||||
|
||||
// Get members
|
||||
const membershipsDto = await this.leaguesApiClient.getMemberships(leagueId);
|
||||
const members: DriverDTO[] = [];
|
||||
for (const member of membershipsDto.members) {
|
||||
if (member.driverId !== league.ownerId && member.role !== 'owner') {
|
||||
const driver = await this.driversApiClient.getDriver(member.driverId);
|
||||
if (driver) {
|
||||
members.push(driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LeagueSettingsViewModel({
|
||||
league,
|
||||
config,
|
||||
presets,
|
||||
owner,
|
||||
members,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load league settings:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer league ownership
|
||||
*/
|
||||
async transferOwnership(leagueId: string, currentOwnerId: string, newOwnerId: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await this.leaguesApiClient.transferOwnership(leagueId, currentOwnerId, newOwnerId);
|
||||
return result.success;
|
||||
} catch (error) {
|
||||
console.error('Failed to transfer ownership:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,10 +34,17 @@ export class MediaService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete media by ID with view model transformation
|
||||
*/
|
||||
async deleteMedia(mediaId: string): Promise<DeleteMediaViewModel> {
|
||||
const dto = await this.apiClient.deleteMedia(mediaId);
|
||||
return new DeleteMediaViewModel(dto);
|
||||
}
|
||||
}
|
||||
* Delete media by ID with view model transformation
|
||||
*/
|
||||
async deleteMedia(mediaId: string): Promise<DeleteMediaViewModel> {
|
||||
const dto = await this.apiClient.deleteMedia(mediaId);
|
||||
return new DeleteMediaViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get team logo URL
|
||||
*/
|
||||
getTeamLogo(teamId: string): string {
|
||||
return `/api/media/teams/${teamId}/logo`;
|
||||
}
|
||||
}
|
||||
70
apps/website/lib/services/protests/ProtestService.ts
Normal file
70
apps/website/lib/services/protests/ProtestService.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ProtestsApiClient } from '../../api/protests/ProtestsApiClient';
|
||||
import { ProtestViewModel } from '../../view-models/ProtestViewModel';
|
||||
import type { LeagueAdminProtestsDTO, ApplyPenaltyCommandDTO, RequestProtestDefenseCommandDTO, DriverSummaryDTO } from '../../types';
|
||||
|
||||
/**
|
||||
* Protest Service
|
||||
*
|
||||
* Orchestrates protest operations by coordinating API calls and view model creation.
|
||||
* All dependencies are injected via constructor.
|
||||
*/
|
||||
export class ProtestService {
|
||||
constructor(
|
||||
private readonly apiClient: ProtestsApiClient
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get protests for a league with view model transformation
|
||||
*/
|
||||
async getLeagueProtests(leagueId: string): Promise<{
|
||||
protests: ProtestViewModel[];
|
||||
racesById: LeagueAdminProtestsDTO['racesById'];
|
||||
driversById: LeagueAdminProtestsDTO['driversById'];
|
||||
}> {
|
||||
const dto = await this.apiClient.getLeagueProtests(leagueId);
|
||||
return {
|
||||
protests: dto.protests.map(protest => new ProtestViewModel(protest)),
|
||||
racesById: dto.racesById,
|
||||
driversById: dto.driversById,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single protest by ID from league protests
|
||||
*/
|
||||
async getProtestById(leagueId: string, protestId: string): Promise<{
|
||||
protest: ProtestViewModel;
|
||||
race: LeagueAdminProtestsDTO['racesById'][string];
|
||||
protestingDriver: DriverSummaryDTO;
|
||||
accusedDriver: DriverSummaryDTO;
|
||||
} | null> {
|
||||
const dto = await this.apiClient.getLeagueProtest(leagueId, protestId);
|
||||
const protest = dto.protests[0];
|
||||
if (!protest) return null;
|
||||
|
||||
const race = Object.values(dto.racesById)[0];
|
||||
const protestingDriver = dto.driversById[protest.protestingDriverId];
|
||||
const accusedDriver = dto.driversById[protest.accusedDriverId];
|
||||
|
||||
return {
|
||||
protest: new ProtestViewModel(protest),
|
||||
race,
|
||||
protestingDriver,
|
||||
accusedDriver,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a penalty
|
||||
*/
|
||||
async applyPenalty(input: ApplyPenaltyCommandDTO): Promise<void> {
|
||||
await this.apiClient.applyPenalty(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request protest defense
|
||||
*/
|
||||
async requestDefense(input: RequestProtestDefenseCommandDTO): Promise<void> {
|
||||
await this.apiClient.requestDefense(input);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,34 @@ export class RaceService {
|
||||
return new RaceStatsViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register for a race
|
||||
*/
|
||||
async registerForRace(raceId: string, leagueId: string, driverId: string): Promise<void> {
|
||||
await this.apiClient.register(raceId, { leagueId, driverId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraw from a race
|
||||
*/
|
||||
async withdrawFromRace(raceId: string, driverId: string): Promise<void> {
|
||||
await this.apiClient.withdraw(raceId, { driverId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a race
|
||||
*/
|
||||
async cancelRace(raceId: string): Promise<void> {
|
||||
await this.apiClient.cancel(raceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a race
|
||||
*/
|
||||
async completeRace(raceId: string): Promise<void> {
|
||||
await this.apiClient.complete(raceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform API races page data to view model format
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
import { CreateTeamViewModel } from '@/lib/view-models/CreateTeamViewModel';
|
||||
import { UpdateTeamViewModel } from '@/lib/view-models/UpdateTeamViewModel';
|
||||
import { DriverTeamViewModel } from '@/lib/view-models/DriverTeamViewModel';
|
||||
import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
|
||||
import type { TeamsApiClient } from '../../api/teams/TeamsApiClient';
|
||||
|
||||
// TODO: Move these types to apps/website/lib/types/generated when available
|
||||
@@ -71,10 +72,33 @@ export class TeamService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driver's team with view model transformation
|
||||
*/
|
||||
async getDriverTeam(driverId: string): Promise<DriverTeamViewModel | null> {
|
||||
const dto = await this.apiClient.getDriverTeam(driverId);
|
||||
return dto ? new DriverTeamViewModel(dto) : null;
|
||||
}
|
||||
}
|
||||
* Get driver's team with view model transformation
|
||||
*/
|
||||
async getDriverTeam(driverId: string): Promise<DriverTeamViewModel | null> {
|
||||
const dto = await this.apiClient.getDriverTeam(driverId);
|
||||
return dto ? new DriverTeamViewModel(dto) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get team membership for a driver
|
||||
*/
|
||||
async getMembership(teamId: string, driverId: string): Promise<LeagueMemberDTO | null> {
|
||||
return this.apiClient.getMembership(teamId, driverId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a driver from the team
|
||||
*/
|
||||
async removeMembership(teamId: string, driverId: string): Promise<void> {
|
||||
// TODO: Implement when API endpoint is available
|
||||
throw new Error('Not implemented: API endpoint for removing team membership');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update team membership role
|
||||
*/
|
||||
async updateMembership(teamId: string, driverId: string, role: string): Promise<void> {
|
||||
// TODO: Implement when API endpoint is available
|
||||
throw new Error('Not implemented: API endpoint for updating team membership role');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user