league service

This commit is contained in:
2025-12-16 00:57:31 +01:00
parent 3b566c973d
commit 775d41e055
130 changed files with 4077 additions and 1036 deletions

View File

@@ -1,125 +1,237 @@
import { Injectable } from '@nestjs/common';
import { AllLeaguesWithCapacityViewModel, LeagueStatsDto, LeagueJoinRequestViewModel, ApproveJoinRequestInput, ApproveJoinRequestOutput, RejectJoinRequestInput, RejectJoinRequestOutput, LeagueAdminPermissionsViewModel, RemoveLeagueMemberInput, RemoveLeagueMemberOutput, UpdateLeagueMemberRoleInput, UpdateLeagueMemberRoleOutput, LeagueOwnerSummaryViewModel, LeagueConfigFormModelDto, LeagueAdminProtestsViewModel, LeagueSeasonSummaryViewModel, GetLeagueAdminPermissionsInput, GetLeagueJoinRequestsQuery, GetLeagueProtestsQuery, GetLeagueSeasonsQuery, GetLeagueAdminConfigQuery, GetLeagueOwnerSummaryQuery } from './dto/LeagueDto';
import { DriverDto } from '../driver/dto/DriverDto'; // Using the local DTO for mock data
import { RaceDto } from '../race/dto/RaceDto'; // Using the local DTO for mock data
import { Injectable, Inject } from '@nestjs/common';
import { AllLeaguesWithCapacityViewModel, LeagueStatsDto, LeagueJoinRequestViewModel, ApproveJoinRequestInput, ApproveJoinRequestOutput, RejectJoinRequestInput, RejectJoinRequestOutput, LeagueAdminPermissionsViewModel, RemoveLeagueMemberInput, RemoveLeagueMemberOutput, UpdateLeagueMemberRoleInput, UpdateLeagueMemberRoleOutput, LeagueOwnerSummaryViewModel, LeagueConfigFormModelDto, LeagueAdminProtestsViewModel, LeagueSeasonSummaryViewModel, GetLeagueAdminPermissionsInput, GetLeagueProtestsQuery, GetLeagueSeasonsQuery, GetLeagueAdminConfigQuery, GetLeagueOwnerSummaryQuery, LeagueMembershipsViewModel, LeagueStandingsViewModel, LeagueScheduleViewModel, LeagueStatsViewModel, LeagueAdminViewModel, CreateLeagueInput, CreateLeagueOutput } from './dto/LeagueDto';
const mockDriverData: Map<string, DriverDto> = new Map();
mockDriverData.set('driver-owner-1', { id: 'driver-owner-1', name: 'Owner Driver' });
mockDriverData.set('driver-1', { id: 'driver-1', name: 'Demo Driver 1' });
mockDriverData.set('driver-2', { id: 'driver-2', name: 'Demo Driver 2' });
// Core imports
import { Logger } from '@gridpilot/shared/application/Logger';
const mockRaceData: Map<string, RaceDto> = new Map();
mockRaceData.set('race-1', { id: 'race-1', name: 'Test Race 1', date: new Date().toISOString() });
mockRaceData.set('race-2', { id: 'race-2', name: 'Test Race 2', date: new Date().toISOString() });
// Use cases
import { GetAllLeaguesWithCapacityUseCase } from '@gridpilot/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase';
import { GetLeagueStandingsUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueStandingsUseCase';
import { GetLeagueStatsUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueStatsUseCase';
import { GetLeagueFullConfigUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueFullConfigUseCase';
import { CreateLeagueWithSeasonAndScoringUseCase } from '@gridpilot/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase';
import { GetRaceProtestsUseCase } from '@gridpilot/racing/application/use-cases/GetRaceProtestsUseCase';
import { GetTotalLeaguesUseCase } from '@gridpilot/racing/application/use-cases/GetTotalLeaguesUseCase';
import { GetLeagueJoinRequestsUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueJoinRequestsUseCase';
import { ApproveLeagueJoinRequestUseCase } from '@gridpilot/racing/application/use-cases/ApproveLeagueJoinRequestUseCase';
import { RejectLeagueJoinRequestUseCase } from '@gridpilot/racing/application/use-cases/RejectLeagueJoinRequestUseCase';
import { RemoveLeagueMemberUseCase } from '@gridpilot/racing/application/use-cases/RemoveLeagueMemberUseCase';
import { UpdateLeagueMemberRoleUseCase } from '@gridpilot/racing/application/use-cases/UpdateLeagueMemberRoleUseCase';
import { GetLeagueOwnerSummaryUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueOwnerSummaryUseCase';
import { GetLeagueProtestsUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueProtestsUseCase';
import { GetLeagueSeasonsUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueSeasonsUseCase';
import { GetLeagueMembershipsUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueMembershipsUseCase';
import { GetLeagueScheduleUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueScheduleUseCase';
import { GetLeagueAdminPermissionsUseCase } from '@gridpilot/racing/application/use-cases/GetLeagueAdminPermissionsUseCase';
// API Presenters
import { LeagueStandingsPresenter } from './presenters/LeagueStandingsPresenter';
import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter';
import { LeagueJoinRequestsPresenter } from './presenters/LeagueJoinRequestsPresenter';
import { ApproveLeagueJoinRequestPresenter } from './presenters/ApproveLeagueJoinRequestPresenter';
import { RejectLeagueJoinRequestPresenter } from './presenters/RejectLeagueJoinRequestPresenter';
import { RemoveLeagueMemberPresenter } from './presenters/RemoveLeagueMemberPresenter';
import { UpdateLeagueMemberRolePresenter } from './presenters/UpdateLeagueMemberRolePresenter';
import { GetLeagueOwnerSummaryPresenter } from './presenters/GetLeagueOwnerSummaryPresenter';
import { GetLeagueProtestsPresenter } from './presenters/GetLeagueProtestsPresenter';
import { GetLeagueSeasonsPresenter } from './presenters/GetLeagueSeasonsPresenter';
import { GetLeagueMembershipsPresenter } from './presenters/GetLeagueMembershipsPresenter';
import { LeagueSchedulePresenter } from './presenters/LeagueSchedulePresenter';
import { TotalLeaguesPresenter } from './presenters/TotalLeaguesPresenter';
import { LeagueConfigPresenter } from './presenters/LeagueConfigPresenter';
import { LeagueStatsPresenter } from './presenters/LeagueStatsPresenter';
import { GetLeagueAdminPermissionsPresenter } from './presenters/GetLeagueAdminPermissionsPresenter';
// Tokens
import { LOGGER_TOKEN } from './LeagueProviders';
@Injectable()
export class LeagueService {
constructor() {}
constructor(
private readonly getAllLeaguesWithCapacityUseCase: GetAllLeaguesWithCapacityUseCase,
private readonly getLeagueStandingsUseCase: GetLeagueStandingsUseCase,
private readonly getLeagueStatsUseCase: GetLeagueStatsUseCase,
private readonly getLeagueFullConfigUseCase: GetLeagueFullConfigUseCase,
private readonly createLeagueWithSeasonAndScoringUseCase: CreateLeagueWithSeasonAndScoringUseCase,
private readonly getRaceProtestsUseCase: GetRaceProtestsUseCase,
private readonly getTotalLeaguesUseCase: GetTotalLeaguesUseCase,
private readonly getLeagueJoinRequestsUseCase: GetLeagueJoinRequestsUseCase,
private readonly approveLeagueJoinRequestUseCase: ApproveLeagueJoinRequestUseCase,
private readonly rejectLeagueJoinRequestUseCase: RejectLeagueJoinRequestUseCase,
private readonly removeLeagueMemberUseCase: RemoveLeagueMemberUseCase,
private readonly updateLeagueMemberRoleUseCase: UpdateLeagueMemberRoleUseCase,
private readonly getLeagueOwnerSummaryUseCase: GetLeagueOwnerSummaryUseCase,
private readonly getLeagueProtestsUseCase: GetLeagueProtestsUseCase,
private readonly getLeagueSeasonsUseCase: GetLeagueSeasonsUseCase,
private readonly getLeagueMembershipsUseCase: GetLeagueMembershipsUseCase,
private readonly getLeagueScheduleUseCase: GetLeagueScheduleUseCase,
private readonly getLeagueAdminPermissionsUseCase: GetLeagueAdminPermissionsUseCase,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
async getAllLeaguesWithCapacity(): Promise<AllLeaguesWithCapacityViewModel> {
console.log('[LeagueService] Returning mock leagues with capacity.');
return {
leagues: [
{ id: 'league-1', name: 'Global Racing', description: 'The premier league', ownerId: 'owner-1', settings: { maxDrivers: 100 }, createdAt: new Date().toISOString(), usedSlots: 50, socialLinks: { discordUrl: 'https://discord.gg/test' } },
{ id: 'league-2', name: 'Amateur Series', description: 'Learn the ropes', ownerId: 'owner-2', settings: { maxDrivers: 50 }, createdAt: new Date().toISOString(), usedSlots: 20 },
],
totalCount: 2,
};
this.logger.debug('[LeagueService] Fetching all leagues with capacity.');
const presenter = new AllLeaguesWithCapacityPresenter();
await this.getAllLeaguesWithCapacityUseCase.execute(undefined, presenter);
return presenter.getViewModel()!;
}
async getTotalLeagues(): Promise<LeagueStatsDto> {
console.log('[LeagueService] Returning mock total leagues.');
return { totalLeagues: 2 };
this.logger.debug('[LeagueService] Fetching total leagues count.');
const presenter = new TotalLeaguesPresenter();
await this.getTotalLeaguesUseCase.execute({}, presenter);
return presenter.getViewModel()!;
}
async getLeagueJoinRequests(leagueId: string): Promise<LeagueJoinRequestViewModel[]> {
console.log(`[LeagueService] Returning mock join requests for league: ${leagueId}.`);
return [
{
id: 'join-req-1',
leagueId: 'league-1',
driverId: 'driver-1',
requestedAt: new Date(),
message: 'I want to join!',
driver: mockDriverData.get('driver-1'),
},
];
this.logger.debug(`[LeagueService] Fetching join requests for league: ${leagueId}.`);
const presenter = new LeagueJoinRequestsPresenter();
await this.getLeagueJoinRequestsUseCase.execute({ leagueId }, presenter);
return presenter.getViewModel()!.joinRequests;
}
async approveLeagueJoinRequest(input: ApproveJoinRequestInput): Promise<ApproveJoinRequestOutput> {
console.log('Approving join request:', input);
return { success: true, message: 'Join request approved.' };
this.logger.debug('Approving join request:', input);
const presenter = new ApproveLeagueJoinRequestPresenter();
await this.approveLeagueJoinRequestUseCase.execute({ leagueId: input.leagueId, requestId: input.requestId }, presenter);
return presenter.getViewModel()!;
}
async rejectLeagueJoinRequest(input: RejectJoinRequestInput): Promise<RejectJoinRequestOutput> {
console.log('Rejecting join request:', input);
return { success: true, message: 'Join request rejected.' };
this.logger.debug('Rejecting join request:', input);
const presenter = new RejectLeagueJoinRequestPresenter();
await this.rejectLeagueJoinRequestUseCase.execute({ requestId: input.requestId }, presenter);
return presenter.getViewModel()!;
}
async getLeagueAdminPermissions(query: GetLeagueAdminPermissionsInput): Promise<LeagueAdminPermissionsViewModel> {
console.log('Getting league admin permissions:', query);
return { canRemoveMember: true, canUpdateRoles: true };
this.logger.debug('Getting league admin permissions', { query });
const presenter = new GetLeagueAdminPermissionsPresenter();
await this.getLeagueAdminPermissionsUseCase.execute(
{ leagueId: query.leagueId, performerDriverId: query.performerDriverId },
presenter
);
return presenter.getViewModel()!;
}
async removeLeagueMember(input: RemoveLeagueMemberInput): Promise<RemoveLeagueMemberOutput> {
console.log('Removing league member:', input.leagueId, input.targetDriverId);
return { success: true };
this.logger.debug('Removing league member', { leagueId: input.leagueId, targetDriverId: input.targetDriverId });
const presenter = new RemoveLeagueMemberPresenter();
await this.removeLeagueMemberUseCase.execute({ leagueId: input.leagueId, targetDriverId: input.targetDriverId }, presenter);
return presenter.getViewModel()!;
}
async updateLeagueMemberRole(input: UpdateLeagueMemberRoleInput): Promise<UpdateLeagueMemberRoleOutput> {
console.log('Updating league member role:', input.leagueId, input.targetDriverId, input.newRole);
return { success: true };
this.logger.debug('Updating league member role', { leagueId: input.leagueId, targetDriverId: input.targetDriverId, newRole: input.newRole });
const presenter = new UpdateLeagueMemberRolePresenter();
await this.updateLeagueMemberRoleUseCase.execute({ leagueId: input.leagueId, targetDriverId: input.targetDriverId, newRole: input.newRole }, presenter);
return presenter.getViewModel()!;
}
async getLeagueOwnerSummary(query: GetLeagueOwnerSummaryQuery): Promise<LeagueOwnerSummaryViewModel | null> {
console.log('Getting league owner summary:', query);
return {
driver: mockDriverData.get(query.ownerId)!,
rating: 2000,
rank: 1,
};
this.logger.debug('Getting league owner summary:', query);
const presenter = new GetLeagueOwnerSummaryPresenter();
await this.getLeagueOwnerSummaryUseCase.execute({ ownerId: query.ownerId }, presenter);
return presenter.getViewModel()!.summary;
}
async getLeagueFullConfig(query: GetLeagueAdminConfigQuery): Promise<LeagueConfigFormModelDto | null> {
console.log('Getting league full config:', query);
return {
leagueId: 'league-1',
basics: { name: 'Demo League', description: 'A demo league', visibility: 'public' },
structure: { mode: 'solo' },
championships: [],
scoring: { type: 'standard', points: 10 },
dropPolicy: { strategy: 'none' },
timings: { raceDayOfWeek: 'Sunday', raceTimeHour: 20, raceTimeMinute: 0 },
stewarding: {
decisionMode: 'single_steward',
requireDefense: false,
defenseTimeLimit: 24,
voteTimeLimit: 24,
protestDeadlineHours: 2,
stewardingClosesHours: 24,
notifyAccusedOnProtest: true,
notifyOnVoteRequired: true,
},
};
this.logger.debug('Getting league full config', { query });
const presenter = new LeagueConfigPresenter();
try {
await this.getLeagueFullConfigUseCase.execute({ leagueId: query.leagueId }, presenter);
return presenter.viewModel;
} catch (error) {
this.logger.error('Error getting league full config', error);
return null;
}
}
async getLeagueProtests(query: GetLeagueProtestsQuery): Promise<LeagueAdminProtestsViewModel> {
console.log('Getting league protests:', query);
return {
protests: [
{ id: 'protest-1', raceId: 'race-1', protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', submittedAt: new Date(), description: 'Bad driving!', status: 'pending' },
],
racesById: { 'race-1': mockRaceData.get('race-1')! },
driversById: { 'driver-1': mockDriverData.get('driver-1')!, 'driver-2': mockDriverData.get('driver-2')! },
};
this.logger.debug('Getting league protests:', query);
const presenter = new GetLeagueProtestsPresenter();
await this.getLeagueProtestsUseCase.execute({ leagueId: query.leagueId }, presenter);
return presenter.getViewModel()!;
}
async getLeagueSeasons(query: GetLeagueSeasonsQuery): Promise<LeagueSeasonSummaryViewModel[]> {
console.log('Getting league seasons:', query);
return [
{ seasonId: 'season-1', name: 'Season 1', status: 'active', startDate: new Date('2025-01-01'), endDate: new Date('2025-12-31'), isPrimary: true, isParallelActive: false },
{ seasonId: 'season-2', name: 'Season 2', status: 'upcoming', startDate: new Date('2026-01-01'), endDate: new Date('2026-12-31'), isPrimary: false, isParallelActive: false },
];
this.logger.debug('Getting league seasons:', query);
const presenter = new GetLeagueSeasonsPresenter();
await this.getLeagueSeasonsUseCase.execute({ leagueId: query.leagueId }, presenter);
return presenter.getViewModel()!.seasons;
}
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsViewModel> {
this.logger.debug('Getting league memberships', { leagueId });
const presenter = new GetLeagueMembershipsPresenter();
await this.getLeagueMembershipsUseCase.execute({ leagueId }, presenter);
return presenter.apiViewModel!;
}
async getLeagueStandings(leagueId: string): Promise<LeagueStandingsViewModel> {
this.logger.debug('Getting league standings', { leagueId });
const presenter = new LeagueStandingsPresenter();
await this.getLeagueStandingsUseCase.execute({ leagueId }, presenter);
return presenter.getViewModel()!;
}
async getLeagueSchedule(leagueId: string): Promise<LeagueScheduleViewModel> {
this.logger.debug('Getting league schedule', { leagueId });
const presenter = new LeagueSchedulePresenter();
await this.getLeagueScheduleUseCase.execute({ leagueId }, presenter);
return presenter.getViewModel()!;
}
async getLeagueStats(leagueId: string): Promise<LeagueStatsViewModel> {
this.logger.debug('Getting league stats', { leagueId });
const presenter = new LeagueStatsPresenter();
await this.getLeagueStatsUseCase.execute({ leagueId }, presenter);
return presenter.getViewModel()!;
}
async getLeagueAdmin(leagueId: string): Promise<LeagueAdminViewModel> {
this.logger.debug('Getting league admin data', { leagueId });
// For now, we'll keep the orchestration in the service since it combines multiple use cases
// TODO: Create a composite use case that handles all the admin data fetching
const joinRequests = await this.getLeagueJoinRequests(leagueId);
const config = await this.getLeagueFullConfig({ leagueId });
const protests = await this.getLeagueProtests({ leagueId });
const seasons = await this.getLeagueSeasons({ leagueId });
// Get owner summary - we need the ownerId, so we use a simple approach for now
// In a full implementation, we'd have a use case that gets league basic info
const ownerSummary = config ? await this.getLeagueOwnerSummary({ ownerId: 'placeholder', leagueId }) : null;
return {
joinRequests,
ownerSummary,
config: { form: config },
protests,
seasons,
};
}
async createLeague(input: CreateLeagueInput): Promise<CreateLeagueOutput> {
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);
return {
leagueId: result.leagueId,
success: true,
};
}
}