more seeds

This commit is contained in:
2025-12-27 11:58:35 +01:00
parent 91612e4256
commit 3efa978ee0
25 changed files with 806 additions and 55 deletions

View File

@@ -11,6 +11,7 @@ import type { RaceDTO } from '../../types/generated/RaceDTO';
import type { GetLeagueAdminConfigOutputDTO } from '../../types/generated/GetLeagueAdminConfigOutputDTO';
import type { LeagueScoringPresetDTO } from '../../types/generated/LeagueScoringPresetDTO';
import type { LeagueSeasonSummaryDTO } from '../../types/generated/LeagueSeasonSummaryDTO';
import type { AllLeaguesWithCapacityAndScoringDTO } from '../../types/AllLeaguesWithCapacityAndScoringDTO';
/**
* Leagues API Client
@@ -23,6 +24,11 @@ export class LeaguesApiClient extends BaseApiClient {
return this.get<AllLeaguesWithCapacityDTO>('/leagues/all-with-capacity');
}
/** Get all leagues with capacity + scoring summary (for leagues page filters) */
getAllWithCapacityAndScoring(): Promise<AllLeaguesWithCapacityAndScoringDTO> {
return this.get<AllLeaguesWithCapacityAndScoringDTO>('/leagues/all-with-capacity-and-scoring');
}
/** Get total number of leagues */
getTotal(): Promise<TotalLeaguesDTO> {
return this.get<TotalLeaguesDTO>('/leagues/total-leagues');

View File

@@ -18,6 +18,7 @@ describe('LeagueService', () => {
beforeEach(() => {
mockApiClient = {
getAllWithCapacity: vi.fn(),
getAllWithCapacityAndScoring: vi.fn(),
getStandings: vi.fn(),
getTotal: vi.fn(),
getSchedule: vi.fn(),
@@ -30,7 +31,7 @@ describe('LeagueService', () => {
});
describe('getAllLeagues', () => {
it('should call apiClient.getAllWithCapacity and return array of LeagueSummaryViewModel', async () => {
it('should call apiClient.getAllWithCapacityAndScoring and return array of LeagueSummaryViewModel', async () => {
const mockDto = {
totalCount: 2,
leagues: [
@@ -39,11 +40,11 @@ describe('LeagueService', () => {
],
} as any;
mockApiClient.getAllWithCapacity.mockResolvedValue(mockDto);
mockApiClient.getAllWithCapacityAndScoring.mockResolvedValue(mockDto);
const result = await service.getAllLeagues();
expect(mockApiClient.getAllWithCapacity).toHaveBeenCalled();
expect(mockApiClient.getAllWithCapacityAndScoring).toHaveBeenCalled();
expect(result).toHaveLength(2);
expect(result.map((l) => l.id)).toEqual(['league-1', 'league-2']);
});
@@ -51,16 +52,16 @@ describe('LeagueService', () => {
it('should handle empty leagues array', async () => {
const mockDto = { totalCount: 0, leagues: [] } as any;
mockApiClient.getAllWithCapacity.mockResolvedValue(mockDto);
mockApiClient.getAllWithCapacityAndScoring.mockResolvedValue(mockDto);
const result = await service.getAllLeagues();
expect(result).toHaveLength(0);
});
it('should throw error when apiClient.getAllWithCapacity fails', async () => {
it('should throw error when apiClient.getAllWithCapacityAndScoring fails', async () => {
const error = new Error('API call failed');
mockApiClient.getAllWithCapacity.mockRejectedValue(error);
mockApiClient.getAllWithCapacityAndScoring.mockRejectedValue(error);
await expect(service.getAllLeagues()).rejects.toThrow('API call failed');
});

View File

@@ -44,17 +44,20 @@ export class LeagueService {
* Get all leagues with view model transformation
*/
async getAllLeagues(): Promise<LeagueSummaryViewModel[]> {
const dto = await this.apiClient.getAllWithCapacity();
return dto.leagues.map((league: LeagueWithCapacityDTO) => ({
const dto = await this.apiClient.getAllWithCapacityAndScoring();
return dto.leagues.map((league) => ({
id: league.id,
name: league.name,
description: (league as any).description ?? '',
ownerId: (league as any).ownerId ?? '',
createdAt: (league as any).createdAt ?? '',
maxDrivers: (league as any).settings?.maxDrivers ?? 0,
usedDriverSlots: (league as any).usedSlots ?? 0,
structureSummary: 'TBD',
timingSummary: 'TBD'
description: league.description,
ownerId: league.ownerId,
createdAt: league.createdAt,
maxDrivers: league.settings?.maxDrivers ?? 0,
usedDriverSlots: league.usedSlots ?? 0,
structureSummary: league.scoring?.scoringPresetName ?? 'Custom rules',
scoringPatternSummary: league.scoring?.scoringPatternSummary,
timingSummary: league.timingSummary ?? '',
...(league.scoring ? { scoring: league.scoring } : {}),
}));
}
@@ -162,16 +165,16 @@ export class LeagueService {
// 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: any) => l.id === leagueId);
const allLeagues = await this.apiClient.getAllWithCapacityAndScoring();
const leagueDto = allLeagues.leagues.find((l) => l.id === leagueId);
if (!leagueDto) return null;
// LeagueWithCapacityDTO already carries core fields; fall back to placeholder description/owner when not provided
const league = {
id: leagueDto.id,
name: leagueDto.name,
description: (leagueDto as any).description ?? 'Description not available',
ownerId: (leagueDto as any).ownerId ?? 'owner-id',
description: leagueDto.description ?? 'Description not available',
ownerId: leagueDto.ownerId ?? 'owner-id',
};
// Get owner
@@ -245,12 +248,12 @@ export class LeagueService {
try {
// Get league basic info
const allLeagues = await this.apiClient.getAllWithCapacity();
const league = allLeagues.leagues.find((l: any) => l.id === leagueId);
const allLeagues = await this.apiClient.getAllWithCapacityAndScoring();
const league = allLeagues.leagues.find((l) => l.id === leagueId);
if (!league) return null;
// Get owner
const owner = await this.driversApiClient.getDriver((league as any).ownerId);
const owner = await this.driversApiClient.getDriver(league.ownerId);
// League scoring configuration is not exposed separately yet; use null to indicate "not configured" in the UI
const scoringConfig: LeagueScoringConfigDTO | null = null;

View File

@@ -0,0 +1,45 @@
export type LeagueCapacityAndScoringPrimaryChampionshipType =
| 'driver'
| 'team'
| 'nations'
| 'trophy';
export type LeagueCapacityAndScoringSummaryScoringDTO = {
gameId: string;
gameName: string;
primaryChampionshipType: LeagueCapacityAndScoringPrimaryChampionshipType;
scoringPresetId: string;
scoringPresetName: string;
dropPolicySummary: string;
scoringPatternSummary: string;
};
export type LeagueCapacityAndScoringSocialLinksDTO = {
discordUrl?: string;
youtubeUrl?: string;
websiteUrl?: string;
};
export type LeagueCapacityAndScoringSettingsDTO = {
maxDrivers: number;
sessionDuration?: number;
qualifyingFormat?: string;
};
export type LeagueWithCapacityAndScoringDTO = {
id: string;
name: string;
description: string;
ownerId: string;
createdAt: string;
settings: LeagueCapacityAndScoringSettingsDTO;
usedSlots: number;
socialLinks?: LeagueCapacityAndScoringSocialLinksDTO;
scoring?: LeagueCapacityAndScoringSummaryScoringDTO;
timingSummary?: string;
};
export type AllLeaguesWithCapacityAndScoringDTO = {
leagues: LeagueWithCapacityAndScoringDTO[];
totalCount: number;
};