wip league admin tools

This commit is contained in:
2025-12-28 12:04:12 +01:00
parent 5dc8c2399c
commit 6edf12fda8
401 changed files with 15365 additions and 6047 deletions

View File

@@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { BadRequestException, ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { ApproveJoinRequestInputDTO } from './dtos/ApproveJoinRequestInputDTO';
import { ApproveLeagueJoinRequestDTO } from './dtos/ApproveLeagueJoinRequestDTO';
import { CreateLeagueInputDTO } from './dtos/CreateLeagueInputDTO';
@@ -9,6 +9,13 @@ import { GetLeagueProtestsQueryDTO } from './dtos/GetLeagueProtestsQueryDTO';
import { GetLeagueRacesOutputDTO } from './dtos/GetLeagueRacesOutputDTO';
import { GetLeagueSeasonsQueryDTO } from './dtos/GetLeagueSeasonsQueryDTO';
import { GetLeagueWalletOutputDTO } from './dtos/GetLeagueWalletOutputDTO';
import { GetLeagueScheduleQueryDTO } from './dtos/GetLeagueScheduleQueryDTO';
import {
CreateLeagueScheduleRaceInputDTO,
CreateLeagueScheduleRaceOutputDTO,
LeagueScheduleRaceMutationSuccessDTO,
UpdateLeagueScheduleRaceInputDTO,
} from './dtos/LeagueScheduleRaceAdminDTO';
import { GetSeasonSponsorshipsOutputDTO } from './dtos/GetSeasonSponsorshipsOutputDTO';
import { LeagueAdminDTO } from './dtos/LeagueAdminDTO';
import { LeagueAdminPermissionsDTO } from './dtos/LeagueAdminPermissionsDTO';
@@ -16,8 +23,14 @@ import { LeagueAdminProtestsDTO } from './dtos/LeagueAdminProtestsDTO';
import { LeagueConfigFormModelDTO } from './dtos/LeagueConfigFormModelDTO';
import { LeagueJoinRequestWithDriverDTO } from './dtos/LeagueJoinRequestWithDriverDTO';
import { LeagueMembershipsDTO } from './dtos/LeagueMembershipsDTO';
import { LeagueRosterJoinRequestDTO } from './dtos/LeagueRosterJoinRequestDTO';
import { LeagueRosterMemberDTO } from './dtos/LeagueRosterMemberDTO';
import { LeagueOwnerSummaryDTO } from './dtos/LeagueOwnerSummaryDTO';
import { LeagueScheduleDTO } from './dtos/LeagueScheduleDTO';
import {
LeagueSeasonSchedulePublishInputDTO,
LeagueSeasonSchedulePublishOutputDTO,
} from './dtos/LeagueSeasonSchedulePublishDTO';
import { LeagueSeasonSummaryDTO } from './dtos/LeagueSeasonSummaryDTO';
import { LeagueStandingsDTO } from './dtos/LeagueStandingsDTO';
import { LeagueStatsDTO } from './dtos/LeagueStatsDTO';
@@ -26,11 +39,15 @@ import { RejectJoinRequestOutputDTO } from './dtos/RejectJoinRequestOutputDTO';
import { RemoveLeagueMemberInputDTO } from './dtos/RemoveLeagueMemberInputDTO';
import { RemoveLeagueMemberOutputDTO } from './dtos/RemoveLeagueMemberOutputDTO';
import { TransferLeagueOwnershipOutputDTO } from './dtos/TransferLeagueOwnershipOutputDTO';
import { TransferLeagueOwnershipInputDTO } from './dtos/TransferLeagueOwnershipInputDTO';
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 { AllLeaguesWithCapacityDTO as AllLeaguesWithCapacityViewModel } from './dtos/AllLeaguesWithCapacityDTO';
import type { AllLeaguesWithCapacityAndScoringDTO as AllLeaguesWithCapacityAndScoringViewModel } from './dtos/AllLeaguesWithCapacityAndScoringDTO';
@@ -52,9 +69,12 @@ import { GetLeagueAdminPermissionsUseCase } from '@core/racing/application/use-c
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 { GetLeagueRosterMembersUseCase } from '@core/racing/application/use-cases/GetLeagueRosterMembersUseCase';
import { GetLeagueRosterJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueRosterJoinRequestsUseCase';
import { GetLeagueOwnerSummaryUseCase } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase';
import { GetLeagueProtestsUseCase } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase';
import { GetLeagueScheduleUseCase } from '@core/racing/application/use-cases/GetLeagueScheduleUseCase';
import type { GetLeagueScheduleInput } 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';
@@ -70,6 +90,12 @@ import { TransferLeagueOwnershipUseCase } from '@core/racing/application/use-cas
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 { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter';
import { AllLeaguesWithCapacityAndScoringPresenter } from './presenters/AllLeaguesWithCapacityAndScoringPresenter';
@@ -77,6 +103,10 @@ import { ApproveLeagueJoinRequestPresenter } from './presenters/ApproveLeagueJoi
import { CreateLeaguePresenter } from './presenters/CreateLeaguePresenter';
import { GetLeagueAdminPermissionsPresenter } from './presenters/GetLeagueAdminPermissionsPresenter';
import { GetLeagueMembershipsPresenter } from './presenters/GetLeagueMembershipsPresenter';
import {
GetLeagueRosterJoinRequestsPresenter,
GetLeagueRosterMembersPresenter,
} from './presenters/LeagueRosterAdminReadPresenters';
import { GetLeagueOwnerSummaryPresenter } from './presenters/GetLeagueOwnerSummaryPresenter';
import { GetLeagueProtestsPresenter } from './presenters/GetLeagueProtestsPresenter';
import { GetLeagueSeasonsPresenter } from './presenters/GetLeagueSeasonsPresenter';
@@ -96,58 +126,74 @@ import { TransferLeagueOwnershipPresenter } from './presenters/TransferLeagueOwn
import { UpdateLeagueMemberRolePresenter } from './presenters/UpdateLeagueMemberRolePresenter';
import { GetLeagueWalletPresenter } from './presenters/GetLeagueWalletPresenter';
import { WithdrawFromLeagueWalletPresenter } from './presenters/WithdrawFromLeagueWalletPresenter';
import {
CreateLeagueSeasonScheduleRacePresenter,
DeleteLeagueSeasonScheduleRacePresenter,
PublishLeagueSeasonSchedulePresenter,
UnpublishLeagueSeasonSchedulePresenter,
UpdateLeagueSeasonScheduleRacePresenter,
} from './presenters/LeagueSeasonScheduleMutationPresenters';
// Tokens
import {
LOGGER_TOKEN,
GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE,
GET_LEAGUE_STANDINGS_USE_CASE,
GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_USE_CASE,
GET_LEAGUE_STATS_USE_CASE,
GET_LEAGUE_FULL_CONFIG_USE_CASE,
GET_LEAGUE_SCORING_CONFIG_USE_CASE,
LIST_LEAGUE_SCORING_PRESETS_USE_CASE,
JOIN_LEAGUE_USE_CASE,
TRANSFER_LEAGUE_OWNERSHIP_USE_CASE,
CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE,
GET_TOTAL_LEAGUES_USE_CASE,
GET_LEAGUE_JOIN_REQUESTS_USE_CASE,
APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE,
REJECT_LEAGUE_JOIN_REQUEST_USE_CASE,
REMOVE_LEAGUE_MEMBER_USE_CASE,
UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE,
GET_LEAGUE_OWNER_SUMMARY_USE_CASE,
GET_LEAGUE_PROTESTS_USE_CASE,
GET_LEAGUE_SEASONS_USE_CASE,
GET_LEAGUE_MEMBERSHIPS_USE_CASE,
GET_LEAGUE_SCHEDULE_USE_CASE,
GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE,
GET_LEAGUE_WALLET_USE_CASE,
WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE,
GET_SEASON_SPONSORSHIPS_USE_CASE,
GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN,
GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN,
GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN,
GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN,
APPROVE_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN,
APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE,
CREATE_LEAGUE_OUTPUT_PORT_TOKEN,
CREATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE,
GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN,
GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_USE_CASE,
GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN,
GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE,
GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE,
GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN,
GET_LEAGUE_FULL_CONFIG_USE_CASE,
GET_LEAGUE_JOIN_REQUESTS_USE_CASE,
GET_LEAGUE_MEMBERSHIPS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_MEMBERSHIPS_USE_CASE,
GET_LEAGUE_ROSTER_JOIN_REQUESTS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_ROSTER_JOIN_REQUESTS_USE_CASE,
GET_LEAGUE_ROSTER_MEMBERS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_ROSTER_MEMBERS_USE_CASE,
GET_LEAGUE_OWNER_SUMMARY_OUTPUT_PORT_TOKEN,
GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN,
JOIN_LEAGUE_OUTPUT_PORT_TOKEN,
GET_LEAGUE_OWNER_SUMMARY_USE_CASE,
GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_PROTESTS_USE_CASE,
GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN,
GET_LEAGUE_SCHEDULE_USE_CASE,
GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN,
GET_LEAGUE_SCORING_CONFIG_USE_CASE,
GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_SEASONS_USE_CASE,
GET_LEAGUE_STATS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_STATS_USE_CASE,
GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN,
GET_LEAGUE_STANDINGS_USE_CASE,
GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
GET_LEAGUE_WALLET_USE_CASE,
GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN,
GET_SEASON_SPONSORSHIPS_USE_CASE,
GET_TOTAL_LEAGUES_USE_CASE,
JOIN_LEAGUE_OUTPUT_PORT_TOKEN,
JOIN_LEAGUE_USE_CASE,
LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN,
LIST_LEAGUE_SCORING_PRESETS_USE_CASE,
LOGGER_TOKEN,
PUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE,
REJECT_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN,
REJECT_LEAGUE_JOIN_REQUEST_USE_CASE,
REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN,
REMOVE_LEAGUE_MEMBER_USE_CASE,
TOTAL_LEAGUES_OUTPUT_PORT_TOKEN,
TRANSFER_LEAGUE_OWNERSHIP_OUTPUT_PORT_TOKEN,
TRANSFER_LEAGUE_OWNERSHIP_USE_CASE,
UNPUBLISH_LEAGUE_SEASON_SCHEDULE_USE_CASE,
UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN,
GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN,
GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN,
GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE,
UPDATE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
DELETE_LEAGUE_SEASON_SCHEDULE_RACE_USE_CASE,
WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN,
WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE,
} from './LeagueTokens';
@Injectable()
@@ -178,7 +224,21 @@ export class LeagueService {
@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(GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN) private readonly allLeaguesWithCapacityPresenter: AllLeaguesWithCapacityPresenter,
@Inject(GET_ALL_LEAGUES_WITH_CAPACITY_AND_SCORING_OUTPUT_PORT_TOKEN) private readonly allLeaguesWithCapacityAndScoringPresenter: AllLeaguesWithCapacityAndScoringPresenter,
@@ -204,8 +264,30 @@ export class LeagueService {
@Inject(GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN) private readonly leagueScoringConfigPresenter: LeagueScoringConfigPresenter,
@Inject(GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN) private readonly getLeagueWalletPresenter: GetLeagueWalletPresenter,
@Inject(WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN) private readonly withdrawFromLeagueWalletPresenter: WithdrawFromLeagueWalletPresenter,
private readonly leagueJoinRequestsPresenter: LeagueJoinRequestsPresenter,
private readonly leagueRacesPresenter: LeagueRacesPresenter,
@Inject(LeagueJoinRequestsPresenter) private readonly leagueJoinRequestsPresenter: LeagueJoinRequestsPresenter,
@Inject(LeagueRacesPresenter) private readonly leagueRacesPresenter: LeagueRacesPresenter,
// 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(GET_LEAGUE_ROSTER_MEMBERS_OUTPUT_PORT_TOKEN)
private readonly getLeagueRosterMembersPresenter: GetLeagueRosterMembersPresenter,
@Inject(GET_LEAGUE_ROSTER_JOIN_REQUESTS_OUTPUT_PORT_TOKEN)
private readonly getLeagueRosterJoinRequestsPresenter: GetLeagueRosterJoinRequestsPresenter,
) {}
async getAllLeaguesWithCapacity(): Promise<AllLeaguesWithCapacityViewModel> {
@@ -247,43 +329,214 @@ export class LeagueService {
return this.totalLeaguesPresenter.getResponseModel()!;
}
private getActor(): ReturnType<typeof getActorFromRequestContext> {
return getActorFromRequestContext();
}
private async requireLeagueAdminPermissions(leagueId: string): Promise<void> {
await requireLeagueAdminOrOwner(leagueId, this.getLeagueAdminPermissionsUseCase);
}
async getLeagueJoinRequests(leagueId: string): Promise<LeagueJoinRequestWithDriverDTO[]> {
this.logger.debug(`[LeagueService] Fetching join requests for league: ${leagueId}.`);
await this.requireLeagueAdminPermissions(leagueId);
this.leagueJoinRequestsPresenter.reset?.();
await this.getLeagueJoinRequestsUseCase.execute({ leagueId });
return this.leagueJoinRequestsPresenter.getViewModel()!.joinRequests;
}
async approveLeagueJoinRequest(input: ApproveJoinRequestInputDTO): Promise<ApproveLeagueJoinRequestDTO> {
this.logger.debug('Approving join request:', input);
await this.approveLeagueJoinRequestUseCase.execute(input, this.approveLeagueJoinRequestPresenter);
await this.requireLeagueAdminPermissions(input.leagueId);
this.approveLeagueJoinRequestPresenter.reset?.();
const result = await this.approveLeagueJoinRequestUseCase.execute(
{ leagueId: input.leagueId, joinRequestId: input.requestId },
this.approveLeagueJoinRequestPresenter,
);
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);
}
return this.approveLeagueJoinRequestPresenter.getViewModel()!;
}
async rejectLeagueJoinRequest(input: RejectJoinRequestInputDTO): Promise<RejectJoinRequestOutputDTO> {
this.logger.debug('Rejecting join request:', input);
await this.rejectLeagueJoinRequestUseCase.execute({
leagueId: input.leagueId,
adminId: 'admin', // This should come from auth context
requestId: input.requestId
});
await this.requireLeagueAdminPermissions(input.leagueId);
this.rejectLeagueJoinRequestPresenter.reset?.();
const result = await this.rejectLeagueJoinRequestUseCase.execute(
{ leagueId: input.leagueId, joinRequestId: input.requestId },
this.rejectLeagueJoinRequestPresenter,
);
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);
}
return this.rejectLeagueJoinRequestPresenter.getViewModel()!;
}
async approveLeagueRosterJoinRequest(leagueId: string, joinRequestId: string): Promise<ApproveLeagueJoinRequestDTO> {
this.logger.debug('Approving roster join request:', { leagueId, joinRequestId });
await this.requireLeagueAdminPermissions(leagueId);
this.approveLeagueJoinRequestPresenter.reset?.();
const result = await this.approveLeagueJoinRequestUseCase.execute(
{ leagueId, joinRequestId },
this.approveLeagueJoinRequestPresenter,
);
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);
}
return this.approveLeagueJoinRequestPresenter.getViewModel()!;
}
async rejectLeagueRosterJoinRequest(leagueId: string, joinRequestId: string): Promise<RejectJoinRequestOutputDTO> {
this.logger.debug('Rejecting roster join request:', { leagueId, joinRequestId });
await this.requireLeagueAdminPermissions(leagueId);
this.rejectLeagueJoinRequestPresenter.reset?.();
const result = await this.rejectLeagueJoinRequestUseCase.execute(
{ leagueId, joinRequestId },
this.rejectLeagueJoinRequestPresenter,
);
if (result.isErr()) {
throw new NotFoundException('Join request not found');
}
return this.rejectLeagueJoinRequestPresenter.getViewModel()!;
}
async getLeagueAdminPermissions(query: GetLeagueAdminPermissionsInputDTO): Promise<LeagueAdminPermissionsDTO> {
this.logger.debug('Getting league admin permissions', { query });
await this.getLeagueAdminPermissionsUseCase.execute(query);
const actor = this.getActor();
this.logger.debug('Getting league admin permissions', { leagueId: query.leagueId, performerDriverId: actor.driverId });
await this.getLeagueAdminPermissionsUseCase.execute({
leagueId: query.leagueId,
performerDriverId: actor.driverId,
});
return this.getLeagueAdminPermissionsPresenter.getResponseModel()!;
}
async removeLeagueMember(input: RemoveLeagueMemberInputDTO): Promise<RemoveLeagueMemberOutputDTO> {
this.logger.debug('Removing league member', { leagueId: input.leagueId, targetDriverId: input.targetDriverId });
await this.removeLeagueMemberUseCase.execute(input);
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);
}
return this.removeLeagueMemberPresenter.getViewModel()!;
}
async updateLeagueMemberRole(input: UpdateLeagueMemberRoleInputDTO): Promise<UpdateLeagueMemberRoleOutputDTO> {
this.logger.debug('Updating league member role', { leagueId: input.leagueId, targetDriverId: input.targetDriverId, newRole: input.newRole });
await this.updateLeagueMemberRoleUseCase.execute(input);
async updateLeagueMemberRole(
leagueId: string,
targetDriverId: string,
input: UpdateLeagueMemberRoleInputDTO,
): Promise<UpdateLeagueMemberRoleOutputDTO> {
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);
}
return this.updateLeagueMemberRolePresenter.getViewModel()!;
}
@@ -323,19 +576,166 @@ export class LeagueService {
return this.getLeagueMembershipsPresenter.getViewModel()!.memberships;
}
async getLeagueRosterMembers(leagueId: string): Promise<LeagueRosterMemberDTO[]> {
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);
}
return this.getLeagueRosterMembersPresenter.getViewModel()!;
}
async getLeagueRosterJoinRequests(leagueId: string): Promise<LeagueRosterJoinRequestDTO[]> {
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);
}
return this.getLeagueRosterJoinRequestsPresenter.getViewModel()!;
}
async getLeagueStandings(leagueId: string): Promise<LeagueStandingsDTO> {
this.logger.debug('Getting league standings', { leagueId });
await this.getLeagueStandingsUseCase.execute({ leagueId });
return this.leagueStandingsPresenter.getResponseModel()!;
}
async getLeagueSchedule(leagueId: string): Promise<LeagueScheduleDTO> {
this.logger.debug('Getting league schedule', { leagueId });
async getLeagueSchedule(leagueId: string, query?: GetLeagueScheduleQueryDTO): Promise<LeagueScheduleDTO> {
this.logger.debug('Getting league schedule', { leagueId, query });
const input: GetLeagueScheduleInput = query?.seasonId ? { leagueId, seasonId: query.seasonId } : { leagueId };
await this.getLeagueScheduleUseCase.execute(input);
await this.getLeagueScheduleUseCase.execute({ leagueId });
return this.leagueSchedulePresenter.getViewModel()!;
}
async publishLeagueSeasonSchedule(
leagueId: string,
seasonId: string,
_input: LeagueSeasonSchedulePublishInputDTO,
): Promise<LeagueSeasonSchedulePublishOutputDTO> {
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);
}
return this.publishLeagueSeasonSchedulePresenter.getResponseModel()!;
}
async unpublishLeagueSeasonSchedule(
leagueId: string,
seasonId: string,
_input: LeagueSeasonSchedulePublishInputDTO,
): Promise<LeagueSeasonSchedulePublishOutputDTO> {
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);
}
return this.unpublishLeagueSeasonSchedulePresenter.getResponseModel()!;
}
async createLeagueSeasonScheduleRace(
leagueId: string,
seasonId: string,
input: CreateLeagueScheduleRaceInputDTO,
): Promise<CreateLeagueScheduleRaceOutputDTO> {
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);
}
return this.createLeagueSeasonScheduleRacePresenter.getResponseModel()!;
}
async updateLeagueSeasonScheduleRace(
leagueId: string,
seasonId: string,
raceId: string,
input: UpdateLeagueScheduleRaceInputDTO,
): Promise<LeagueScheduleRaceMutationSuccessDTO> {
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);
}
return this.updateLeagueSeasonScheduleRacePresenter.getResponseModel()!;
}
async deleteLeagueSeasonScheduleRace(
leagueId: string,
seasonId: string,
raceId: string,
): Promise<LeagueScheduleRaceMutationSuccessDTO> {
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);
}
return this.deleteLeagueSeasonScheduleRacePresenter.getResponseModel()!;
}
async getLeagueStats(leagueId: string): Promise<LeagueStatsDTO> {
this.logger.debug('Getting league stats', { leagueId });
await this.getLeagueStatsUseCase.execute({ leagueId });
@@ -408,17 +808,27 @@ export class LeagueService {
return this.leagueScoringPresetsPresenter.getViewModel()!;
}
async joinLeague(leagueId: string, driverId: string): Promise<JoinLeagueOutputDTO> {
this.logger.debug('Joining league', { leagueId, driverId });
async joinLeague(leagueId: string): Promise<JoinLeagueOutputDTO> {
const actor = this.getActor();
this.logger.debug('Joining league', { leagueId, actorDriverId: actor.driverId });
await this.joinLeagueUseCase.execute({ leagueId, driverId });
await this.joinLeagueUseCase.execute({ leagueId, driverId: actor.driverId });
return this.joinLeaguePresenter.getViewModel()!;
}
async transferLeagueOwnership(leagueId: string, currentOwnerId: string, newOwnerId: string): Promise<TransferLeagueOwnershipOutputDTO> {
this.logger.debug('Transferring league ownership', { leagueId, currentOwnerId, newOwnerId });
async transferLeagueOwnership(leagueId: string, input: TransferLeagueOwnershipInputDTO): Promise<TransferLeagueOwnershipOutputDTO> {
this.logger.debug('Transferring league ownership', { leagueId, newOwnerId: input.newOwnerId });
await this.requireLeagueAdminPermissions(leagueId);
const actor = this.getActor();
await this.transferLeagueOwnershipUseCase.execute({
leagueId,
currentOwnerId: actor.driverId,
newOwnerId: input.newOwnerId,
});
await this.transferLeagueOwnershipUseCase.execute({ leagueId, currentOwnerId, newOwnerId });
return this.transferLeagueOwnershipPresenter.getViewModel()!;
}
@@ -444,15 +854,22 @@ export class LeagueService {
return this.getLeagueWalletPresenter.getResponseModel();
}
async withdrawFromLeagueWallet(leagueId: string, input: WithdrawFromLeagueWalletInputDTO): Promise<WithdrawFromLeagueWalletOutputDTO> {
async withdrawFromLeagueWallet(
leagueId: string,
input: WithdrawFromLeagueWalletInputDTO,
): Promise<WithdrawFromLeagueWalletOutputDTO> {
this.logger.debug('Withdrawing from league wallet', { leagueId, amount: input.amount });
const actor = this.getActor();
await this.withdrawFromLeagueWalletUseCase.execute({
leagueId,
requestedById: "admin",
requestedById: actor.driverId,
amount: input.amount,
currency: input.currency as 'USD' | 'EUR' | 'GBP',
reason: input.destinationAccount,
});
return this.withdrawFromLeagueWalletPresenter.getResponseModel();
}
}