import { BadRequestException, ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common'; import { ApproveJoinRequestInputDTO } from './dtos/ApproveJoinRequestInputDTO'; import { ApproveLeagueJoinRequestDTO } from './dtos/ApproveLeagueJoinRequestDTO'; import { CreateLeagueInputDTO } from './dtos/CreateLeagueInputDTO'; import { GetLeagueAdminConfigQueryDTO } from './dtos/GetLeagueAdminConfigQueryDTO'; import { GetLeagueAdminPermissionsInputDTO } from './dtos/GetLeagueAdminPermissionsInputDTO'; import { GetLeagueOwnerSummaryQueryDTO } from './dtos/GetLeagueOwnerSummaryQueryDTO'; import { GetLeagueProtestsQueryDTO } from './dtos/GetLeagueProtestsQueryDTO'; import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO'; import { GetLeagueScheduleQueryDTO } from './dtos/GetLeagueScheduleQueryDTO'; import { GetLeagueSeasonsQueryDTO } from './dtos/GetLeagueSeasonsQueryDTO'; import { GetLeagueWalletOutputDTO } from './dtos/GetLeagueWalletOutputDTO'; import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO'; import { LeagueAdminDTO } from './dtos/LeagueAdminDTO'; import { LeagueAdminPermissionsDTO } from './dtos/LeagueAdminPermissionsDTO'; import { LeagueAdminProtestsDTO } from './dtos/LeagueAdminProtestsDTO'; import { LeagueConfigFormModelDTO } from './dtos/LeagueConfigFormModelDTO'; import { LeagueJoinRequestWithDriverDTO } from './dtos/LeagueJoinRequestWithDriverDTO'; import { LeagueMembershipsDTO } from './dtos/LeagueMembershipsDTO'; import { LeagueOwnerSummaryDTO } from './dtos/LeagueOwnerSummaryDTO'; import { LeagueRosterJoinRequestDTO } from './dtos/LeagueRosterJoinRequestDTO'; import { LeagueRosterMemberDTO } from './dtos/LeagueRosterMemberDTO'; import { LeagueScheduleDTO } from './dtos/LeagueScheduleDTO'; import { CreateLeagueScheduleRaceInputDTO, CreateLeagueScheduleRaceOutputDTO, LeagueScheduleRaceMutationSuccessDTO, UpdateLeagueScheduleRaceInputDTO, } from './dtos/LeagueScheduleRaceAdminDTO'; import { LeagueSeasonSchedulePublishInputDTO, LeagueSeasonSchedulePublishOutputDTO, } from './dtos/LeagueSeasonSchedulePublishDTO'; import { LeagueSeasonSummaryDTO } from './dtos/LeagueSeasonSummaryDTO'; import { LeagueStandingsDTO } from './dtos/LeagueStandingsDTO'; import { LeagueStatsDTO } from './dtos/LeagueStatsDTO'; import { RejectJoinRequestInputDTO } from './dtos/RejectJoinRequestInputDTO'; import { RejectJoinRequestOutputDTO } from './dtos/RejectJoinRequestOutputDTO'; import { RemoveLeagueMemberInputDTO } from './dtos/RemoveLeagueMemberInputDTO'; import { RemoveLeagueMemberOutputDTO } from './dtos/RemoveLeagueMemberOutputDTO'; import { TransferLeagueOwnershipInputDTO } from './dtos/TransferLeagueOwnershipInputDTO'; import { TransferLeagueOwnershipOutputDTO } from './dtos/TransferLeagueOwnershipOutputDTO'; import { UpdateLeagueMemberRoleInputDTO } from './dtos/UpdateLeagueMemberRoleInputDTO'; import { UpdateLeagueMemberRoleOutputDTO } from './dtos/UpdateLeagueMemberRoleOutputDTO'; import { WithdrawFromLeagueWalletInputDTO } from './dtos/WithdrawFromLeagueWalletInputDTO'; import { WithdrawFromLeagueWalletOutputDTO } from './dtos/WithdrawFromLeagueWalletOutputDTO'; import { getActorFromRequestContext } from '../auth/getActorFromRequestContext'; import { requireLeagueAdminOrOwner } from './LeagueAuthorization'; // Core imports for view models import type { AllLeaguesWithCapacityAndScoringDTO as AllLeaguesWithCapacityAndScoringViewModel } from './dtos/AllLeaguesWithCapacityAndScoringDTO'; import type { AllLeaguesWithCapacityDTO as AllLeaguesWithCapacityViewModel } from './dtos/AllLeaguesWithCapacityDTO'; import type { CreateLeagueViewModel } from './dtos/CreateLeagueDTO'; import type { JoinLeagueOutputDTO } from './dtos/JoinLeagueOutputDTO'; import { TotalLeaguesDTO } from './dtos/TotalLeaguesDTO'; import type { LeagueScoringConfigViewModel } from './presenters/LeagueScoringConfigPresenter'; import type { LeagueScoringPresetsViewModel } from './presenters/LeagueScoringPresetsPresenter'; // Core imports import type { Logger } from '@core/shared/domain/Logger'; // Use cases import { ApproveLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase'; import { CreateLeagueWithSeasonAndScoringUseCase } from '@core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase'; import { GetAllLeaguesWithCapacityAndScoringUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase'; import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase'; import { GetLeagueAdminPermissionsUseCase } from '@core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase'; import { GetLeagueFullConfigUseCase } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase'; import { GetLeagueJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase'; import { GetLeagueMembershipsUseCase } from '@core/racing/application/use-cases/GetLeagueMembershipsUseCase'; import { GetLeagueOwnerSummaryUseCase } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase'; import { GetLeagueProtestsUseCase } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase'; import { GetLeagueRosterJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueRosterJoinRequestsUseCase'; import { GetLeagueRosterMembersUseCase } from '@core/racing/application/use-cases/GetLeagueRosterMembersUseCase'; import type { GetLeagueScheduleInput } from '@core/racing/application/use-cases/GetLeagueScheduleUseCase'; import { GetLeagueScheduleUseCase } from '@core/racing/application/use-cases/GetLeagueScheduleUseCase'; import { GetLeagueScoringConfigUseCase } from '@core/racing/application/use-cases/GetLeagueScoringConfigUseCase'; import { GetLeagueSeasonsUseCase } from '@core/racing/application/use-cases/GetLeagueSeasonsUseCase'; import { GetLeagueStandingsUseCase } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase'; import { GetLeagueStatsUseCase } from '@core/racing/application/use-cases/GetLeagueStatsUseCase'; import { GetLeagueWalletUseCase } from '@core/racing/application/use-cases/GetLeagueWalletUseCase'; import { GetSeasonSponsorshipsUseCase } from '@core/racing/application/use-cases/GetSeasonSponsorshipsUseCase'; import { GetTotalLeaguesUseCase } from '@core/racing/application/use-cases/GetTotalLeaguesUseCase'; import { JoinLeagueUseCase } from '@core/racing/application/use-cases/JoinLeagueUseCase'; import { ListLeagueScoringPresetsUseCase } from '@core/racing/application/use-cases/ListLeagueScoringPresetsUseCase'; import { RejectLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/RejectLeagueJoinRequestUseCase'; import { RemoveLeagueMemberUseCase } from '@core/racing/application/use-cases/RemoveLeagueMemberUseCase'; import { TransferLeagueOwnershipUseCase } from '@core/racing/application/use-cases/TransferLeagueOwnershipUseCase'; import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase'; import { WithdrawFromLeagueWalletUseCase } from '@core/racing/application/use-cases/WithdrawFromLeagueWalletUseCase'; import { CreateLeagueSeasonScheduleRaceUseCase } from '@core/racing/application/use-cases/CreateLeagueSeasonScheduleRaceUseCase'; import { DeleteLeagueSeasonScheduleRaceUseCase } from '@core/racing/application/use-cases/DeleteLeagueSeasonScheduleRaceUseCase'; import { PublishLeagueSeasonScheduleUseCase } from '@core/racing/application/use-cases/PublishLeagueSeasonScheduleUseCase'; import { UnpublishLeagueSeasonScheduleUseCase } from '@core/racing/application/use-cases/UnpublishLeagueSeasonScheduleUseCase'; import { UpdateLeagueSeasonScheduleRaceUseCase } from '@core/racing/application/use-cases/UpdateLeagueSeasonScheduleRaceUseCase'; // API Presenters import { AllLeaguesWithCapacityAndScoringPresenter } from './presenters/AllLeaguesWithCapacityAndScoringPresenter'; import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter'; import { ApproveLeagueJoinRequestPresenter } from './presenters/ApproveLeagueJoinRequestPresenter'; import { CreateLeaguePresenter } from './presenters/CreateLeaguePresenter'; import { GetLeagueAdminPermissionsPresenter } from './presenters/GetLeagueAdminPermissionsPresenter'; import { GetLeagueMembershipsPresenter } from './presenters/GetLeagueMembershipsPresenter'; import { GetLeagueOwnerSummaryPresenter } from './presenters/GetLeagueOwnerSummaryPresenter'; import { GetLeagueProtestsPresenter } from './presenters/GetLeagueProtestsPresenter'; import { GetLeagueSeasonsPresenter } from './presenters/GetLeagueSeasonsPresenter'; import { GetLeagueWalletPresenter } from './presenters/GetLeagueWalletPresenter'; import { GetSeasonSponsorshipsPresenter } from './presenters/GetSeasonSponsorshipsPresenter'; import { JoinLeaguePresenter } from './presenters/JoinLeaguePresenter'; import { LeagueConfigPresenter } from './presenters/LeagueConfigPresenter'; import { LeagueJoinRequestsPresenter } from './presenters/LeagueJoinRequestsPresenter'; import { GetLeagueRosterJoinRequestsPresenter, GetLeagueRosterMembersPresenter, } from './presenters/LeagueRosterAdminReadPresenters'; import { LeagueSchedulePresenter } from './presenters/LeagueSchedulePresenter'; import { LeagueScoringConfigPresenter } from './presenters/LeagueScoringConfigPresenter'; import { LeagueScoringPresetsPresenter } from './presenters/LeagueScoringPresetsPresenter'; import { CreateLeagueSeasonScheduleRacePresenter, DeleteLeagueSeasonScheduleRacePresenter, PublishLeagueSeasonSchedulePresenter, UnpublishLeagueSeasonSchedulePresenter, UpdateLeagueSeasonScheduleRacePresenter, } from './presenters/LeagueSeasonScheduleMutationPresenters'; import { LeagueStandingsPresenter } from './presenters/LeagueStandingsPresenter'; import { LeagueStatsPresenter } from './presenters/LeagueStatsPresenter'; import { RejectLeagueJoinRequestPresenter } from './presenters/RejectLeagueJoinRequestPresenter'; import { RemoveLeagueMemberPresenter } from './presenters/RemoveLeagueMemberPresenter'; import { TotalLeaguesPresenter } from './presenters/TotalLeaguesPresenter'; import { TransferLeagueOwnershipPresenter } from './presenters/TransferLeagueOwnershipPresenter'; import { UpdateLeagueMemberRolePresenter } from './presenters/UpdateLeagueMemberRolePresenter'; import { WithdrawFromLeagueWalletPresenter } from './presenters/WithdrawFromLeagueWalletPresenter'; // Tokens import { APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE, CREATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE, CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE, DELETE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE, GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_USE_CASE, GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE, GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE, GET_LEAGUE_FULL_CONFIG_USE_CASE, GET_LEAGUE_JOIN_REQUESTS_USE_CASE, GET_LEAGUE_MEMBERSHIPS_USE_CASE, GET_LEAGUE_OWNER_SUMMARY_USE_CASE, GET_LEAGUE_PROTESTS_USE_CASE, GET_LEAGUE_ROSTER_JOIN_REQUESTS_USE_CASE, GET_LEAGUE_ROSTER_MEMBERS_USE_CASE, GET_LEAGUE_SCHEDULE_USE_CASE, GET_LEAGUE_SCORING_CONFIG_USE_CASE, GET_LEAGUE_SEASONS_USE_CASE, GET_LEAGUE_STANDINGS_USE_CASE, GET_LEAGUE_STATS_USE_CASE, GET_LEAGUE_WALLET_USE_CASE, GET_SEASON_SPONSORSHIPS_USE_CASE, GET_TOTAL_LEAGUES_USE_CASE, JOIN_LEAGUE_USE_CASE, LIST_LEAGUE_SCORING_PRESETS_USE_CASE, LOGGER_TOKEN, PUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE, REJECT_LEAGUE_JOIN_REQUEST_USE_CASE, REMOVE_LEAGUE_MEMBER_USE_CASE, TRANSFER_LEAGUE_OWNERSHIP_USE_CASE, UNPUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE, UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE, UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE, WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE, } from './LeagueTokens'; @Injectable() export class LeagueService { constructor( @Inject(GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE) private readonly getAllLeaguesWithCapacityUseCase: GetAllLeaguesWithCapacityUseCase, @Inject(GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_USE_CASE) private readonly getAllLeaguesWithCapacityAndScoringUseCase: GetAllLeaguesWithCapacityAndScoringUseCase, @Inject(GET_LEAGUE_STANDINGS_USE_CASE) private readonly getLeagueStandingsUseCase: GetLeagueStandingsUseCase, @Inject(GET_LEAGUE_STATS_USE_CASE) private readonly getLeagueStatsUseCase: GetLeagueStatsUseCase, @Inject(GET_LEAGUE_FULL_CONFIG_USE_CASE) private readonly getLeagueFullConfigUseCase: GetLeagueFullConfigUseCase, @Inject(GET_LEAGUE_SCORING_CONFIG_USE_CASE) private readonly getLeagueScoringConfigUseCase: GetLeagueScoringConfigUseCase, @Inject(LIST_LEAGUE_SCORING_PRESETS_USE_CASE) private readonly listLeagueScoringPresetsUseCase: ListLeagueScoringPresetsUseCase, @Inject(JOIN_LEAGUE_USE_CASE) private readonly joinLeagueUseCase: JoinLeagueUseCase, @Inject(TRANSFER_LEAGUE_OWNERSHIP_USE_CASE) private readonly transferLeagueOwnershipUseCase: TransferLeagueOwnershipUseCase, @Inject(CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE) private readonly createLeagueWithSeasonAndScoringUseCase: CreateLeagueWithSeasonAndScoringUseCase, @Inject(GET_TOTAL_LEAGUES_USE_CASE) private readonly getTotalLeaguesUseCase: GetTotalLeaguesUseCase, @Inject(GET_LEAGUE_JOIN_REQUESTS_USE_CASE) private readonly getLeagueJoinRequestsUseCase: GetLeagueJoinRequestsUseCase, @Inject(APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE) private readonly approveLeagueJoinRequestUseCase: ApproveLeagueJoinRequestUseCase, @Inject(REJECT_LEAGUE_JOIN_REQUEST_USE_CASE) private readonly rejectLeagueJoinRequestUseCase: RejectLeagueJoinRequestUseCase, @Inject(REMOVE_LEAGUE_MEMBER_USE_CASE) private readonly removeLeagueMemberUseCase: RemoveLeagueMemberUseCase, @Inject(UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE) private readonly updateLeagueMemberRoleUseCase: UpdateLeagueMemberRoleUseCase, @Inject(GET_LEAGUE_OWNER_SUMMARY_USE_CASE) private readonly getLeagueOwnerSummaryUseCase: GetLeagueOwnerSummaryUseCase, @Inject(GET_LEAGUE_PROTESTS_USE_CASE) private readonly getLeagueProtestsUseCase: GetLeagueProtestsUseCase, @Inject(GET_LEAGUE_SEASONS_USE_CASE) private readonly getLeagueSeasonsUseCase: GetLeagueSeasonsUseCase, @Inject(GET_LEAGUE_MEMBERSHIPS_USE_CASE) private readonly getLeagueMembershipsUseCase: GetLeagueMembershipsUseCase, @Inject(GET_LEAGUE_SCHEDULE_USE_CASE) private readonly getLeagueScheduleUseCase: GetLeagueScheduleUseCase, @Inject(GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE) private readonly getLeagueAdminPermissionsUseCase: GetLeagueAdminPermissionsUseCase, @Inject(GET_LEAGUE_WALLET_USE_CASE) private readonly getLeagueWalletUseCase: GetLeagueWalletUseCase, @Inject(WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE) private readonly withdrawFromLeagueWalletUseCase: WithdrawFromLeagueWalletUseCase, @Inject(GET_SEASON_SPONSORSHIPS_USE_CASE) private readonly getSeasonSponsorshipsUseCase: GetSeasonSponsorshipsUseCase, // Schedule mutations @Inject(CREATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE) private readonly createLeagueSeasonScheduleRaceUseCase: CreateLeagueSeasonScheduleRaceUseCase, @Inject(UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE) private readonly updateLeagueSeasonScheduleRaceUseCase: UpdateLeagueSeasonScheduleRaceUseCase, @Inject(DELETE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE) private readonly deleteLeagueSeasonScheduleRaceUseCase: DeleteLeagueSeasonScheduleRaceUseCase, @Inject(PUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE) private readonly publishLeagueSeasonScheduleUseCase: PublishLeagueSeasonScheduleUseCase, @Inject(UNPUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE) private readonly unpublishLeagueSeasonScheduleUseCase: UnpublishLeagueSeasonScheduleUseCase, @Inject(LOGGER_TOKEN) private readonly logger: Logger, // Injected presenters @Inject(AllLeaguesWithCapacityPresenter) private readonly allLeaguesWithCapacityPresenter: AllLeaguesWithCapacityPresenter, @Inject(AllLeaguesWithCapacityAndScoringPresenter) private readonly allLeaguesWithCapacityAndScoringPresenter: AllLeaguesWithCapacityAndScoringPresenter, @Inject(LeagueStandingsPresenter) private readonly leagueStandingsPresenter: LeagueStandingsPresenter, @Inject(GetLeagueProtestsPresenter) private readonly leagueProtestsPresenter: GetLeagueProtestsPresenter, @Inject(GetSeasonSponsorshipsPresenter) private readonly seasonSponsorshipsPresenter: GetSeasonSponsorshipsPresenter, @Inject(LeagueScoringPresetsPresenter) private readonly leagueScoringPresetsPresenter: LeagueScoringPresetsPresenter, @Inject(ApproveLeagueJoinRequestPresenter) private readonly approveLeagueJoinRequestPresenter: ApproveLeagueJoinRequestPresenter, @Inject(CreateLeaguePresenter) private readonly createLeaguePresenter: CreateLeaguePresenter, @Inject(GetLeagueAdminPermissionsPresenter) private readonly getLeagueAdminPermissionsPresenter: GetLeagueAdminPermissionsPresenter, @Inject(GetLeagueMembershipsPresenter) private readonly getLeagueMembershipsPresenter: GetLeagueMembershipsPresenter, @Inject(GetLeagueOwnerSummaryPresenter) private readonly getLeagueOwnerSummaryPresenter: GetLeagueOwnerSummaryPresenter, @Inject(GetLeagueSeasonsPresenter) private readonly getLeagueSeasonsPresenter: GetLeagueSeasonsPresenter, @Inject(JoinLeaguePresenter) private readonly joinLeaguePresenter: JoinLeaguePresenter, @Inject(LeagueSchedulePresenter) private readonly leagueSchedulePresenter: LeagueSchedulePresenter, @Inject(LeagueStatsPresenter) private readonly leagueStatsPresenter: LeagueStatsPresenter, @Inject(RejectLeagueJoinRequestPresenter) private readonly rejectLeagueJoinRequestPresenter: RejectLeagueJoinRequestPresenter, @Inject(RemoveLeagueMemberPresenter) private readonly removeLeagueMemberPresenter: RemoveLeagueMemberPresenter, @Inject(TotalLeaguesPresenter) private readonly totalLeaguesPresenter: TotalLeaguesPresenter, @Inject(TransferLeagueOwnershipPresenter) private readonly transferLeagueOwnershipPresenter: TransferLeagueOwnershipPresenter, @Inject(UpdateLeagueMemberRolePresenter) private readonly updateLeagueMemberRolePresenter: UpdateLeagueMemberRolePresenter, @Inject(LeagueConfigPresenter) private readonly leagueConfigPresenter: LeagueConfigPresenter, @Inject(LeagueScoringConfigPresenter) private readonly leagueScoringConfigPresenter: LeagueScoringConfigPresenter, @Inject(GetLeagueWalletPresenter) private readonly getLeagueWalletPresenter: GetLeagueWalletPresenter, @Inject(WithdrawFromLeagueWalletPresenter) private readonly withdrawFromLeagueWalletPresenter: WithdrawFromLeagueWalletPresenter, @Inject(LeagueJoinRequestsPresenter) private readonly leagueJoinRequestsPresenter: LeagueJoinRequestsPresenter, // Schedule mutation presenters @Inject(CreateLeagueSeasonScheduleRacePresenter) private readonly createLeagueSeasonScheduleRacePresenter: CreateLeagueSeasonScheduleRacePresenter, @Inject(UpdateLeagueSeasonScheduleRacePresenter) private readonly updateLeagueSeasonScheduleRacePresenter: UpdateLeagueSeasonScheduleRacePresenter, @Inject(DeleteLeagueSeasonScheduleRacePresenter) private readonly deleteLeagueSeasonScheduleRacePresenter: DeleteLeagueSeasonScheduleRacePresenter, @Inject(PublishLeagueSeasonSchedulePresenter) private readonly publishLeagueSeasonSchedulePresenter: PublishLeagueSeasonSchedulePresenter, @Inject(UnpublishLeagueSeasonSchedulePresenter) private readonly unpublishLeagueSeasonSchedulePresenter: UnpublishLeagueSeasonSchedulePresenter, // Roster admin read delegation @Inject(GET_LEAGUE_ROSTER_MEMBERS_USE_CASE) private readonly getLeagueRosterMembersUseCase: GetLeagueRosterMembersUseCase, @Inject(GET_LEAGUE_ROSTER_JOIN_REQUESTS_USE_CASE) private readonly getLeagueRosterJoinRequestsUseCase: GetLeagueRosterJoinRequestsUseCase, @Inject(GetLeagueRosterMembersPresenter) private readonly getLeagueRosterMembersPresenter: GetLeagueRosterMembersPresenter, @Inject(GetLeagueRosterJoinRequestsPresenter) private readonly getLeagueRosterJoinRequestsPresenter: GetLeagueRosterJoinRequestsPresenter, ) {} async getAllLeaguesWithCapacity(): Promise { this.logger.debug('[LeagueService] Fetching all leagues with capacity.'); const result = await this.getAllLeaguesWithCapacityUseCase.execute({}); if (result.isErr()) { const err = result.unwrapErr(); this.logger.error('[LeagueService] Failed to fetch leagues with capacity', new Error(err.code), { details: err.details, }); throw new Error(err.code); } this.allLeaguesWithCapacityPresenter.present(result.unwrap()); return this.allLeaguesWithCapacityPresenter.getViewModel(); } async getAllLeaguesWithCapacityAndScoring(): Promise { this.logger.debug('[LeagueService] Fetching all leagues with capacity and scoring.'); const result = await this.getAllLeaguesWithCapacityAndScoringUseCase.execute({}); if (result.isErr()) { const err = result.unwrapErr(); this.logger.error('[LeagueService] Failed to fetch leagues with capacity and scoring', new Error(err.code), { details: err.details, }); throw new Error(err.code); } this.allLeaguesWithCapacityAndScoringPresenter.present(result.unwrap()); return this.allLeaguesWithCapacityAndScoringPresenter.getViewModel(); } async getTotalLeagues(): Promise { this.logger.debug('[LeagueService] Fetching total leagues count.'); const result = await this.getTotalLeaguesUseCase.execute({}); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.totalLeaguesPresenter.present(result.unwrap()); return this.totalLeaguesPresenter.getResponseModel()!; } private getActor(): ReturnType { return getActorFromRequestContext(); } private async requireLeagueAdminPermissions(leagueId: string): Promise { await requireLeagueAdminOrOwner(leagueId, this.getLeagueAdminPermissionsUseCase); } async getLeagueJoinRequests(leagueId: string): Promise { this.logger.debug(`[LeagueService] Fetching join requests for league: ${leagueId}.`); await this.requireLeagueAdminPermissions(leagueId); this.leagueJoinRequestsPresenter.reset?.(); const result = await this.getLeagueJoinRequestsUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueJoinRequestsPresenter.present(result.unwrap()); return this.leagueJoinRequestsPresenter.getViewModel()!.joinRequests; } async approveLeagueJoinRequest(input: ApproveJoinRequestInputDTO): Promise { this.logger.debug('Approving join request:', input); await this.requireLeagueAdminPermissions(input.leagueId); const result = await this.approveLeagueJoinRequestUseCase.execute( { leagueId: input.leagueId, joinRequestId: input.requestId }, ); if (result.isErr()) { const err = result.unwrapErr(); if (err.code === 'JOIN_REQUEST_NOT_FOUND') { throw new NotFoundException('Join request not found'); } if (err.code === 'LEAGUE_NOT_FOUND') { throw new NotFoundException('League not found'); } if (err.code === 'LEAGUE_AT_CAPACITY') { throw new ConflictException('League is at capacity'); } throw new Error(err.code); } this.approveLeagueJoinRequestPresenter.present(result.unwrap()); return this.approveLeagueJoinRequestPresenter.getViewModel()!; } async rejectLeagueJoinRequest(input: RejectJoinRequestInputDTO): Promise { this.logger.debug('Rejecting join request:', input); await this.requireLeagueAdminPermissions(input.leagueId); const result = await this.rejectLeagueJoinRequestUseCase.execute( { leagueId: input.leagueId, joinRequestId: input.requestId }, ); if (result.isErr()) { const err = result.unwrapErr(); if (err.code === 'JOIN_REQUEST_NOT_FOUND') { throw new NotFoundException('Join request not found'); } if (err.code === 'LEAGUE_NOT_FOUND') { throw new NotFoundException('League not found'); } if (err.code === 'LEAGUE_AT_CAPACITY') { throw new ConflictException('League is at capacity'); } throw new Error(err.code); } this.rejectLeagueJoinRequestPresenter.present(result.unwrap()); return this.rejectLeagueJoinRequestPresenter.getViewModel()!; } async approveLeagueRosterJoinRequest(leagueId: string, joinRequestId: string): Promise { this.logger.debug('Approving roster join request:', { leagueId, joinRequestId }); await this.requireLeagueAdminPermissions(leagueId); const result = await this.approveLeagueJoinRequestUseCase.execute( { leagueId, joinRequestId }, ); if (result.isErr()) { const err = result.unwrapErr(); if (err.code === 'JOIN_REQUEST_NOT_FOUND') { throw new NotFoundException('Join request not found'); } if (err.code === 'LEAGUE_NOT_FOUND') { throw new NotFoundException('League not found'); } if (err.code === 'LEAGUE_AT_CAPACITY') { throw new ConflictException('League is at capacity'); } throw new Error(err.code); } this.approveLeagueJoinRequestPresenter.present(result.unwrap()); return this.approveLeagueJoinRequestPresenter.getViewModel()!; } async rejectLeagueRosterJoinRequest(leagueId: string, joinRequestId: string): Promise { this.logger.debug('Rejecting roster join request:', { leagueId, joinRequestId }); await this.requireLeagueAdminPermissions(leagueId); const result = await this.rejectLeagueJoinRequestUseCase.execute( { leagueId, joinRequestId }, ); if (result.isErr()) { throw new NotFoundException('Join request not found'); } this.rejectLeagueJoinRequestPresenter.present(result.unwrap()); return this.rejectLeagueJoinRequestPresenter.getViewModel()!; } async getLeagueAdminPermissions(query: GetLeagueAdminPermissionsInputDTO): Promise { const actor = this.getActor(); this.logger.debug('Getting league admin permissions', { leagueId: query.leagueId, performerDriverId: actor.driverId }); const result = await this.getLeagueAdminPermissionsUseCase.execute({ leagueId: query.leagueId, performerDriverId: actor.driverId, }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.getLeagueAdminPermissionsPresenter.present(result.unwrap()); return this.getLeagueAdminPermissionsPresenter.getResponseModel()!; } async removeLeagueMember(input: RemoveLeagueMemberInputDTO): Promise { this.logger.debug('Removing league member', { leagueId: input.leagueId, targetDriverId: input.targetDriverId }); await this.requireLeagueAdminPermissions(input.leagueId); this.removeLeagueMemberPresenter.reset?.(); const result = await this.removeLeagueMemberUseCase.execute({ leagueId: input.leagueId, targetDriverId: input.targetDriverId, }); if (result.isErr()) { const err = result.unwrapErr(); if (err.code === 'MEMBERSHIP_NOT_FOUND') { throw new NotFoundException('Member not found'); } if (err.code === 'CANNOT_REMOVE_LAST_OWNER') { throw new BadRequestException(err.details.message); } throw new Error(err.code); } this.removeLeagueMemberPresenter.present(result.unwrap()); return this.removeLeagueMemberPresenter.getViewModel()!; } async updateLeagueMemberRole( leagueId: string, targetDriverId: string, input: UpdateLeagueMemberRoleInputDTO, ): Promise { this.logger.debug('Updating league member role', { leagueId, targetDriverId, newRole: input.newRole, }); await this.requireLeagueAdminPermissions(leagueId); this.updateLeagueMemberRolePresenter.reset?.(); const result = await this.updateLeagueMemberRoleUseCase.execute({ leagueId, targetDriverId, newRole: input.newRole, }); if (result.isErr()) { const err = result.unwrapErr(); if (err.code === 'MEMBERSHIP_NOT_FOUND') { throw new NotFoundException('Member not found'); } if (err.code === 'INVALID_ROLE' || err.code === 'CANNOT_DOWNGRADE_LAST_OWNER') { throw new BadRequestException(err.details.message); } throw new Error(err.code); } this.updateLeagueMemberRolePresenter.present(result.unwrap()); return this.updateLeagueMemberRolePresenter.getViewModel()!; } async getLeagueOwnerSummary(query: GetLeagueOwnerSummaryQueryDTO): Promise { this.logger.debug('Getting league owner summary:', query); const result = await this.getLeagueOwnerSummaryUseCase.execute(query); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.getLeagueOwnerSummaryPresenter.present(result.unwrap()); return this.getLeagueOwnerSummaryPresenter.getViewModel()!; } async getLeagueFullConfig(query: GetLeagueAdminConfigQueryDTO): Promise { this.logger.debug('Getting league full config', { query }); try { const result = await this.getLeagueFullConfigUseCase.execute(query); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueConfigPresenter.present(result.unwrap()); return this.leagueConfigPresenter.getViewModel(); } catch (error) { this.logger.error('Error getting league full config', error instanceof Error ? error : new Error(String(error))); return null; } } async getLeagueProtests(query: GetLeagueProtestsQueryDTO): Promise { this.logger.debug('Getting league protests:', query); const result = await this.getLeagueProtestsUseCase.execute(query); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueProtestsPresenter.present(result.unwrap()); return this.leagueProtestsPresenter.getResponseModel()!; } async getLeagueSeasons(query: GetLeagueSeasonsQueryDTO): Promise { this.logger.debug('Getting league seasons:', query); const result = await this.getLeagueSeasonsUseCase.execute(query); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.getLeagueSeasonsPresenter.present(result.unwrap()); return this.getLeagueSeasonsPresenter.getResponseModel()!; } async getLeagueMemberships(leagueId: string): Promise { this.logger.debug('Getting league memberships', { leagueId }); const result = await this.getLeagueMembershipsUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.getLeagueMembershipsPresenter.present(result.unwrap()); return this.getLeagueMembershipsPresenter.getViewModel()!.memberships; } async getLeagueRosterMembers(leagueId: string): Promise { this.logger.debug('Getting league roster members (admin)', { leagueId }); await this.requireLeagueAdminPermissions(leagueId); this.getLeagueRosterMembersPresenter.reset?.(); const result = await this.getLeagueRosterMembersUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.getLeagueRosterMembersPresenter.present(result.unwrap()); return this.getLeagueRosterMembersPresenter.getViewModel()!; } async getLeagueRosterJoinRequests(leagueId: string): Promise { this.logger.debug('Getting league roster join requests (admin)', { leagueId }); await this.requireLeagueAdminPermissions(leagueId); this.getLeagueRosterJoinRequestsPresenter.reset?.(); const result = await this.getLeagueRosterJoinRequestsUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.getLeagueRosterJoinRequestsPresenter.present(result.unwrap()); return this.getLeagueRosterJoinRequestsPresenter.getViewModel()!; } async getLeagueStandings(leagueId: string): Promise { this.logger.debug('Getting league standings', { leagueId }); const result = await this.getLeagueStandingsUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueStandingsPresenter.present(result.unwrap()); return this.leagueStandingsPresenter.getResponseModel()!; } async getLeagueSchedule(leagueId: string, query?: GetLeagueScheduleQueryDTO): Promise { this.logger.debug('Getting league schedule', { leagueId, query }); const input: GetLeagueScheduleInput = query?.seasonId ? { leagueId, seasonId: query.seasonId } : { leagueId }; const result = await this.getLeagueScheduleUseCase.execute(input); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueSchedulePresenter.present(result.unwrap()); return this.leagueSchedulePresenter.getViewModel()!; } async publishLeagueSeasonSchedule( leagueId: string, seasonId: string, _input: LeagueSeasonSchedulePublishInputDTO, ): Promise { void _input; await this.requireLeagueAdminPermissions(leagueId); this.publishLeagueSeasonSchedulePresenter.reset?.(); const result = await this.publishLeagueSeasonScheduleUseCase.execute({ leagueId, seasonId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.publishLeagueSeasonSchedulePresenter.present(result.unwrap()); return this.publishLeagueSeasonSchedulePresenter.getResponseModel()!; } async unpublishLeagueSeasonSchedule( leagueId: string, seasonId: string, _input: LeagueSeasonSchedulePublishInputDTO, ): Promise { void _input; await this.requireLeagueAdminPermissions(leagueId); this.unpublishLeagueSeasonSchedulePresenter.reset?.(); const result = await this.unpublishLeagueSeasonScheduleUseCase.execute({ leagueId, seasonId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.unpublishLeagueSeasonSchedulePresenter.present(result.unwrap()); return this.unpublishLeagueSeasonSchedulePresenter.getResponseModel()!; } async createLeagueSeasonScheduleRace( leagueId: string, seasonId: string, input: CreateLeagueScheduleRaceInputDTO, ): Promise { await this.requireLeagueAdminPermissions(leagueId); const scheduledAt = new Date(input.scheduledAtIso); if (Number.isNaN(scheduledAt.getTime())) { throw new Error('INVALID_SCHEDULED_AT'); } this.createLeagueSeasonScheduleRacePresenter.reset?.(); const result = await this.createLeagueSeasonScheduleRaceUseCase.execute({ leagueId, seasonId, track: input.track, car: input.car, scheduledAt, }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.createLeagueSeasonScheduleRacePresenter.present(result.unwrap()); return this.createLeagueSeasonScheduleRacePresenter.getResponseModel()!; } async updateLeagueSeasonScheduleRace( leagueId: string, seasonId: string, raceId: string, input: UpdateLeagueScheduleRaceInputDTO, ): Promise { await this.requireLeagueAdminPermissions(leagueId); const scheduledAt = input.scheduledAtIso !== undefined ? new Date(input.scheduledAtIso) : undefined; if (scheduledAt && Number.isNaN(scheduledAt.getTime())) { throw new Error('INVALID_SCHEDULED_AT'); } this.updateLeagueSeasonScheduleRacePresenter.reset?.(); const result = await this.updateLeagueSeasonScheduleRaceUseCase.execute({ leagueId, seasonId, raceId, ...(input.track !== undefined ? { track: input.track } : {}), ...(input.car !== undefined ? { car: input.car } : {}), ...(scheduledAt !== undefined ? { scheduledAt } : {}), }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.updateLeagueSeasonScheduleRacePresenter.present(result.unwrap()); return this.updateLeagueSeasonScheduleRacePresenter.getResponseModel()!; } async deleteLeagueSeasonScheduleRace( leagueId: string, seasonId: string, raceId: string, ): Promise { await this.requireLeagueAdminPermissions(leagueId); this.deleteLeagueSeasonScheduleRacePresenter.reset?.(); const result = await this.deleteLeagueSeasonScheduleRaceUseCase.execute({ leagueId, seasonId, raceId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.deleteLeagueSeasonScheduleRacePresenter.present(result.unwrap()); return this.deleteLeagueSeasonScheduleRacePresenter.getResponseModel()!; } async getLeagueStats(leagueId: string): Promise { this.logger.debug('Getting league stats', { leagueId }); const result = await this.getLeagueStatsUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueStatsPresenter.present(result.unwrap()); return this.leagueStatsPresenter.getResponseModel()!; } async getLeagueAdmin(leagueId: string): Promise { this.logger.debug('Getting league admin data', { leagueId }); const [fullConfigResult, joinRequests, protests, seasons] = await Promise.all([ this.getLeagueFullConfigUseCase.execute({ leagueId }), this.getLeagueJoinRequests(leagueId), this.getLeagueProtests({ leagueId }), this.getLeagueSeasons({ leagueId }), ]); if (fullConfigResult.isErr()) { throw new Error(fullConfigResult.unwrapErr().code); } // Present the full config result this.leagueConfigPresenter.present(fullConfigResult.unwrap()); const ownerSummaryResult = await this.getLeagueOwnerSummaryUseCase.execute({ leagueId }); if (ownerSummaryResult.isErr()) { throw new Error(ownerSummaryResult.unwrapErr().code); } this.getLeagueOwnerSummaryPresenter.present(ownerSummaryResult.unwrap()); const ownerSummary = this.getLeagueOwnerSummaryPresenter.getViewModel()!; const configForm = this.leagueConfigPresenter.getViewModel(); // For now, return a simple structure since we don't have a LeagueAdminPresenter return { joinRequests: joinRequests, ownerSummary, config: { form: configForm }, protests, seasons, }; } async createLeague(input: CreateLeagueInputDTO): Promise { this.logger.debug('Creating league', { input }); const command = { name: input.name, description: input.description, ownerId: input.ownerId, visibility: 'unranked' as const, gameId: 'iracing', // Assume default maxDrivers: 32, // Default value enableDriverChampionship: true, enableTeamChampionship: false, enableNationsChampionship: false, enableTrophyChampionship: false, }; const result = await this.createLeagueWithSeasonAndScoringUseCase.execute(command); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.createLeaguePresenter.present(result.unwrap()); return this.createLeaguePresenter.getViewModel()!; } async getLeagueScoringConfig(leagueId: string): Promise { this.logger.debug('Getting league scoring config', { leagueId }); try { const result = await this.getLeagueScoringConfigUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueScoringConfigPresenter.present(result.unwrap()); return this.leagueScoringConfigPresenter.getViewModel(); } catch (error) { this.logger.error('Error getting league scoring config', error instanceof Error ? error : new Error(String(error))); return null; } } async listLeagueScoringPresets(): Promise { this.logger.debug('Listing league scoring presets'); const result = await this.listLeagueScoringPresetsUseCase.execute({}); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueScoringPresetsPresenter.present(result.unwrap()); return this.leagueScoringPresetsPresenter.getViewModel()!; } async joinLeague(leagueId: string): Promise { const actor = this.getActor(); this.logger.debug('Joining league', { leagueId, actorDriverId: actor.driverId }); const result = await this.joinLeagueUseCase.execute({ leagueId, driverId: actor.driverId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.joinLeaguePresenter.present(result.unwrap()); return this.joinLeaguePresenter.getViewModel()!; } async transferLeagueOwnership(leagueId: string, input: TransferLeagueOwnershipInputDTO): Promise { this.logger.debug('Transferring league ownership', { leagueId, newOwnerId: input.newOwnerId }); await this.requireLeagueAdminPermissions(leagueId); const actor = this.getActor(); const result = await this.transferLeagueOwnershipUseCase.execute({ leagueId, currentOwnerId: actor.driverId, newOwnerId: input.newOwnerId, }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.transferLeagueOwnershipPresenter.present(result.unwrap()); return this.transferLeagueOwnershipPresenter.getViewModel()!; } async getSeasonSponsorships(seasonId: string): Promise { this.logger.debug('Getting season sponsorships', { seasonId }); const result = await this.getSeasonSponsorshipsUseCase.execute({ seasonId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.seasonSponsorshipsPresenter.present(result.unwrap()); return this.seasonSponsorshipsPresenter.getViewModel()!; } async getRaces(leagueId: string): Promise { this.logger.debug('Getting league races', { leagueId }); // `GetLeagueScheduleUseCase` is wired to `LeagueSchedulePresenter` (not `LeagueRacesPresenter`), // so `LeagueRacesPresenter.getViewModel()` can be null at runtime. this.leagueSchedulePresenter.reset?.(); const result = await this.getLeagueScheduleUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.leagueSchedulePresenter.present(result.unwrap()); return { races: this.leagueSchedulePresenter.getViewModel()?.races ?? [], }; } async getLeagueWallet(leagueId: string): Promise { this.logger.debug('Getting league wallet', { leagueId }); const result = await this.getLeagueWalletUseCase.execute({ leagueId }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.getLeagueWalletPresenter.present(result.unwrap()); return this.getLeagueWalletPresenter.getResponseModel(); } async withdrawFromLeagueWallet( leagueId: string, input: WithdrawFromLeagueWalletInputDTO, ): Promise { this.logger.debug('Withdrawing from league wallet', { leagueId, amount: input.amount }); const actor = this.getActor(); const result = await this.withdrawFromLeagueWalletUseCase.execute({ leagueId, requestedById: actor.driverId, amount: input.amount, currency: input.currency as 'USD' | 'EUR' | 'GBP', reason: input.destinationAccount, }); if (result.isErr()) { throw new Error(result.unwrapErr().code); } this.withdrawFromLeagueWalletPresenter.present(result.unwrap()); return this.withdrawFromLeagueWalletPresenter.getResponseModel(); } }