resolve todos in website and api
This commit is contained in:
@@ -1,28 +1,49 @@
|
||||
import { GetLeagueProtestsOutputPort } from '@core/racing/application/ports/output/GetLeagueProtestsOutputPort';
|
||||
import { GetLeagueProtestsOutputPort, type ProtestOutputPort } from '@core/racing/application/ports/output/GetLeagueProtestsOutputPort';
|
||||
import { LeagueAdminProtestsDTO } from '../dtos/LeagueAdminProtestsDTO';
|
||||
import { ProtestDTO } from '../dtos/ProtestDTO';
|
||||
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
||||
import { DriverDTO } from '../../driver/dtos/DriverDTO';
|
||||
|
||||
export function mapGetLeagueProtestsOutputPortToDTO(output: GetLeagueProtestsOutputPort): LeagueAdminProtestsDTO {
|
||||
const protests: ProtestDTO[] = output.protests.map(protest => ({
|
||||
id: protest.id,
|
||||
raceId: protest.raceId,
|
||||
protestingDriverId: protest.protestingDriverId,
|
||||
accusedDriverId: protest.accusedDriverId,
|
||||
submittedAt: new Date(protest.filedAt),
|
||||
description: protest.incident.description,
|
||||
status: protest.status as 'pending' | 'accepted' | 'rejected', // TODO: map properly
|
||||
}));
|
||||
function mapProtestStatus(status: ProtestOutputPort['status']): ProtestDTO['status'] {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
case 'awaiting_defense':
|
||||
case 'under_review':
|
||||
return 'pending';
|
||||
case 'upheld':
|
||||
return 'accepted';
|
||||
case 'dismissed':
|
||||
case 'withdrawn':
|
||||
return 'rejected';
|
||||
default:
|
||||
return 'pending';
|
||||
}
|
||||
}
|
||||
|
||||
export function mapGetLeagueProtestsOutputPortToDTO(output: GetLeagueProtestsOutputPort, leagueName?: string): LeagueAdminProtestsDTO {
|
||||
const protests: ProtestDTO[] = output.protests.map((protest) => {
|
||||
const race = output.racesById[protest.raceId];
|
||||
|
||||
return {
|
||||
id: protest.id,
|
||||
leagueId: race?.leagueId,
|
||||
raceId: protest.raceId,
|
||||
protestingDriverId: protest.protestingDriverId,
|
||||
accusedDriverId: protest.accusedDriverId,
|
||||
submittedAt: new Date(protest.filedAt),
|
||||
description: protest.incident.description,
|
||||
status: mapProtestStatus(protest.status),
|
||||
};
|
||||
});
|
||||
|
||||
const racesById: { [raceId: string]: RaceDTO } = {};
|
||||
for (const raceId in output.racesById) {
|
||||
const race = output.racesById[raceId];
|
||||
racesById[raceId] = {
|
||||
id: race.id,
|
||||
name: race.track, // assuming name is track
|
||||
name: race.track,
|
||||
date: race.scheduledAt,
|
||||
leagueName: undefined, // TODO: get league name if needed
|
||||
leagueName,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { LeagueConfigPresenter } from './LeagueConfigPresenter';
|
||||
import type { LeagueFullConfigOutputPort } from '@core/racing/application/ports/output/LeagueFullConfigOutputPort';
|
||||
|
||||
describe('LeagueConfigPresenter', () => {
|
||||
const createFullConfig = (overrides: Partial<LeagueFullConfigOutputPort> = {}): LeagueFullConfigOutputPort => {
|
||||
const base: LeagueFullConfigOutputPort = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
league: {
|
||||
id: 'league-1',
|
||||
name: 'Test League',
|
||||
description: 'Desc',
|
||||
ownerId: 'owner-1',
|
||||
settings: { pointsSystem: 'custom' },
|
||||
createdAt: new Date(),
|
||||
} as any,
|
||||
activeSeason: {
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
gameId: 'iracing',
|
||||
name: 'Season 1',
|
||||
status: 'planned',
|
||||
schedule: {
|
||||
startDate: new Date('2025-01-05T19:00:00Z'),
|
||||
timeOfDay: { hour: 20, minute: 0 } as any,
|
||||
} as any,
|
||||
dropPolicy: { strategy: 'bestNResults', n: 3 } as any,
|
||||
stewardingConfig: {
|
||||
decisionMode: 'steward_vote',
|
||||
requiredVotes: 3,
|
||||
requireDefense: true,
|
||||
defenseTimeLimit: 24,
|
||||
voteTimeLimit: 24,
|
||||
protestDeadlineHours: 48,
|
||||
stewardingClosesHours: 72,
|
||||
notifyAccusedOnProtest: true,
|
||||
notifyOnVoteRequired: true,
|
||||
} as any,
|
||||
} as any,
|
||||
scoringConfig: {
|
||||
id: 'scoring-1',
|
||||
seasonId: 'season-1',
|
||||
championships: [
|
||||
{
|
||||
id: 'champ-1',
|
||||
name: 'Drivers',
|
||||
type: 'driver' as any,
|
||||
sessionTypes: ['race'] as any,
|
||||
pointsTableBySessionType: {
|
||||
race: {
|
||||
getPointsForPosition: (pos: number) => (pos === 1 ? 25 : 0),
|
||||
} as any,
|
||||
},
|
||||
dropScorePolicy: { strategy: 'bestNResults', count: 3 } as any,
|
||||
},
|
||||
],
|
||||
} as any,
|
||||
game: undefined,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
it('maps league config into form model with scoring and timings', () => {
|
||||
const presenter = new LeagueConfigPresenter();
|
||||
const fullConfig = createFullConfig();
|
||||
|
||||
presenter.present(fullConfig);
|
||||
const vm = presenter.getViewModel();
|
||||
|
||||
expect(vm).not.toBeNull();
|
||||
expect(vm!.leagueId).toBe('league-1');
|
||||
expect(vm!.basics.name).toBe('Test League');
|
||||
expect(vm!.scoring.type).toBe('custom');
|
||||
expect(vm!.scoring.points).toBe(25);
|
||||
expect(vm!.championships.length).toBe(1);
|
||||
expect(vm!.timings.raceTimeHour).toBe(20);
|
||||
expect(vm!.timings.raceTimeMinute).toBe(0);
|
||||
expect(vm!.dropPolicy.strategy).toBe('worst_n');
|
||||
expect(vm!.dropPolicy.n).toBe(3);
|
||||
expect(vm!.stewarding.decisionMode).toBe('committee_vote');
|
||||
});
|
||||
});
|
||||
@@ -10,45 +10,64 @@ export class LeagueConfigPresenter implements Presenter<LeagueFullConfigOutputPo
|
||||
}
|
||||
|
||||
present(dto: LeagueFullConfigOutputPort) {
|
||||
// Map from LeagueFullConfigOutputPort to LeagueConfigFormModelDTO
|
||||
const league = dto.league;
|
||||
const settings = league.settings;
|
||||
const stewarding = settings.stewarding;
|
||||
const stewarding = dto.activeSeason?.stewardingConfig;
|
||||
const dropPolicy = dto.activeSeason?.dropPolicy;
|
||||
const schedule = dto.activeSeason?.schedule;
|
||||
const scoringConfig = dto.scoringConfig;
|
||||
|
||||
const visibility: 'public' | 'private' = 'public';
|
||||
|
||||
const championships = scoringConfig?.championships ?? [];
|
||||
|
||||
const firstChampionship = championships[0];
|
||||
const firstSessionType = firstChampionship?.sessionTypes[0];
|
||||
const firstPointsTable = firstSessionType
|
||||
? firstChampionship.pointsTableBySessionType[firstSessionType]
|
||||
: undefined;
|
||||
const pointsForWin = firstPointsTable?.getPointsForPosition(1) ?? 0;
|
||||
|
||||
const raceDayOfWeek = schedule?.startDate
|
||||
? schedule.startDate.toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase()
|
||||
: 'sunday';
|
||||
const raceTimeHour = schedule?.timeOfDay?.hour ?? 20;
|
||||
const raceTimeMinute = schedule?.timeOfDay?.minute ?? 0;
|
||||
|
||||
this.result = {
|
||||
leagueId: league.id,
|
||||
basics: {
|
||||
name: league.name,
|
||||
description: league.description,
|
||||
visibility: 'public', // TODO: Map visibility from league
|
||||
visibility,
|
||||
},
|
||||
structure: {
|
||||
mode: 'solo', // TODO: Map from league settings
|
||||
mode: 'solo',
|
||||
},
|
||||
championships: [], // TODO: Map championships
|
||||
championships,
|
||||
scoring: {
|
||||
type: 'standard', // TODO: Map scoring type
|
||||
points: 25, // TODO: Map points
|
||||
type: settings.pointsSystem,
|
||||
points: pointsForWin,
|
||||
},
|
||||
dropPolicy: {
|
||||
strategy: 'none', // TODO: Map
|
||||
n: 0,
|
||||
strategy: dropPolicy?.strategy === 'none' ? 'none' : 'worst_n',
|
||||
n: dropPolicy?.n,
|
||||
},
|
||||
timings: {
|
||||
raceDayOfWeek: 'sunday', // TODO: Map from timings
|
||||
raceTimeHour: 20,
|
||||
raceTimeMinute: 0,
|
||||
raceDayOfWeek,
|
||||
raceTimeHour,
|
||||
raceTimeMinute,
|
||||
},
|
||||
stewarding: {
|
||||
decisionMode: stewarding?.decisionMode === 'steward_vote' ? 'committee_vote' : 'single_steward',
|
||||
requireDefense: stewarding?.requireDefense || false,
|
||||
defenseTimeLimit: stewarding?.defenseTimeLimit || 48,
|
||||
voteTimeLimit: stewarding?.voteTimeLimit || 72,
|
||||
protestDeadlineHours: stewarding?.protestDeadlineHours || 48,
|
||||
stewardingClosesHours: stewarding?.stewardingClosesHours || 168,
|
||||
notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest || true,
|
||||
notifyOnVoteRequired: stewarding?.notifyOnVoteRequired || true,
|
||||
requiredVotes: stewarding?.requiredVotes || 0,
|
||||
requireDefense: stewarding?.requireDefense ?? false,
|
||||
defenseTimeLimit: stewarding?.defenseTimeLimit ?? 48,
|
||||
voteTimeLimit: stewarding?.voteTimeLimit ?? 72,
|
||||
protestDeadlineHours: stewarding?.protestDeadlineHours ?? 48,
|
||||
stewardingClosesHours: stewarding?.stewardingClosesHours ?? 168,
|
||||
notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest ?? true,
|
||||
notifyOnVoteRequired: stewarding?.notifyOnVoteRequired ?? true,
|
||||
requiredVotes: stewarding?.requiredVotes,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,13 +2,22 @@ import { GetLeagueScheduleOutputPort } from '@core/racing/application/ports/outp
|
||||
import { LeagueScheduleDTO } from '../dtos/LeagueScheduleDTO';
|
||||
import { RaceDTO } from '../../race/dtos/RaceDTO';
|
||||
|
||||
export function mapGetLeagueScheduleOutputPortToDTO(output: GetLeagueScheduleOutputPort): LeagueScheduleDTO {
|
||||
export function mapGetLeagueScheduleOutputPortToDTO(output: GetLeagueScheduleOutputPort, leagueName?: string): LeagueScheduleDTO {
|
||||
return {
|
||||
races: output.races.map(race => ({
|
||||
races: output.races.map<RaceDTO>(race => ({
|
||||
id: race.id,
|
||||
name: race.name,
|
||||
date: race.scheduledAt.toISOString(),
|
||||
leagueName: undefined, // TODO: get league name if needed
|
||||
leagueName,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function mapGetLeagueScheduleOutputPortToRaceDTOs(output: GetLeagueScheduleOutputPort, leagueName?: string): RaceDTO[] {
|
||||
return output.races.map<RaceDTO>(race => ({
|
||||
id: race.id,
|
||||
name: race.name,
|
||||
date: race.scheduledAt.toISOString(),
|
||||
leagueName,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user