diff --git a/apps/api/src/domain/bootstrap/BootstrapModule.ts b/apps/api/src/domain/bootstrap/BootstrapModule.ts index 6085523db..54bf60676 100644 --- a/apps/api/src/domain/bootstrap/BootstrapModule.ts +++ b/apps/api/src/domain/bootstrap/BootstrapModule.ts @@ -1,4 +1,4 @@ -import { EnsureInitialData } from '@/adapters/bootstrap/EnsureInitialData'; +import { EnsureInitialData } from '../../../../../adapters/bootstrap/EnsureInitialData'; import { Module, OnModuleInit } from '@nestjs/common'; import { BootstrapProviders } from './BootstrapProviders'; diff --git a/apps/api/src/domain/bootstrap/BootstrapProviders.ts b/apps/api/src/domain/bootstrap/BootstrapProviders.ts index 828e99544..7085590bf 100644 --- a/apps/api/src/domain/bootstrap/BootstrapProviders.ts +++ b/apps/api/src/domain/bootstrap/BootstrapProviders.ts @@ -6,6 +6,7 @@ import type { IUserRepository } from '@core/identity/domain/repositories/IUserRe import type { IAchievementRepository } from '@core/identity/application/use-cases/achievement/CreateAchievementUseCase'; import type { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort'; import type { Logger } from '@core/shared/application'; +import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import { InMemoryUserRepository } from '../../../../../adapters/identity/persistence/inmemory/InMemoryUserRepository'; import { InMemoryAchievementRepository } from '../../../../../adapters/persistence/inmemory/achievement/InMemoryAchievementRepository'; import { CookieIdentitySessionAdapter } from '../../../../../adapters/identity/session/CookieIdentitySessionAdapter'; @@ -17,6 +18,21 @@ export const IDENTITY_SESSION_PORT_TOKEN = 'IdentitySessionPort_Bootstrap'; export const SIGNUP_USE_CASE_TOKEN = 'SignupWithEmailUseCase_Bootstrap'; export const CREATE_ACHIEVEMENT_USE_CASE_TOKEN = 'CreateAchievementUseCase_Bootstrap'; +// Adapter classes for output ports +class SignupWithEmailOutputAdapter implements UseCaseOutputPort { + present(result: any): void { + // Bootstrap doesn't need to handle output, just log success + console.log('[Bootstrap] Signup completed', result); + } +} + +class CreateAchievementOutputAdapter implements UseCaseOutputPort { + present(result: any): void { + // Bootstrap doesn't need to handle output, just log success + console.log('[Bootstrap] Achievement created', result); + } +} + export const BootstrapProviders: Provider[] = [ { provide: USER_REPOSITORY_TOKEN, @@ -34,21 +50,41 @@ export const BootstrapProviders: Provider[] = [ }, { provide: SIGNUP_USE_CASE_TOKEN, - useFactory: (userRepository: IUserRepository, sessionPort: IdentitySessionPort) => { - return new SignupWithEmailUseCase(userRepository, sessionPort); + useFactory: ( + userRepository: IUserRepository, + sessionPort: IdentitySessionPort, + logger: Logger + ) => { + return new SignupWithEmailUseCase( + userRepository, + sessionPort, + logger, + new SignupWithEmailOutputAdapter() + ); }, - inject: [USER_REPOSITORY_TOKEN, IDENTITY_SESSION_PORT_TOKEN], + inject: [USER_REPOSITORY_TOKEN, IDENTITY_SESSION_PORT_TOKEN, 'Logger'], }, { provide: CREATE_ACHIEVEMENT_USE_CASE_TOKEN, - useFactory: (achievementRepository: IAchievementRepository) => { - return new CreateAchievementUseCase(achievementRepository); + useFactory: ( + achievementRepository: IAchievementRepository, + logger: Logger + ) => { + return new CreateAchievementUseCase( + achievementRepository, + logger, + new CreateAchievementOutputAdapter() + ); }, - inject: [ACHIEVEMENT_REPOSITORY_TOKEN], + inject: [ACHIEVEMENT_REPOSITORY_TOKEN, 'Logger'], }, { provide: EnsureInitialData, - useFactory: (signupUseCase: SignupWithEmailUseCase, createAchievementUseCase: CreateAchievementUseCase, logger: Logger) => { + useFactory: ( + signupUseCase: SignupWithEmailUseCase, + createAchievementUseCase: CreateAchievementUseCase, + logger: Logger + ) => { return new EnsureInitialData(signupUseCase, createAchievementUseCase, logger); }, inject: [SIGNUP_USE_CASE_TOKEN, CREATE_ACHIEVEMENT_USE_CASE_TOKEN, 'Logger'], diff --git a/apps/api/src/domain/race/RaceProviders.ts b/apps/api/src/domain/race/RaceProviders.ts index b8ed916e0..1b35f8e9a 100644 --- a/apps/api/src/domain/race/RaceProviders.ts +++ b/apps/api/src/domain/race/RaceProviders.ts @@ -13,6 +13,8 @@ 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 { getPointsSystems } from '@adapters/bootstrap/PointsSystems'; @@ -52,6 +54,29 @@ 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'; @@ -92,6 +117,183 @@ export const RACE_PROTESTS_PRESENTER_TOKEN = 'RaceProtestsPresenter'; export const RACE_PENALTIES_PRESENTER_TOKEN = 'RacePenaltiesPresenter'; export const COMMAND_RESULT_PRESENTER_TOKEN = 'CommandResultPresenter'; +// Adapter classes to bridge presenters with UseCaseOutputPort interface +class GetAllRacesOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: GetAllRacesPresenter) {} + + present(result: GetAllRacesResult): void { + this.presenter.present(result); + } +} + +class GetTotalRacesOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: GetTotalRacesPresenter) {} + + present(result: GetTotalRacesResult): void { + // Wrap the result in a Result.ok() to match presenter expectations + const resultWrapper = Result.ok(result); + this.presenter.present(resultWrapper); + } +} + +class ImportRaceResultsApiOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: ImportRaceResultsApiPresenter) {} + + present(result: ImportRaceResultsApiResult): void { + const resultWrapper = Result.ok(result); + this.presenter.present(resultWrapper); + } +} + +class RaceDetailOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: RaceDetailPresenter) {} + + present(result: GetRaceDetailResult): void { + this.presenter.present(result); + } +} + +class RacesPageDataOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: RacesPageDataPresenter) {} + + present(result: GetRacesPageDataResult): void { + const resultWrapper = Result.ok(result); + this.presenter.present(resultWrapper); + } +} + +class AllRacesPageDataOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: AllRacesPageDataPresenter) {} + + present(result: GetAllRacesPageDataResult): void { + const resultWrapper = Result.ok(result); + this.presenter.present(resultWrapper); + } +} + +class RaceResultsDetailOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: RaceResultsDetailPresenter) {} + + present(result: GetRaceResultsDetailResult): void { + this.presenter.present(result); + } +} + +class RaceWithSOFOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: RaceWithSOFPresenter) {} + + present(result: GetRaceWithSOFResult): void { + const resultWrapper = Result.ok(result); + this.presenter.present(resultWrapper); + } +} + +class RaceProtestsOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: RaceProtestsPresenter) {} + + present(result: GetRaceProtestsResult): void { + const resultWrapper = Result.ok(result); + this.presenter.present(resultWrapper); + } +} + +class RacePenaltiesOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: RacePenaltiesPresenter) {} + + present(result: GetRacePenaltiesResult): void { + const resultWrapper = Result.ok(result); + this.presenter.present(resultWrapper); + } +} + +class RegisterForRaceOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Race registered successfully'); + } +} + +class WithdrawFromRaceOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Race withdrawal successful'); + } +} + +class CancelRaceOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Race cancelled successfully'); + } +} + +class CompleteRaceOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Race completed successfully'); + } +} + +class ReopenRaceOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Race reopened successfully'); + } +} + +class ImportRaceResultsOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Race results imported successfully'); + } +} + +class FileProtestOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Protest filed successfully'); + } +} + +class QuickPenaltyOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Penalty applied successfully'); + } +} + +class ApplyPenaltyOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Penalty applied successfully'); + } +} + +class RequestProtestDefenseOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Defense request sent successfully'); + } +} + +class ReviewProtestOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: CommandResultPresenter) {} + + present(): void { + this.presenter.presentSuccess('Protest reviewed successfully'); + } +} + export const RaceProviders: Provider[] = [ RaceService, { @@ -168,7 +370,7 @@ export const RaceProviders: Provider[] = [ }, { provide: RACE_DETAIL_PRESENTER_TOKEN, - useFactory: (driverRatingProvider: DriverRatingProvider, imageService: any) => + useFactory: (driverRatingProvider: DriverRatingProvider, imageService: InMemoryImageServiceAdapter) => new RaceDetailPresenter(driverRatingProvider, imageService, { raceId: '', driverId: '' }), inject: [DRIVER_RATING_PROVIDER_TOKEN, IMAGE_SERVICE_TOKEN], }, @@ -182,7 +384,7 @@ export const RaceProviders: Provider[] = [ }, { provide: RACE_RESULTS_DETAIL_PRESENTER_TOKEN, - useFactory: (imageService: any) => new RaceResultsDetailPresenter(imageService), + useFactory: (imageService: InMemoryImageServiceAdapter) => new RaceResultsDetailPresenter(imageService), inject: [IMAGE_SERVICE_TOKEN], }, { @@ -201,34 +403,31 @@ export const RaceProviders: Provider[] = [ provide: COMMAND_RESULT_PRESENTER_TOKEN, useClass: CommandResultPresenter, }, - // Use cases - using simplified approach since presenters need to be adapted + // Use cases { provide: GetAllRacesUseCase, useFactory: ( raceRepo: IRaceRepository, leagueRepo: ILeagueRepository, logger: Logger, + presenter: GetAllRacesPresenter, ) => { const useCase = new GetAllRacesUseCase(raceRepo, leagueRepo, logger); - // Create a simple wrapper that calls the presenter - const wrapper = { - present: (data: any) => { - const presenter = new GetAllRacesPresenter(); - presenter.present(data); - } - }; - useCase.setOutput(wrapper as any); + useCase.setOutput(new GetAllRacesOutputAdapter(presenter)); return useCase; }, - inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, GET_ALL_RACES_PRESENTER_TOKEN], }, { provide: GetTotalRacesUseCase, - useFactory: (raceRepo: IRaceRepository, logger: Logger) => { - const presenter = new GetTotalRacesPresenter(); - return new GetTotalRacesUseCase(raceRepo, logger, presenter as any); + useFactory: ( + raceRepo: IRaceRepository, + logger: Logger, + presenter: GetTotalRacesPresenter, + ) => { + return new GetTotalRacesUseCase(raceRepo, logger, new GetTotalRacesOutputAdapter(presenter)); }, - inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, GET_TOTAL_RACES_PRESENTER_TOKEN], }, { provide: ImportRaceResultsApiUseCase, @@ -239,9 +438,17 @@ export const RaceProviders: Provider[] = [ driverRepo: IDriverRepository, standingRepo: IStandingRepository, logger: Logger, + presenter: ImportRaceResultsApiPresenter, ) => { - const presenter = new ImportRaceResultsApiPresenter(); - return new ImportRaceResultsApiUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, standingRepo, logger, presenter as any); + return new ImportRaceResultsApiUseCase( + raceRepo, + leagueRepo, + resultRepo, + driverRepo, + standingRepo, + logger, + new ImportRaceResultsApiOutputAdapter(presenter) + ); }, inject: [ RACE_REPOSITORY_TOKEN, @@ -250,6 +457,7 @@ export const RaceProviders: Provider[] = [ DRIVER_REPOSITORY_TOKEN, STANDING_REPOSITORY_TOKEN, LOGGER_TOKEN, + IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN, ], }, { @@ -261,6 +469,7 @@ export const RaceProviders: Provider[] = [ raceRegRepo: IRaceRegistrationRepository, resultRepo: IResultRepository, leagueMembershipRepo: ILeagueMembershipRepository, + presenter: RaceDetailPresenter, ) => { const useCase = new GetRaceDetailUseCase( raceRepo, @@ -270,13 +479,7 @@ export const RaceProviders: Provider[] = [ resultRepo, leagueMembershipRepo, ); - const wrapper = { - present: (data: any) => { - const presenter = new RaceDetailPresenter({} as any, {} as any, {} as any); - presenter.present(data); - } - }; - useCase.setOutput(wrapper as any); + useCase.setOutput(new RaceDetailOutputAdapter(presenter)); return useCase; }, inject: [ @@ -286,6 +489,7 @@ export const RaceProviders: Provider[] = [ RACE_REGISTRATION_REPOSITORY_TOKEN, RESULT_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, + RACE_DETAIL_PRESENTER_TOKEN, ], }, { @@ -294,11 +498,16 @@ export const RaceProviders: Provider[] = [ raceRepo: IRaceRepository, leagueRepo: ILeagueRepository, logger: Logger, + presenter: RacesPageDataPresenter, ) => { - const presenter = new RacesPageDataPresenter(); - return new GetRacesPageDataUseCase(raceRepo, leagueRepo, logger, presenter as any); + return new GetRacesPageDataUseCase( + raceRepo, + leagueRepo, + logger, + new RacesPageDataOutputAdapter(presenter) + ); }, - inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, RACES_PAGE_DATA_PRESENTER_TOKEN], }, { provide: GetAllRacesPageDataUseCase, @@ -306,11 +515,16 @@ export const RaceProviders: Provider[] = [ raceRepo: IRaceRepository, leagueRepo: ILeagueRepository, logger: Logger, + presenter: AllRacesPageDataPresenter, ) => { - const presenter = new AllRacesPageDataPresenter(); - return new GetAllRacesPageDataUseCase(raceRepo, leagueRepo, logger, presenter as any); + return new GetAllRacesPageDataUseCase( + raceRepo, + leagueRepo, + logger, + new AllRacesPageDataOutputAdapter(presenter) + ); }, - inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [RACE_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LOGGER_TOKEN, ALL_RACES_PAGE_DATA_PRESENTER_TOKEN], }, { provide: GetRaceResultsDetailUseCase, @@ -320,9 +534,16 @@ export const RaceProviders: Provider[] = [ resultRepo: IResultRepository, driverRepo: IDriverRepository, penaltyRepo: IPenaltyRepository, + presenter: RaceResultsDetailPresenter, ) => { - const presenter = new RaceResultsDetailPresenter({} as any); - return new GetRaceResultsDetailUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, penaltyRepo, presenter as any); + return new GetRaceResultsDetailUseCase( + raceRepo, + leagueRepo, + resultRepo, + driverRepo, + penaltyRepo, + new RaceResultsDetailOutputAdapter(presenter) + ); }, inject: [ RACE_REPOSITORY_TOKEN, @@ -330,6 +551,7 @@ export const RaceProviders: Provider[] = [ RESULT_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, PENALTY_REPOSITORY_TOKEN, + RACE_RESULTS_DETAIL_PRESENTER_TOKEN, ], }, { @@ -339,32 +561,56 @@ export const RaceProviders: Provider[] = [ raceRegRepo: IRaceRegistrationRepository, resultRepo: IResultRepository, driverRatingProvider: DriverRatingProvider, + presenter: RaceWithSOFPresenter, ) => { - const presenter = new RaceWithSOFPresenter(); - return new GetRaceWithSOFUseCase(raceRepo, raceRegRepo, resultRepo, driverRatingProvider as any, presenter as any); + return new GetRaceWithSOFUseCase( + raceRepo, + raceRegRepo, + resultRepo, + async (input: { driverId: string }) => { + const rating = driverRatingProvider.getRating(input.driverId); + return { rating }; + }, + new RaceWithSOFOutputAdapter(presenter) + ); }, inject: [ RACE_REPOSITORY_TOKEN, RACE_REGISTRATION_REPOSITORY_TOKEN, RESULT_REPOSITORY_TOKEN, DRIVER_RATING_PROVIDER_TOKEN, + RACE_WITH_SOF_PRESENTER_TOKEN, ], }, { provide: GetRaceProtestsUseCase, - useFactory: (protestRepo: IProtestRepository, driverRepo: IDriverRepository) => { - const presenter = new RaceProtestsPresenter(); - return new GetRaceProtestsUseCase(protestRepo, driverRepo, presenter as any); + useFactory: ( + protestRepo: IProtestRepository, + driverRepo: IDriverRepository, + presenter: RaceProtestsPresenter, + ) => { + return new GetRaceProtestsUseCase( + protestRepo, + driverRepo, + new RaceProtestsOutputAdapter(presenter) + ); }, - inject: [PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN], + inject: [PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, RACE_PROTESTS_PRESENTER_TOKEN], }, { provide: GetRacePenaltiesUseCase, - useFactory: (penaltyRepo: IPenaltyRepository, driverRepo: IDriverRepository) => { - const presenter = new RacePenaltiesPresenter(); - return new GetRacePenaltiesUseCase(penaltyRepo, driverRepo, presenter as any); + useFactory: ( + penaltyRepo: IPenaltyRepository, + driverRepo: IDriverRepository, + presenter: RacePenaltiesPresenter, + ) => { + return new GetRacePenaltiesUseCase( + penaltyRepo, + driverRepo, + new RacePenaltiesOutputAdapter(presenter) + ); }, - inject: [PENALTY_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN], + inject: [PENALTY_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, RACE_PENALTIES_PRESENTER_TOKEN], }, { provide: RegisterForRaceUseCase, @@ -372,11 +618,16 @@ export const RaceProviders: Provider[] = [ raceRegRepo: IRaceRegistrationRepository, leagueMembershipRepo: ILeagueMembershipRepository, logger: Logger, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new RegisterForRaceUseCase(raceRegRepo, leagueMembershipRepo, logger, presenter as any); + return new RegisterForRaceUseCase( + raceRegRepo, + leagueMembershipRepo, + logger, + new RegisterForRaceOutputAdapter(presenter) + ); }, - inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [RACE_REGISTRATION_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, { provide: WithdrawFromRaceUseCase, @@ -384,19 +635,31 @@ export const RaceProviders: Provider[] = [ raceRepo: IRaceRepository, raceRegRepo: IRaceRegistrationRepository, logger: Logger, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new WithdrawFromRaceUseCase(raceRepo, raceRegRepo, logger, presenter as any); + return new WithdrawFromRaceUseCase( + raceRepo, + raceRegRepo, + logger, + new WithdrawFromRaceOutputAdapter(presenter) + ); }, - inject: [RACE_REPOSITORY_TOKEN, RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [RACE_REPOSITORY_TOKEN, RACE_REGISTRATION_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, { provide: CancelRaceUseCase, - useFactory: (raceRepo: IRaceRepository, logger: Logger) => { - const presenter = new CommandResultPresenter(); - return new CancelRaceUseCase(raceRepo, logger, presenter as any); + useFactory: ( + raceRepo: IRaceRepository, + logger: Logger, + presenter: CommandResultPresenter, + ) => { + return new CancelRaceUseCase( + raceRepo, + logger, + new CancelRaceOutputAdapter(presenter) + ); }, - inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, { provide: CompleteRaceUseCase, @@ -406,9 +669,19 @@ export const RaceProviders: Provider[] = [ resultRepo: IResultRepository, standingRepo: IStandingRepository, driverRatingProvider: DriverRatingProvider, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new CompleteRaceUseCase(raceRepo, raceRegRepo, resultRepo, standingRepo, driverRatingProvider as any, presenter as any); + return new CompleteRaceUseCase( + raceRepo, + raceRegRepo, + resultRepo, + standingRepo, + async (input: { driverId: string }) => { + const rating = driverRatingProvider.getRating(input.driverId); + return { rating, ratingChange: null }; + }, + new CompleteRaceOutputAdapter(presenter) + ); }, inject: [ RACE_REPOSITORY_TOKEN, @@ -416,15 +689,23 @@ export const RaceProviders: Provider[] = [ RESULT_REPOSITORY_TOKEN, STANDING_REPOSITORY_TOKEN, DRIVER_RATING_PROVIDER_TOKEN, + COMMAND_RESULT_PRESENTER_TOKEN, ], }, { provide: ReopenRaceUseCase, - useFactory: (raceRepo: IRaceRepository, logger: Logger) => { - const presenter = new CommandResultPresenter(); - return new ReopenRaceUseCase(raceRepo, logger, presenter as any); + useFactory: ( + raceRepo: IRaceRepository, + logger: Logger, + presenter: CommandResultPresenter, + ) => { + return new ReopenRaceUseCase( + raceRepo, + logger, + new ReopenRaceOutputAdapter(presenter) + ); }, - inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [RACE_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, { provide: ImportRaceResultsUseCase, @@ -435,9 +716,17 @@ export const RaceProviders: Provider[] = [ driverRepo: IDriverRepository, standingRepo: IStandingRepository, logger: Logger, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new ImportRaceResultsUseCase(raceRepo, leagueRepo, resultRepo, driverRepo, standingRepo, logger, presenter as any); + return new ImportRaceResultsUseCase( + raceRepo, + leagueRepo, + resultRepo, + driverRepo, + standingRepo, + logger, + new ImportRaceResultsOutputAdapter(presenter) + ); }, inject: [ RACE_REPOSITORY_TOKEN, @@ -446,6 +735,7 @@ export const RaceProviders: Provider[] = [ DRIVER_REPOSITORY_TOKEN, STANDING_REPOSITORY_TOKEN, LOGGER_TOKEN, + COMMAND_RESULT_PRESENTER_TOKEN, ], }, { @@ -454,11 +744,16 @@ export const RaceProviders: Provider[] = [ protestRepo: IProtestRepository, raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new FileProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, presenter as any); + return new FileProtestUseCase( + protestRepo, + raceRepo, + leagueMembershipRepo, + new FileProtestOutputAdapter(presenter) + ); }, - inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN], + inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, { provide: QuickPenaltyUseCase, @@ -467,11 +762,17 @@ export const RaceProviders: Provider[] = [ raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository, logger: Logger, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new QuickPenaltyUseCase(penaltyRepo, raceRepo, leagueMembershipRepo, logger, presenter as any); + return new QuickPenaltyUseCase( + penaltyRepo, + raceRepo, + leagueMembershipRepo, + logger, + new QuickPenaltyOutputAdapter(presenter) + ); }, - inject: [PENALTY_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [PENALTY_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, { provide: ApplyPenaltyUseCase, @@ -481,11 +782,18 @@ export const RaceProviders: Provider[] = [ raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository, logger: Logger, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new ApplyPenaltyUseCase(penaltyRepo, protestRepo, raceRepo, leagueMembershipRepo, logger, presenter as any); + return new ApplyPenaltyUseCase( + penaltyRepo, + protestRepo, + raceRepo, + leagueMembershipRepo, + logger, + new ApplyPenaltyOutputAdapter(presenter) + ); }, - inject: [PENALTY_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [PENALTY_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, { provide: RequestProtestDefenseUseCase, @@ -494,11 +802,17 @@ export const RaceProviders: Provider[] = [ raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository, logger: Logger, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new RequestProtestDefenseUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger, presenter as any); + return new RequestProtestDefenseUseCase( + protestRepo, + raceRepo, + leagueMembershipRepo, + logger, + new RequestProtestDefenseOutputAdapter(presenter) + ); }, - inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, { provide: ReviewProtestUseCase, @@ -507,10 +821,16 @@ export const RaceProviders: Provider[] = [ raceRepo: IRaceRepository, leagueMembershipRepo: ILeagueMembershipRepository, logger: Logger, + presenter: CommandResultPresenter, ) => { - const presenter = new CommandResultPresenter(); - return new ReviewProtestUseCase(protestRepo, raceRepo, leagueMembershipRepo, logger, presenter as any); + return new ReviewProtestUseCase( + protestRepo, + raceRepo, + leagueMembershipRepo, + logger, + new ReviewProtestOutputAdapter(presenter) + ); }, - inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN], + inject: [PROTEST_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN, COMMAND_RESULT_PRESENTER_TOKEN], }, ]; \ No newline at end of file diff --git a/apps/api/src/domain/race/RaceService.ts b/apps/api/src/domain/race/RaceService.ts index a98f2cefd..6be012e56 100644 --- a/apps/api/src/domain/race/RaceService.ts +++ b/apps/api/src/domain/race/RaceService.ts @@ -2,76 +2,70 @@ import { Inject, Injectable } from '@nestjs/common'; // DTOs import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO'; +import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO'; +import { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO'; import { RegisterForRaceParamsDTO } from './dtos/RegisterForRaceParamsDTO'; import { WithdrawFromRaceParamsDTO } from './dtos/WithdrawFromRaceParamsDTO'; -import { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO'; -import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO'; // Core imports import type { Logger } from '@core/shared/application/Logger'; -import { DriverRatingProvider } from '@core/racing/application/ports/DriverRatingProvider'; -import type { IImageServicePort } from '@core/racing/application/ports/IImageServicePort'; -import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository'; // Use cases -import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase'; -import { GetTotalRacesUseCase } from '@core/racing/application/use-cases/GetTotalRacesUseCase'; -import { ImportRaceResultsApiUseCase } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase'; -import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase'; -import { GetRacesPageDataUseCase } from '@core/racing/application/use-cases/GetRacesPageDataUseCase'; -import { GetAllRacesPageDataUseCase } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase'; -import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase'; -import { GetRaceWithSOFUseCase } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase'; -import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase'; -import { GetRacePenaltiesUseCase } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase'; -import { RegisterForRaceUseCase } from '@core/racing/application/use-cases/RegisterForRaceUseCase'; -import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase'; import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase'; import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase'; import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase'; +import { GetAllRacesPageDataUseCase } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase'; +import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase'; +import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase'; +import { GetRacePenaltiesUseCase } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase'; +import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase'; +import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase'; +import { GetRacesPageDataUseCase } from '@core/racing/application/use-cases/GetRacesPageDataUseCase'; +import { GetRaceWithSOFUseCase } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase'; +import { GetTotalRacesUseCase } from '@core/racing/application/use-cases/GetTotalRacesUseCase'; +import { ImportRaceResultsApiUseCase } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase'; import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase'; -import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase'; +import { RegisterForRaceUseCase } from '@core/racing/application/use-cases/RegisterForRaceUseCase'; +import { ReopenRaceUseCase } from '@core/racing/application/use-cases/ReopenRaceUseCase'; import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase'; import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase'; -import { ReopenRaceUseCase } from '@core/racing/application/use-cases/ReopenRaceUseCase'; +import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase'; // Presenters +import { AllRacesPageDataPresenter } from './presenters/AllRacesPageDataPresenter'; +import { CommandResultPresenter } from './presenters/CommandResultPresenter'; import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter'; import { GetTotalRacesPresenter } from './presenters/GetTotalRacesPresenter'; import { ImportRaceResultsApiPresenter } from './presenters/ImportRaceResultsApiPresenter'; import { RaceDetailPresenter } from './presenters/RaceDetailPresenter'; -import { RacesPageDataPresenter } from './presenters/RacesPageDataPresenter'; -import { AllRacesPageDataPresenter } from './presenters/AllRacesPageDataPresenter'; -import { RaceResultsDetailPresenter } from './presenters/RaceResultsDetailPresenter'; -import { RaceWithSOFPresenter } from './presenters/RaceWithSOFPresenter'; -import { RaceProtestsPresenter } from './presenters/RaceProtestsPresenter'; import { RacePenaltiesPresenter } from './presenters/RacePenaltiesPresenter'; -import { CommandResultPresenter } from './presenters/CommandResultPresenter'; +import { RaceProtestsPresenter } from './presenters/RaceProtestsPresenter'; +import { RaceResultsDetailPresenter } from './presenters/RaceResultsDetailPresenter'; +import { RacesPageDataPresenter } from './presenters/RacesPageDataPresenter'; +import { RaceWithSOFPresenter } from './presenters/RaceWithSOFPresenter'; // Command DTOs +import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO'; import { FileProtestCommandDTO } from './dtos/FileProtestCommandDTO'; import { QuickPenaltyCommandDTO } from './dtos/QuickPenaltyCommandDTO'; -import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO'; import { RequestProtestDefenseCommandDTO } from './dtos/RequestProtestDefenseCommandDTO'; import { ReviewProtestCommandDTO } from './dtos/ReviewProtestCommandDTO'; // Tokens -import { - DRIVER_RATING_PROVIDER_TOKEN, - IMAGE_SERVICE_TOKEN, - LEAGUE_REPOSITORY_TOKEN, - LOGGER_TOKEN, +import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase'; +import { + ALL_RACES_PAGE_DATA_PRESENTER_TOKEN, + COMMAND_RESULT_PRESENTER_TOKEN, GET_ALL_RACES_PRESENTER_TOKEN, GET_TOTAL_RACES_PRESENTER_TOKEN, IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN, + LOGGER_TOKEN, RACE_DETAIL_PRESENTER_TOKEN, - RACES_PAGE_DATA_PRESENTER_TOKEN, - ALL_RACES_PAGE_DATA_PRESENTER_TOKEN, + RACE_PENALTIES_PRESENTER_TOKEN, + RACE_PROTESTS_PRESENTER_TOKEN, RACE_RESULTS_DETAIL_PRESENTER_TOKEN, RACE_WITH_SOF_PRESENTER_TOKEN, - RACE_PROTESTS_PRESENTER_TOKEN, - RACE_PENALTIES_PRESENTER_TOKEN, - COMMAND_RESULT_PRESENTER_TOKEN + RACES_PAGE_DATA_PRESENTER_TOKEN } from './RaceProviders'; @Injectable() @@ -97,10 +91,7 @@ export class RaceService { private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase, private readonly reviewProtestUseCase: ReviewProtestUseCase, private readonly reopenRaceUseCase: ReopenRaceUseCase, - @Inject(LEAGUE_REPOSITORY_TOKEN) private readonly leagueRepository: ILeagueRepository, @Inject(LOGGER_TOKEN) private readonly logger: Logger, - @Inject(DRIVER_RATING_PROVIDER_TOKEN) private readonly driverRatingProvider: DriverRatingProvider, - @Inject(IMAGE_SERVICE_TOKEN) private readonly imageService: IImageServicePort, // Injected presenters @Inject(GET_ALL_RACES_PRESENTER_TOKEN) private readonly getAllRacesPresenter: GetAllRacesPresenter, @Inject(GET_TOTAL_RACES_PRESENTER_TOKEN) private readonly getTotalRacesPresenter: GetTotalRacesPresenter, diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 6c9e8e2ef..d6eac5e33 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -26,10 +26,12 @@ async function bootstrap() { .addTag('analytics', 'Analytics and reporting endpoints') .build(); - const document = SwaggerModule.createDocument(app, config); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const document = SwaggerModule.createDocument(app as any, config); // Serve Swagger UI at /api/docs - SwaggerModule.setup('api/docs', app, document); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + SwaggerModule.setup('api/docs', app as any, document); // Export OpenAPI spec as JSON file when GENERATE_OPENAPI env var is set if (process.env.GENERATE_OPENAPI) {