refactor use cases

This commit is contained in:
2026-01-08 15:34:51 +01:00
parent d984ab24a8
commit 52e9a2f6a7
362 changed files with 5192 additions and 8409 deletions

View File

@@ -12,8 +12,6 @@ import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepo
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
import type { Logger } from '@core/shared/application/Logger';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import { Result } from '@core/shared/application/Result';
// Import concrete in-memory implementations
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
@@ -43,29 +41,6 @@ import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
// Import use case result types
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
import type { GetTotalRacesResult } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
import type { ImportRaceResultsApiResult } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
import type { GetRaceDetailResult } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
import type { GetRacesPageDataResult } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
import type { GetAllRacesPageDataResult } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
import type { GetRaceResultsDetailResult } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
import type { GetRaceWithSOFResult } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
import type { GetRaceProtestsResult } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
import type { GetRacePenaltiesResult } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
import type { RegisterForRaceResult } from '@core/racing/application/use-cases/RegisterForRaceUseCase';
import type { WithdrawFromRaceResult } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';
import type { CancelRaceResult } from '@core/racing/application/use-cases/CancelRaceUseCase';
import type { CompleteRaceResult } from '@core/racing/application/use-cases/CompleteRaceUseCase';
import type { ReopenRaceResult } from '@core/racing/application/use-cases/ReopenRaceUseCase';
import type { ImportRaceResultsResult } from '@core/racing/application/use-cases/ImportRaceResultsUseCase';
import type { FileProtestResult } from '@core/racing/application/use-cases/FileProtestUseCase';
import type { QuickPenaltyResult } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
import type { ApplyPenaltyResult } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
import type { RequestProtestDefenseResult } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
import type { ReviewProtestResult } from '@core/racing/application/use-cases/ReviewProtestUseCase';
// Import presenters
import { AllRacesPageDataPresenter } from './presenters/AllRacesPageDataPresenter';
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
@@ -107,183 +82,6 @@ import {
export * from './RaceTokens';
// Adapter classes to bridge presenters with UseCaseOutputPort interface
class GetAllRacesOutputAdapter implements UseCaseOutputPort<GetAllRacesResult> {
constructor(private presenter: GetAllRacesPresenter) {}
present(result: GetAllRacesResult): void {
this.presenter.present(result);
}
}
class GetTotalRacesOutputAdapter implements UseCaseOutputPort<GetTotalRacesResult> {
constructor(private presenter: GetTotalRacesPresenter) {}
present(result: GetTotalRacesResult): void {
// Wrap the result in a Result.ok() to match presenter expectations
const resultWrapper = Result.ok<GetTotalRacesResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
this.presenter.present(resultWrapper);
}
}
class ImportRaceResultsApiOutputAdapter implements UseCaseOutputPort<ImportRaceResultsApiResult> {
constructor(private presenter: ImportRaceResultsApiPresenter) {}
present(result: ImportRaceResultsApiResult): void {
const resultWrapper = Result.ok<ImportRaceResultsApiResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
this.presenter.present(resultWrapper);
}
}
class RaceDetailOutputAdapter implements UseCaseOutputPort<GetRaceDetailResult> {
constructor(private presenter: RaceDetailPresenter) {}
present(result: GetRaceDetailResult): void {
this.presenter.present(result);
}
}
class RacesPageDataOutputAdapter implements UseCaseOutputPort<GetRacesPageDataResult> {
constructor(private presenter: RacesPageDataPresenter) {}
present(result: GetRacesPageDataResult): void {
const resultWrapper = Result.ok<GetRacesPageDataResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
this.presenter.present(resultWrapper);
}
}
class AllRacesPageDataOutputAdapter implements UseCaseOutputPort<GetAllRacesPageDataResult> {
constructor(private presenter: AllRacesPageDataPresenter) {}
present(result: GetAllRacesPageDataResult): void {
const resultWrapper = Result.ok<GetAllRacesPageDataResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
this.presenter.present(resultWrapper);
}
}
class RaceResultsDetailOutputAdapter implements UseCaseOutputPort<GetRaceResultsDetailResult> {
constructor(private presenter: RaceResultsDetailPresenter) {}
present(result: GetRaceResultsDetailResult): void {
this.presenter.present(result);
}
}
class RaceWithSOFOutputAdapter implements UseCaseOutputPort<GetRaceWithSOFResult> {
constructor(private presenter: RaceWithSOFPresenter) {}
present(result: GetRaceWithSOFResult): void {
const resultWrapper = Result.ok<GetRaceWithSOFResult, { code: 'RACE_NOT_FOUND' | 'REPOSITORY_ERROR'; details: { message: string } }>(result);
this.presenter.present(resultWrapper);
}
}
class RaceProtestsOutputAdapter implements UseCaseOutputPort<GetRaceProtestsResult> {
constructor(private presenter: RaceProtestsPresenter) {}
present(result: GetRaceProtestsResult): void {
const resultWrapper = Result.ok<GetRaceProtestsResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
this.presenter.present(resultWrapper);
}
}
class RacePenaltiesOutputAdapter implements UseCaseOutputPort<GetRacePenaltiesResult> {
constructor(private presenter: RacePenaltiesPresenter) {}
present(result: GetRacePenaltiesResult): void {
const resultWrapper = Result.ok<GetRacePenaltiesResult, { code: 'REPOSITORY_ERROR'; details: { message: string } }>(result);
this.presenter.present(resultWrapper);
}
}
class RegisterForRaceOutputAdapter implements UseCaseOutputPort<RegisterForRaceResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Race registered successfully');
}
}
class WithdrawFromRaceOutputAdapter implements UseCaseOutputPort<WithdrawFromRaceResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Race withdrawal successful');
}
}
class CancelRaceOutputAdapter implements UseCaseOutputPort<CancelRaceResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Race cancelled successfully');
}
}
class CompleteRaceOutputAdapter implements UseCaseOutputPort<CompleteRaceResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Race completed successfully');
}
}
class ReopenRaceOutputAdapter implements UseCaseOutputPort<ReopenRaceResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Race reopened successfully');
}
}
class ImportRaceResultsOutputAdapter implements UseCaseOutputPort<ImportRaceResultsResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Race results imported successfully');
}
}
class FileProtestOutputAdapter implements UseCaseOutputPort<FileProtestResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Protest filed successfully');
}
}
class QuickPenaltyOutputAdapter implements UseCaseOutputPort<QuickPenaltyResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Penalty applied successfully');
}
}
class ApplyPenaltyOutputAdapter implements UseCaseOutputPort<ApplyPenaltyResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Penalty applied successfully');
}
}
class RequestProtestDefenseOutputAdapter implements UseCaseOutputPort<RequestProtestDefenseResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Defense request sent successfully');
}
}
class ReviewProtestOutputAdapter implements UseCaseOutputPort<ReviewProtestResult> {
constructor(private presenter: CommandResultPresenter) {}
present(): void {
this.presenter.presentSuccess('Protest reviewed successfully');
}
}
export const RaceProviders: Provider[] = [
{
provide: DRIVER_RATING_PROVIDER_TOKEN,
@@ -354,24 +152,19 @@ export const RaceProviders: Provider[] = [
raceRepo: IRaceRepository,
leagueRepo: ILeagueRepository,
logger: Logger,
presenter: GetAllRacesPresenter,
) => {
const useCase = new GetAllRacesUseCase(raceRepo, leagueRepo, logger);
useCase.setOutput(new GetAllRacesOutputAdapter(presenter));
return useCase;
return new GetAllRacesUseCase(raceRepo, leagueRepo, logger);
},
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, GET_ALL_RACES_PRESENTER_TOKEN],
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GetTotalRacesUseCase,
useFactory: (
raceRepo: IRaceRepository,
logger: Logger,
presenter: GetTotalRacesPresenter,
) => {
return new GetTotalRacesUseCase(raceRepo, logger, new GetTotalRacesOutputAdapter(presenter));
return new GetTotalRacesUseCase(raceRepo);
},
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, GET_TOTAL_RACES_PRESENTER_TOKEN],
inject: [RACE_REPOSITORY_TOKEN],
},
{
provide: ImportRaceResultsApiUseCase,
@@ -382,7 +175,6 @@ export const RaceProviders: Provider[] = [
driverRepo: IDriverRepository,
standingRepo: IStandingRepository,
logger: Logger,
presenter: ImportRaceResultsApiPresenter,
) => {
return new ImportRaceResultsApiUseCase(
raceRepo,
@@ -391,7 +183,6 @@ export const RaceProviders: Provider[] = [
driverRepo,
standingRepo,
logger,
new ImportRaceResultsApiOutputAdapter(presenter)
);
},
inject: [
@@ -401,7 +192,6 @@ export const RaceProviders: Provider[] = [
DRIVER_REPOSITORY_TOKEN,
STANDING_REPOSITORY_TOKEN,
LOGGER_TOKEN,
IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN,
],
},
{
@@ -413,7 +203,6 @@ export const RaceProviders: Provider[] = [
raceRegRepo: IRaceRegistrationRepository,
resultRepo: IResultRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
presenter: RaceDetailPresenter,
) => {
return new GetRaceDetailUseCase(
raceRepo,
@@ -422,7 +211,6 @@ export const RaceProviders: Provider[] = [
raceRegRepo,
resultRepo,
leagueMembershipRepo,
new RaceDetailOutputAdapter(presenter),
);
},
inject: [
@@ -432,7 +220,6 @@ export const RaceProviders: Provider[] = [
RACE_REGISTRATION_REPOSITORY_TOKEN,
RESULT_REPOSITORY_TOKEN,
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
RACE_DETAIL_PRESENTER_TOKEN,
],
},
{
@@ -441,16 +228,14 @@ export const RaceProviders: Provider[] = [
raceRepo: IRaceRepository,
leagueRepo: ILeagueRepository,
logger: Logger,
presenter: RacesPageDataPresenter,
) => {
return new GetRacesPageDataUseCase(
raceRepo,
leagueRepo,
logger,
new RacesPageDataOutputAdapter(presenter)
);
},
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, RACES_PAGE_DATA_PRESENTER_TOKEN],
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GetAllRacesPageDataUseCase,
@@ -458,16 +243,14 @@ export const RaceProviders: Provider[] = [
raceRepo: IRaceRepository,
leagueRepo: ILeagueRepository,
logger: Logger,
presenter: AllRacesPageDataPresenter,
) => {
return new GetAllRacesPageDataUseCase(
raceRepo,
leagueRepo,
logger,
new AllRacesPageDataOutputAdapter(presenter)
);
},
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, ALL_RACES_PAGE_DATA_PRESENTER_TOKEN],
inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GetRaceResultsDetailUseCase,
@@ -477,7 +260,6 @@ export const RaceProviders: Provider[] = [
resultRepo: IResultRepository,
driverRepo: IDriverRepository,
penaltyRepo: IPenaltyRepository,
presenter: RaceResultsDetailPresenter,
) => {
return new GetRaceResultsDetailUseCase(
raceRepo,
@@ -485,7 +267,6 @@ export const RaceProviders: Provider[] = [
resultRepo,
driverRepo,
penaltyRepo,
new RaceResultsDetailOutputAdapter(presenter)
);
},
inject: [
@@ -494,7 +275,6 @@ export const RaceProviders: Provider[] = [
RESULT_REPOSITORY_TOKEN,
DRIVER_REPOSITORY_TOKEN,
PENALTY_REPOSITORY_TOKEN,
RACE_RESULTS_DETAIL_PRESENTER_TOKEN,
],
},
{
@@ -504,7 +284,6 @@ export const RaceProviders: Provider[] = [
raceRegRepo: IRaceRegistrationRepository,
resultRepo: IResultRepository,
driverRatingProvider: DriverRatingProvider,
presenter: RaceWithSOFPresenter,
) => {
return new GetRaceWithSOFUseCase(
raceRepo,
@@ -514,7 +293,6 @@ export const RaceProviders: Provider[] = [
const rating = driverRatingProvider.getRating(input.driverId);
return { rating };
},
new RaceWithSOFOutputAdapter(presenter)
);
},
inject: [
@@ -522,7 +300,6 @@ export const RaceProviders: Provider[] = [
RACE_REGISTRATION_REPOSITORY_TOKEN,
RESULT_REPOSITORY_TOKEN,
DRIVER_RATING_PROVIDER_TOKEN,
RACE_WITH_SOF_PRESENTER_TOKEN,
],
},
{
@@ -530,30 +307,26 @@ export const RaceProviders: Provider[] = [
useFactory: (
protestRepo: IProtestRepository,
driverRepo: IDriverRepository,
presenter: RaceProtestsPresenter,
) => {
return new GetRaceProtestsUseCase(
protestRepo,
driverRepo,
new RaceProtestsOutputAdapter(presenter)
);
},
inject: [PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, RACE_PROTESTS_PRESENTER_TOKEN],
inject: [PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN],
},
{
provide: GetRacePenaltiesUseCase,
useFactory: (
penaltyRepo: IPenaltyRepository,
driverRepo: IDriverRepository,
presenter: RacePenaltiesPresenter,
) => {
return new GetRacePenaltiesUseCase(
penaltyRepo,
driverRepo,
new RacePenaltiesOutputAdapter(presenter)
);
},
inject: [PENALTY_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, RACE_PENALTIES_PRESENTER_TOKEN],
inject: [PENALTY_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN],
},
{
provide: RegisterForRaceUseCase,
@@ -561,16 +334,14 @@ export const RaceProviders: Provider[] = [
raceRegRepo: IRaceRegistrationRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new RegisterForRaceUseCase(
raceRegRepo,
leagueMembershipRepo,
logger,
new RegisterForRaceOutputAdapter(presenter)
);
},
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: WithdrawFromRaceUseCase,
@@ -578,31 +349,27 @@ export const RaceProviders: Provider[] = [
raceRepo: IRaceRepository,
raceRegRepo: IRaceRegistrationRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new WithdrawFromRaceUseCase(
raceRepo,
raceRegRepo,
logger,
new WithdrawFromRaceOutputAdapter(presenter)
);
},
inject: [RACE_REPOSITORY_TOKEN, RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [RACE_REPOSITORY_TOKEN, RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: CancelRaceUseCase,
useFactory: (
raceRepo: IRaceRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new CancelRaceUseCase(
raceRepo,
logger,
new CancelRaceOutputAdapter(presenter)
);
},
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: CompleteRaceUseCase,
@@ -612,7 +379,6 @@ export const RaceProviders: Provider[] = [
resultRepo: IResultRepository,
standingRepo: IStandingRepository,
driverRatingProvider: DriverRatingProvider,
presenter: CommandResultPresenter,
) => {
return new CompleteRaceUseCase(
raceRepo,
@@ -623,7 +389,6 @@ export const RaceProviders: Provider[] = [
const rating = driverRatingProvider.getRating(input.driverId);
return { rating, ratingChange: null };
},
new CompleteRaceOutputAdapter(presenter)
);
},
inject: [
@@ -632,7 +397,6 @@ export const RaceProviders: Provider[] = [
RESULT_REPOSITORY_TOKEN,
STANDING_REPOSITORY_TOKEN,
DRIVER_RATING_PROVIDER_TOKEN,
COMMAND_RESULT_PRESENTER_TOKEN,
],
},
{
@@ -640,15 +404,13 @@ export const RaceProviders: Provider[] = [
useFactory: (
raceRepo: IRaceRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new ReopenRaceUseCase(
raceRepo,
logger,
new ReopenRaceOutputAdapter(presenter)
);
},
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: ImportRaceResultsUseCase,
@@ -659,7 +421,6 @@ export const RaceProviders: Provider[] = [
driverRepo: IDriverRepository,
standingRepo: IStandingRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new ImportRaceResultsUseCase(
raceRepo,
@@ -668,7 +429,6 @@ export const RaceProviders: Provider[] = [
driverRepo,
standingRepo,
logger,
new ImportRaceResultsOutputAdapter(presenter)
);
},
inject: [
@@ -678,7 +438,6 @@ export const RaceProviders: Provider[] = [
DRIVER_REPOSITORY_TOKEN,
STANDING_REPOSITORY_TOKEN,
LOGGER_TOKEN,
COMMAND_RESULT_PRESENTER_TOKEN,
],
},
{
@@ -687,16 +446,14 @@ export const RaceProviders: Provider[] = [
protestRepo: IProtestRepository,
raceRepo: IRaceRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
presenter: CommandResultPresenter,
) => {
return new FileProtestUseCase(
protestRepo,
raceRepo,
leagueMembershipRepo,
new FileProtestOutputAdapter(presenter)
);
},
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN],
},
{
provide: QuickPenaltyUseCase,
@@ -705,17 +462,15 @@ export const RaceProviders: Provider[] = [
raceRepo: IRaceRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new QuickPenaltyUseCase(
penaltyRepo,
raceRepo,
leagueMembershipRepo,
logger,
new QuickPenaltyOutputAdapter(presenter)
);
},
inject: [PENALTY_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [PENALTY_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: ApplyPenaltyUseCase,
@@ -725,7 +480,6 @@ export const RaceProviders: Provider[] = [
raceRepo: IRaceRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new ApplyPenaltyUseCase(
penaltyRepo,
@@ -733,10 +487,9 @@ export const RaceProviders: Provider[] = [
raceRepo,
leagueMembershipRepo,
logger,
new ApplyPenaltyOutputAdapter(presenter)
);
},
inject: [PENALTY_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [PENALTY_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: RequestProtestDefenseUseCase,
@@ -745,17 +498,15 @@ export const RaceProviders: Provider[] = [
raceRepo: IRaceRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new RequestProtestDefenseUseCase(
protestRepo,
raceRepo,
leagueMembershipRepo,
logger,
new RequestProtestDefenseOutputAdapter(presenter)
);
},
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: ReviewProtestUseCase,
@@ -764,16 +515,14 @@ export const RaceProviders: Provider[] = [
raceRepo: IRaceRepository,
leagueMembershipRepo: ILeagueMembershipRepository,
logger: Logger,
presenter: CommandResultPresenter,
) => {
return new ReviewProtestUseCase(
protestRepo,
raceRepo,
leagueMembershipRepo,
logger,
new ReviewProtestOutputAdapter(presenter)
);
},
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN],
inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
];

View File

@@ -1,20 +1,24 @@
import { describe, expect, it, vi } from 'vitest';
import { RaceService } from './RaceService';
import { Result } from '@core/shared/application/Result';
describe('RaceService', () => {
it('invokes each use case and returns the corresponding presenter', async () => {
const mkUseCase = () => ({ execute: vi.fn(async () => {}) });
// Mock use cases to return Result.ok()
const mkUseCase = (resultValue: any = { success: true }) => ({
execute: vi.fn(async () => Result.ok(resultValue))
});
const getAllRacesUseCase = mkUseCase();
const getTotalRacesUseCase = mkUseCase();
const importRaceResultsApiUseCase = mkUseCase();
const getRaceDetailUseCase = mkUseCase();
const getRacesPageDataUseCase = mkUseCase();
const getAllRacesPageDataUseCase = mkUseCase();
const getRaceResultsDetailUseCase = mkUseCase();
const getRaceWithSOFUseCase = mkUseCase();
const getRaceProtestsUseCase = mkUseCase();
const getRacePenaltiesUseCase = mkUseCase();
const getAllRacesUseCase = mkUseCase({ races: [], leagues: [] });
const getTotalRacesUseCase = mkUseCase({ totalRaces: 0 });
const importRaceResultsApiUseCase = mkUseCase({ success: true, raceId: 'r1', driversProcessed: 0, resultsRecorded: 0, errors: [] });
const getRaceDetailUseCase = mkUseCase({ race: null, league: null, drivers: [], isUserRegistered: false, canRegister: false });
const getRacesPageDataUseCase = mkUseCase({ races: [] });
const getAllRacesPageDataUseCase = mkUseCase({ races: [], filters: { statuses: [], leagues: [] } });
const getRaceResultsDetailUseCase = mkUseCase({ race: null, results: [], penalties: [] });
const getRaceWithSOFUseCase = mkUseCase({ race: null, strengthOfField: 0, participantCount: 0, registeredCount: 0, maxParticipants: 0 });
const getRaceProtestsUseCase = mkUseCase({ protests: [], drivers: [] });
const getRacePenaltiesUseCase = mkUseCase({ penalties: [], drivers: [] });
const registerForRaceUseCase = mkUseCase();
const withdrawFromRaceUseCase = mkUseCase();
const cancelRaceUseCase = mkUseCase();
@@ -28,17 +32,17 @@ describe('RaceService', () => {
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
const getAllRacesPresenter = {} as any;
const getTotalRacesPresenter = {} as any;
const importRaceResultsApiPresenter = {} as any;
const raceDetailPresenter = {} as any;
const racesPageDataPresenter = {} as any;
const allRacesPageDataPresenter = {} as any;
const raceResultsDetailPresenter = {} as any;
const raceWithSOFPresenter = {} as any;
const raceProtestsPresenter = {} as any;
const racePenaltiesPresenter = {} as any;
const commandResultPresenter = {} as any;
const getAllRacesPresenter = { present: vi.fn() } as any;
const getTotalRacesPresenter = { present: vi.fn() } as any;
const importRaceResultsApiPresenter = { present: vi.fn() } as any;
const raceDetailPresenter = { present: vi.fn() } as any;
const racesPageDataPresenter = { present: vi.fn() } as any;
const allRacesPageDataPresenter = { present: vi.fn() } as any;
const raceResultsDetailPresenter = { present: vi.fn() } as any;
const raceWithSOFPresenter = { present: vi.fn() } as any;
const raceProtestsPresenter = { present: vi.fn() } as any;
const racePenaltiesPresenter = { present: vi.fn() } as any;
const commandResultPresenter = { present: vi.fn() } as any;
const service = new RaceService(
getAllRacesUseCase as any,
@@ -77,62 +81,81 @@ describe('RaceService', () => {
expect(await service.getAllRaces()).toBe(getAllRacesPresenter);
expect(getAllRacesUseCase.execute).toHaveBeenCalledWith({});
expect(getAllRacesPresenter.present).toHaveBeenCalledWith({ races: [], leagues: [] });
expect(await service.getTotalRaces()).toBe(getTotalRacesPresenter);
expect(getTotalRacesUseCase.execute).toHaveBeenCalledWith({});
expect(getTotalRacesPresenter.present).toHaveBeenCalledWith({ totalRaces: 0 });
expect(await service.importRaceResults({ raceId: 'r1', resultsFileContent: 'x' } as any)).toBe(importRaceResultsApiPresenter);
expect(importRaceResultsApiUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', resultsFileContent: 'x' });
expect(importRaceResultsApiPresenter.present).toHaveBeenCalledWith({ success: true, raceId: 'r1', driversProcessed: 0, resultsRecorded: 0, errors: [] });
expect(await service.getRaceDetail({ raceId: 'r1' } as any)).toBe(raceDetailPresenter);
expect(getRaceDetailUseCase.execute).toHaveBeenCalled();
expect(await service.getRacesPageData('l1')).toBe(racesPageDataPresenter);
expect(getRacesPageDataUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
expect(racesPageDataPresenter.present).toHaveBeenCalledWith({ races: [] });
expect(await service.getAllRacesPageData()).toBe(allRacesPageDataPresenter);
expect(getAllRacesPageDataUseCase.execute).toHaveBeenCalledWith({});
expect(allRacesPageDataPresenter.present).toHaveBeenCalledWith({ races: [], filters: { statuses: [], leagues: [] } });
expect(await service.getRaceResultsDetail('r1')).toBe(raceResultsDetailPresenter);
expect(getRaceResultsDetailUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(raceResultsDetailPresenter.present).toHaveBeenCalledWith({ race: null, results: [], penalties: [] });
expect(await service.getRaceWithSOF('r1')).toBe(raceWithSOFPresenter);
expect(getRaceWithSOFUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(raceWithSOFPresenter.present).toHaveBeenCalledWith({ race: null, strengthOfField: 0, participantCount: 0, registeredCount: 0, maxParticipants: 0 });
expect(await service.getRaceProtests('r1')).toBe(raceProtestsPresenter);
expect(getRaceProtestsUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(raceProtestsPresenter.present).toHaveBeenCalledWith({ protests: [], drivers: [] });
expect(await service.getRacePenalties('r1')).toBe(racePenaltiesPresenter);
expect(getRacePenaltiesUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(racePenaltiesPresenter.present).toHaveBeenCalledWith({ penalties: [], drivers: [] });
expect(await service.registerForRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
expect(registerForRaceUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.withdrawFromRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
expect(withdrawFromRaceUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.cancelRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
expect(cancelRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', cancelledById: 'admin' });
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.completeRace({ raceId: 'r1' } as any)).toBe(commandResultPresenter);
expect(completeRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.reopenRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
expect(reopenRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', reopenedById: 'admin' });
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.fileProtest({} as any)).toBe(commandResultPresenter);
expect(fileProtestUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.applyQuickPenalty({} as any)).toBe(commandResultPresenter);
expect(quickPenaltyUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.applyPenalty({} as any)).toBe(commandResultPresenter);
expect(applyPenaltyUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.requestProtestDefense({} as any)).toBe(commandResultPresenter);
expect(requestProtestDefenseUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.reviewProtest({} as any)).toBe(commandResultPresenter);
expect(reviewProtestUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
});
});

View File

@@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
// DTOs
import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO';
@@ -116,55 +116,127 @@ export class RaceService {
async getAllRaces(): Promise<GetAllRacesPresenter> {
this.logger.debug('[RaceService] Fetching all races.');
await this.getAllRacesUseCase.execute({});
const result = await this.getAllRacesUseCase.execute({});
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get all races');
}
const value = result.unwrap();
this.getAllRacesPresenter.present(value);
return this.getAllRacesPresenter;
}
async getTotalRaces(): Promise<GetTotalRacesPresenter> {
this.logger.debug('[RaceService] Fetching total races count.');
await this.getTotalRacesUseCase.execute({});
const result = await this.getTotalRacesUseCase.execute({});
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get total races');
}
const value = result.unwrap();
this.getTotalRacesPresenter.present(value);
return this.getTotalRacesPresenter;
}
async importRaceResults(input: ImportRaceResultsDTO): Promise<ImportRaceResultsApiPresenter> {
this.logger.debug('Importing race results:', input);
await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent });
const result = await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to import race results');
}
const value = result.unwrap();
this.importRaceResultsApiPresenter.present(value);
return this.importRaceResultsApiPresenter;
}
async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailPresenter> {
this.logger.debug('[RaceService] Fetching race detail:', params);
await this.getRaceDetailUseCase.execute(params);
const result = await this.getRaceDetailUseCase.execute(params);
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get race detail');
}
const value = result.unwrap();
this.raceDetailPresenter.present(value);
return this.raceDetailPresenter;
}
async getRacesPageData(leagueId: string): Promise<RacesPageDataPresenter> {
this.logger.debug('[RaceService] Fetching races page data.');
await this.getRacesPageDataUseCase.execute({ leagueId });
const result = await this.getRacesPageDataUseCase.execute({ leagueId });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get races page data');
}
const value = result.unwrap();
this.racesPageDataPresenter.present(value);
return this.racesPageDataPresenter;
}
async getAllRacesPageData(): Promise<AllRacesPageDataPresenter> {
this.logger.debug('[RaceService] Fetching all races page data.');
await this.getAllRacesPageDataUseCase.execute({});
const result = await this.getAllRacesPageDataUseCase.execute({});
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get all races page data');
}
const value = result.unwrap();
this.allRacesPageDataPresenter.present(value);
return this.allRacesPageDataPresenter;
}
async getRaceResultsDetail(raceId: string): Promise<RaceResultsDetailPresenter> {
this.logger.debug('[RaceService] Fetching race results detail:', { raceId });
await this.getRaceResultsDetailUseCase.execute({ raceId });
const result = await this.getRaceResultsDetailUseCase.execute({ raceId });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get race results detail');
}
const value = result.unwrap();
this.raceResultsDetailPresenter.present(value);
return this.raceResultsDetailPresenter;
}
async getRaceWithSOF(raceId: string): Promise<RaceWithSOFPresenter> {
this.logger.debug('[RaceService] Fetching race with SOF:', { raceId });
await this.getRaceWithSOFUseCase.execute({ raceId });
const result = await this.getRaceWithSOFUseCase.execute({ raceId });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get race with SOF');
}
const value = result.unwrap();
this.raceWithSOFPresenter.present(value);
return this.raceWithSOFPresenter;
}
async getRaceProtests(raceId: string): Promise<RaceProtestsPresenter> {
this.logger.debug('[RaceService] Fetching race protests:', { raceId });
await this.getRaceProtestsUseCase.execute({ raceId });
const result = await this.getRaceProtestsUseCase.execute({ raceId });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get race protests');
}
const value = result.unwrap();
this.raceProtestsPresenter.present(value);
return this.raceProtestsPresenter;
}
@@ -186,67 +258,145 @@ export class RaceService {
async getRacePenalties(raceId: string): Promise<RacePenaltiesPresenter> {
this.logger.debug('[RaceService] Fetching race penalties:', { raceId });
await this.getRacePenaltiesUseCase.execute({ raceId });
const result = await this.getRacePenaltiesUseCase.execute({ raceId });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to get race penalties');
}
const value = result.unwrap();
this.racePenaltiesPresenter.present(value);
return this.racePenaltiesPresenter;
}
async registerForRace(params: RegisterForRaceParamsDTO): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Registering for race:', params);
await this.registerForRaceUseCase.execute(params);
const result = await this.registerForRaceUseCase.execute(params);
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to register for race');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async withdrawFromRace(params: WithdrawFromRaceParamsDTO): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Withdrawing from race:', params);
await this.withdrawFromRaceUseCase.execute(params);
const result = await this.withdrawFromRaceUseCase.execute(params);
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to withdraw from race');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async cancelRace(params: RaceActionParamsDTO, cancelledById: string): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Cancelling race:', params);
await this.cancelRaceUseCase.execute({ raceId: params.raceId, cancelledById });
const result = await this.cancelRaceUseCase.execute({ raceId: params.raceId, cancelledById });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to cancel race');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async completeRace(params: RaceActionParamsDTO): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Completing race:', params);
await this.completeRaceUseCase.execute({ raceId: params.raceId });
const result = await this.completeRaceUseCase.execute({ raceId: params.raceId });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to complete race');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async reopenRace(params: RaceActionParamsDTO, reopenedById: string): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Re-opening race:', params);
await this.reopenRaceUseCase.execute({ raceId: params.raceId, reopenedById });
const result = await this.reopenRaceUseCase.execute({ raceId: params.raceId, reopenedById });
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to reopen race');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async fileProtest(command: FileProtestCommandDTO): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Filing protest:', command);
await this.fileProtestUseCase.execute(command);
const result = await this.fileProtestUseCase.execute(command);
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to file protest');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async applyQuickPenalty(command: QuickPenaltyCommandDTO): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Applying quick penalty:', command);
await this.quickPenaltyUseCase.execute(command);
const result = await this.quickPenaltyUseCase.execute(command);
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to apply quick penalty');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async applyPenalty(command: ApplyPenaltyCommandDTO): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Applying penalty:', command);
await this.applyPenaltyUseCase.execute(command);
const result = await this.applyPenaltyUseCase.execute(command);
if (result.isErr()) {
// ApplyPenaltyUseCase errors don't have details, just code
throw new NotFoundException('Failed to apply penalty');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async requestProtestDefense(command: RequestProtestDefenseCommandDTO): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Requesting protest defense:', command);
await this.requestProtestDefenseUseCase.execute(command);
const result = await this.requestProtestDefenseUseCase.execute(command);
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to request protest defense');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
async reviewProtest(command: ReviewProtestCommandDTO): Promise<CommandResultPresenter> {
this.logger.debug('[RaceService] Reviewing protest:', command);
await this.reviewProtestUseCase.execute(command);
const result = await this.reviewProtestUseCase.execute(command);
if (result.isErr()) {
const error = result.unwrapErr();
throw new NotFoundException(error.details?.message ?? 'Failed to review protest');
}
this.commandResultPresenter.present();
return this.commandResultPresenter;
}
}

View File

@@ -1,18 +1,8 @@
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type {
GetAllRacesPageDataResult,
GetAllRacesPageDataErrorCode,
} from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
import type { GetAllRacesPageDataResult } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
export type AllRacesPageDataResponseModel = AllRacesPageDTO;
export type GetAllRacesPageDataApplicationError = ApplicationErrorCode<
GetAllRacesPageDataErrorCode,
{ message: string }
>;
export class AllRacesPageDataPresenter {
private model: AllRacesPageDataResponseModel | null = null;
@@ -20,19 +10,10 @@ export class AllRacesPageDataPresenter {
this.model = null;
}
present(
result: Result<GetAllRacesPageDataResult, GetAllRacesPageDataApplicationError>,
): void {
if (result.isErr()) {
const error = result.unwrapErr();
throw new Error(error.details?.message ?? 'Failed to get all races page data');
}
const output = result.unwrap();
present(result: GetAllRacesPageDataResult): void {
this.model = {
races: output.races,
filters: output.filters,
races: result.races,
filters: result.filters,
};
}
@@ -51,4 +32,4 @@ export class AllRacesPageDataPresenter {
get viewModel(): AllRacesPageDataResponseModel {
return this.responseModel;
}
}
}

View File

@@ -1,17 +1,9 @@
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
export interface CommandResultDTO {
success: boolean;
errorCode?: string;
message?: string;
}
export type CommandApplicationError = ApplicationErrorCode<
string,
{ message: string }
>;
export class CommandResultPresenter {
private model: CommandResultDTO | null = null;
@@ -19,17 +11,9 @@ export class CommandResultPresenter {
this.model = null;
}
present(result: Result<unknown, CommandApplicationError>): void {
if (result.isErr()) {
const error = result.unwrapErr();
this.model = {
success: false,
errorCode: error.code,
message: error.details?.message,
};
return;
}
present(): void {
// For command use cases, if we get here, it was successful
// The service handles errors by throwing exceptions
this.model = { success: true };
}
@@ -63,4 +47,4 @@ export class CommandResultPresenter {
get viewModel(): CommandResultDTO {
return this.responseModel;
}
}
}

View File

@@ -1,10 +1,9 @@
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { GetAllRacesResult } from '@core/racing/application/use-cases/GetAllRacesUseCase';
import type { AllRacesPageDTO } from '../dtos/AllRacesPageDTO';
export type GetAllRacesResponseModel = AllRacesPageDTO;
export class GetAllRacesPresenter implements UseCaseOutputPort<GetAllRacesResult> {
export class GetAllRacesPresenter {
private model: GetAllRacesResponseModel | null = null;
present(result: GetAllRacesResult): void {

View File

@@ -1,18 +1,8 @@
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type {
GetTotalRacesResult,
GetTotalRacesErrorCode,
} from '@core/racing/application/use-cases/GetTotalRacesUseCase';
import type { GetTotalRacesResult } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
import type { RaceStatsDTO } from '../dtos/RaceStatsDTO';
export type GetTotalRacesResponseModel = RaceStatsDTO;
export type GetTotalRacesApplicationError = ApplicationErrorCode<
GetTotalRacesErrorCode,
{ message: string }
>;
export class GetTotalRacesPresenter {
private model: GetTotalRacesResponseModel | null = null;
@@ -20,16 +10,9 @@ export class GetTotalRacesPresenter {
this.model = null;
}
present(result: Result<GetTotalRacesResult, GetTotalRacesApplicationError>): void {
if (result.isErr()) {
const error = result.unwrapErr();
throw new Error(error.details?.message ?? 'Failed to get total races');
}
const output = result.unwrap();
present(result: GetTotalRacesResult): void {
this.model = {
totalRaces: output.totalRaces,
totalRaces: result.totalRaces,
};
}

View File

@@ -1,18 +1,8 @@
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type {
ImportRaceResultsApiResult,
ImportRaceResultsApiErrorCode,
} from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
import type { ImportRaceResultsApiResult } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
import { ImportRaceResultsSummaryDTO } from '../dtos/ImportRaceResultsSummaryDTO';
export type ImportRaceResultsApiResponseModel = ImportRaceResultsSummaryDTO;
export type ImportRaceResultsApiApplicationError = ApplicationErrorCode<
ImportRaceResultsApiErrorCode,
{ message: string }
>;
export class ImportRaceResultsApiPresenter {
private model: ImportRaceResultsApiResponseModel | null = null;
@@ -20,22 +10,13 @@ export class ImportRaceResultsApiPresenter {
this.model = null;
}
present(
result: Result<ImportRaceResultsApiResult, ImportRaceResultsApiApplicationError>,
): void {
if (result.isErr()) {
const error = result.unwrapErr();
throw new Error(error.details?.message ?? 'Failed to import race results');
}
const output = result.unwrap();
present(result: ImportRaceResultsApiResult): void {
this.model = {
success: output.success,
raceId: output.raceId,
driversProcessed: output.driversProcessed,
resultsRecorded: output.resultsRecorded,
errors: output.errors,
success: result.success,
raceId: result.raceId,
driversProcessed: result.driversProcessed,
resultsRecorded: result.resultsRecorded,
errors: result.errors,
};
}

View File

@@ -1,4 +1,3 @@
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { GetRaceDetailResult } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
import type { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider';
import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
@@ -12,7 +11,7 @@ import type { RaceDetailUserResultDTO } from '../dtos/RaceDetailUserResultDTO';
export type GetRaceDetailResponseModel = RaceDetailDTO;
export class RaceDetailPresenter implements UseCaseOutputPort<GetRaceDetailResult> {
export class RaceDetailPresenter {
private result: GetRaceDetailResult | null = null;
constructor(
@@ -118,4 +117,4 @@ export class RaceDetailPresenter implements UseCaseOutputPort<GetRaceDetailResul
const positionBonus = Math.max(0, (20 - position) * 2);
return baseChange + positionBonus;
}
}
}

View File

@@ -1,19 +1,9 @@
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type {
GetRacePenaltiesResult,
GetRacePenaltiesErrorCode,
} from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
import type { GetRacePenaltiesResult } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
import type { RacePenaltiesDTO } from '../dtos/RacePenaltiesDTO';
import type { RacePenaltyDTO } from '../dtos/RacePenaltyDTO';
export type GetRacePenaltiesResponseModel = RacePenaltiesDTO;
export type GetRacePenaltiesApplicationError = ApplicationErrorCode<
GetRacePenaltiesErrorCode,
{ message: string }
>;
export class RacePenaltiesPresenter {
private model: GetRacePenaltiesResponseModel | null = null;
@@ -21,15 +11,8 @@ export class RacePenaltiesPresenter {
this.model = null;
}
present(result: Result<GetRacePenaltiesResult, GetRacePenaltiesApplicationError>): void {
if (result.isErr()) {
const error = result.unwrapErr();
throw new Error(error.details?.message ?? 'Failed to get race penalties');
}
const output = result.unwrap();
const penalties: RacePenaltyDTO[] = output.penalties.map(penalty => ({
present(result: GetRacePenaltiesResult): void {
const penalties: RacePenaltyDTO[] = result.penalties.map(penalty => ({
id: penalty.id,
driverId: penalty.driverId,
type: penalty.type,
@@ -41,7 +24,7 @@ export class RacePenaltiesPresenter {
} as RacePenaltyDTO));
const driverMap: Record<string, string> = {};
output.drivers.forEach(driver => {
result.drivers.forEach(driver => {
driverMap[driver.id] = driver.name.toString();
});
@@ -66,4 +49,4 @@ export class RacePenaltiesPresenter {
get viewModel(): GetRacePenaltiesResponseModel {
return this.responseModel;
}
}
}

View File

@@ -1,19 +1,9 @@
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type {
GetRaceProtestsResult,
GetRaceProtestsErrorCode,
} from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
import type { GetRaceProtestsResult } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
import type { RaceProtestsDTO } from '../dtos/RaceProtestsDTO';
import type { RaceProtestDTO } from '../dtos/RaceProtestDTO';
export type GetRaceProtestsResponseModel = RaceProtestsDTO;
export type GetRaceProtestsApplicationError = ApplicationErrorCode<
GetRaceProtestsErrorCode,
{ message: string }
>;
export class RaceProtestsPresenter {
private model: GetRaceProtestsResponseModel | null = null;
@@ -21,15 +11,8 @@ export class RaceProtestsPresenter {
this.model = null;
}
present(result: Result<GetRaceProtestsResult, GetRaceProtestsApplicationError>): void {
if (result.isErr()) {
const error = result.unwrapErr();
throw new Error(error.details?.message ?? 'Failed to get race protests');
}
const output = result.unwrap();
const protests: RaceProtestDTO[] = output.protests.map(protest => ({
present(result: GetRaceProtestsResult): void {
const protests: RaceProtestDTO[] = result.protests.map(protest => ({
id: protest.id,
protestingDriverId: protest.protestingDriverId,
accusedDriverId: protest.accusedDriverId,
@@ -42,7 +25,7 @@ export class RaceProtestsPresenter {
} as RaceProtestDTO));
const driverMap: Record<string, string> = {};
output.drivers.forEach(driver => {
result.drivers.forEach(driver => {
driverMap[driver.id] = driver.name.toString();
});
@@ -67,4 +50,4 @@ export class RaceProtestsPresenter {
get viewModel(): GetRaceProtestsResponseModel {
return this.responseModel;
}
}
}

View File

@@ -1,18 +1,8 @@
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type {
GetRaceWithSOFResult,
GetRaceWithSOFErrorCode,
} from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
import type { GetRaceWithSOFResult } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
import type { RaceWithSOFDTO } from '../dtos/RaceWithSOFDTO';
export type GetRaceWithSOFResponseModel = RaceWithSOFDTO;
export type GetRaceWithSOFApplicationError = ApplicationErrorCode<
GetRaceWithSOFErrorCode,
{ message: string }
>;
export class RaceWithSOFPresenter {
private model: GetRaceWithSOFResponseModel | null = null;
@@ -20,27 +10,11 @@ export class RaceWithSOFPresenter {
this.model = null;
}
present(result: Result<GetRaceWithSOFResult, GetRaceWithSOFApplicationError>): void {
if (result.isErr()) {
const error = result.unwrapErr();
if (error.code === 'RACE_NOT_FOUND') {
this.model = {
id: '',
track: '',
strengthOfField: null,
} as RaceWithSOFDTO;
return;
}
throw new Error(error.details?.message ?? 'Failed to get race with SOF');
}
const output = result.unwrap();
present(result: GetRaceWithSOFResult): void {
this.model = {
id: output.race.id,
track: output.race.track,
strengthOfField: output.strengthOfField,
id: result.race.id,
track: result.race.track,
strengthOfField: result.strengthOfField,
} as RaceWithSOFDTO;
}
@@ -59,4 +33,4 @@ export class RaceWithSOFPresenter {
get viewModel(): GetRaceWithSOFResponseModel {
return this.responseModel;
}
}
}

View File

@@ -1,19 +1,9 @@
import type { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type {
GetRacesPageDataResult,
GetRacesPageDataErrorCode,
} from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
import type { GetRacesPageDataResult } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
import type { RacesPageDataDTO } from '../dtos/RacesPageDataDTO';
import type { RacesPageDataRaceDTO } from '../dtos/RacesPageDataRaceDTO';
export type GetRacesPageDataResponseModel = RacesPageDataDTO;
export type GetRacesPageDataApplicationError = ApplicationErrorCode<
GetRacesPageDataErrorCode,
{ message: string }
>;
export class RacesPageDataPresenter {
private model: GetRacesPageDataResponseModel | null = null;
@@ -21,17 +11,8 @@ export class RacesPageDataPresenter {
this.model = null;
}
present(
result: Result<GetRacesPageDataResult, GetRacesPageDataApplicationError>,
): void {
if (result.isErr()) {
const error = result.unwrapErr();
throw new Error(error.details?.message ?? 'Failed to get races page data');
}
const output = result.unwrap();
const races: RacesPageDataRaceDTO[] = output.races.map(({ race, leagueName }) => ({
present(result: GetRacesPageDataResult): void {
const races: RacesPageDataRaceDTO[] = result.races.map(({ race, leagueName }) => ({
id: race.id,
track: race.track,
car: race.car,
@@ -63,4 +44,4 @@ export class RacesPageDataPresenter {
get viewModel(): GetRacesPageDataResponseModel {
return this.responseModel;
}
}
}