refactor
This commit is contained in:
43
apps/api/src/domain/league/LeagueController.test.ts
Normal file
43
apps/api/src/domain/league/LeagueController.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LeagueController } from './LeagueController';
|
||||
import { LeagueService } from './LeagueService';
|
||||
import { LeagueProviders } from './LeagueProviders';
|
||||
|
||||
describe('LeagueController (integration)', () => {
|
||||
let controller: LeagueController;
|
||||
let service: LeagueService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [LeagueController],
|
||||
providers: [LeagueService, ...LeagueProviders],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<LeagueController>(LeagueController);
|
||||
service = module.get<LeagueService>(LeagueService);
|
||||
});
|
||||
|
||||
it('should get total leagues', async () => {
|
||||
const result = await controller.getTotalLeagues();
|
||||
expect(result).toHaveProperty('totalLeagues');
|
||||
expect(typeof result.totalLeagues).toBe('number');
|
||||
});
|
||||
|
||||
it('should get all leagues with capacity', async () => {
|
||||
const result = await controller.getAllLeaguesWithCapacity();
|
||||
expect(result).toHaveProperty('leagues');
|
||||
expect(result).toHaveProperty('totalCount');
|
||||
expect(Array.isArray(result.leagues)).toBe(true);
|
||||
});
|
||||
|
||||
it('should get league standings', async () => {
|
||||
try {
|
||||
const result = await controller.getLeagueStandings('non-existent-league');
|
||||
expect(result).toHaveProperty('standings');
|
||||
expect(Array.isArray(result.standings)).toBe(true);
|
||||
} catch (error) {
|
||||
// Expected for non-existent league
|
||||
expect(error.message).toContain('not found');
|
||||
}
|
||||
});
|
||||
});
|
||||
179
apps/api/src/domain/league/LeagueController.ts
Normal file
179
apps/api/src/domain/league/LeagueController.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { Controller, Get, Post, Patch, Body, Param } from '@nestjs/common';
|
||||
import { ApiTags, ApiResponse, ApiOperation, ApiBody } from '@nestjs/swagger';
|
||||
import { LeagueService } from './LeagueService';
|
||||
import { AllLeaguesWithCapacityViewModel, LeagueStatsDto, LeagueJoinRequestViewModel, ApproveJoinRequestInput, ApproveJoinRequestOutput, RejectJoinRequestInput, RejectJoinRequestOutput, LeagueAdminPermissionsViewModel, RemoveLeagueMemberInput, RemoveLeagueMemberOutput, UpdateLeagueMemberRoleInput, UpdateLeagueMemberRoleOutput, LeagueOwnerSummaryViewModel, LeagueConfigFormModelDto, LeagueAdminProtestsViewModel, LeagueSeasonSummaryViewModel, LeagueMembershipsViewModel, LeagueStandingsViewModel, LeagueScheduleViewModel, LeagueStatsViewModel, LeagueAdminViewModel, CreateLeagueInput, CreateLeagueOutput } from './dto/LeagueDto';
|
||||
import { GetLeagueAdminPermissionsInput, GetLeagueJoinRequestsQuery, GetLeagueProtestsQuery, GetLeagueSeasonsQuery, GetLeagueAdminConfigQuery, GetLeagueOwnerSummaryQuery } from './dto/LeagueDto'; // Explicitly import queries
|
||||
|
||||
@ApiTags('leagues')
|
||||
@Controller('leagues')
|
||||
export class LeagueController {
|
||||
constructor(private readonly leagueService: LeagueService) {}
|
||||
|
||||
@Get('all-with-capacity')
|
||||
@ApiOperation({ summary: 'Get all leagues with their capacity information' })
|
||||
@ApiResponse({ status: 200, description: 'List of leagues with capacity', type: AllLeaguesWithCapacityViewModel })
|
||||
async getAllLeaguesWithCapacity(): Promise<AllLeaguesWithCapacityViewModel> {
|
||||
return this.leagueService.getAllLeaguesWithCapacity();
|
||||
}
|
||||
|
||||
@Get('total-leagues')
|
||||
@ApiOperation({ summary: 'Get the total number of leagues' })
|
||||
@ApiResponse({ status: 200, description: 'Total number of leagues', type: LeagueStatsDto })
|
||||
async getTotalLeagues(): Promise<LeagueStatsDto> {
|
||||
return this.leagueService.getTotalLeagues();
|
||||
}
|
||||
|
||||
@Get(':leagueId/join-requests')
|
||||
@ApiOperation({ summary: 'Get all outstanding join requests for a league' })
|
||||
@ApiResponse({ status: 200, description: 'List of join requests', type: [LeagueJoinRequestViewModel] })
|
||||
async getJoinRequests(@Param('leagueId') leagueId: string): Promise<LeagueJoinRequestViewModel[]> {
|
||||
// No specific query DTO needed for GET, leagueId from param
|
||||
return this.leagueService.getLeagueJoinRequests(leagueId);
|
||||
}
|
||||
|
||||
@Post(':leagueId/join-requests/approve')
|
||||
@ApiOperation({ summary: 'Approve a league join request' })
|
||||
@ApiBody({ type: ApproveJoinRequestInput }) // Explicitly define body type for Swagger
|
||||
@ApiResponse({ status: 200, description: 'Join request approved', type: ApproveJoinRequestOutput })
|
||||
@ApiResponse({ status: 404, description: 'Join request not found' })
|
||||
async approveJoinRequest(
|
||||
@Param('leagueId') leagueId: string,
|
||||
@Body() input: ApproveJoinRequestInput,
|
||||
): Promise<ApproveJoinRequestOutput> {
|
||||
return this.leagueService.approveLeagueJoinRequest({ ...input, leagueId });
|
||||
}
|
||||
|
||||
@Post(':leagueId/join-requests/reject')
|
||||
@ApiOperation({ summary: 'Reject a league join request' })
|
||||
@ApiBody({ type: RejectJoinRequestInput })
|
||||
@ApiResponse({ status: 200, description: 'Join request rejected', type: RejectJoinRequestOutput })
|
||||
@ApiResponse({ status: 404, description: 'Join request not found' })
|
||||
async rejectJoinRequest(
|
||||
@Param('leagueId') leagueId: string,
|
||||
@Body() input: RejectJoinRequestInput,
|
||||
): Promise<RejectJoinRequestOutput> {
|
||||
return this.leagueService.rejectLeagueJoinRequest({ ...input, leagueId });
|
||||
}
|
||||
|
||||
@Get(':leagueId/permissions/:performerDriverId')
|
||||
@ApiOperation({ summary: 'Get league admin permissions for a performer' })
|
||||
@ApiResponse({ status: 200, description: 'League admin permissions', type: LeagueAdminPermissionsViewModel })
|
||||
async getLeagueAdminPermissions(
|
||||
@Param('leagueId') leagueId: string,
|
||||
@Param('performerDriverId') performerDriverId: string,
|
||||
): Promise<LeagueAdminPermissionsViewModel> {
|
||||
// No specific input DTO needed for Get, parameters from path
|
||||
return this.leagueService.getLeagueAdminPermissions({ leagueId, performerDriverId });
|
||||
}
|
||||
|
||||
@Patch(':leagueId/members/:targetDriverId/remove')
|
||||
@ApiOperation({ summary: 'Remove a member from the league' })
|
||||
@ApiBody({ type: RemoveLeagueMemberInput }) // Explicitly define body type for Swagger
|
||||
@ApiResponse({ status: 200, description: 'Member removed successfully', type: RemoveLeagueMemberOutput })
|
||||
@ApiResponse({ status: 400, description: 'Cannot remove member' })
|
||||
@ApiResponse({ status: 404, description: 'Member not found' })
|
||||
async removeLeagueMember(
|
||||
@Param('leagueId') leagueId: string,
|
||||
@Param('performerDriverId') performerDriverId: string,
|
||||
@Param('targetDriverId') targetDriverId: string,
|
||||
@Body() input: RemoveLeagueMemberInput, // Body content for a patch often includes IDs
|
||||
): Promise<RemoveLeagueMemberOutput> {
|
||||
return this.leagueService.removeLeagueMember({ leagueId, performerDriverId, targetDriverId });
|
||||
}
|
||||
|
||||
@Patch(':leagueId/members/:targetDriverId/role')
|
||||
@ApiOperation({ summary: "Update a member's role in the league" })
|
||||
@ApiBody({ type: UpdateLeagueMemberRoleInput }) // Explicitly define body type for Swagger
|
||||
@ApiResponse({ status: 200, description: 'Member role updated successfully', type: UpdateLeagueMemberRoleOutput })
|
||||
@ApiResponse({ status: 400, description: 'Cannot update role' })
|
||||
@ApiResponse({ status: 404, description: 'Member not found' })
|
||||
async updateLeagueMemberRole(
|
||||
@Param('leagueId') leagueId: string,
|
||||
@Param('performerDriverId') performerDriverId: string,
|
||||
@Param('targetDriverId') targetDriverId: string,
|
||||
@Body() input: UpdateLeagueMemberRoleInput, // Body includes newRole, other for swagger
|
||||
): Promise<UpdateLeagueMemberRoleOutput> {
|
||||
return this.leagueService.updateLeagueMemberRole({ leagueId, performerDriverId, targetDriverId, newRole: input.newRole });
|
||||
}
|
||||
|
||||
@Get(':leagueId/owner-summary/:ownerId')
|
||||
@ApiOperation({ summary: 'Get owner summary for a league' })
|
||||
@ApiResponse({ status: 200, description: 'League owner summary', type: LeagueOwnerSummaryViewModel })
|
||||
@ApiResponse({ status: 404, description: 'Owner or league not found' })
|
||||
async getLeagueOwnerSummary(
|
||||
@Param('leagueId') leagueId: string,
|
||||
@Param('ownerId') ownerId: string,
|
||||
): Promise<LeagueOwnerSummaryViewModel | null> {
|
||||
const query: GetLeagueOwnerSummaryQuery = { ownerId, leagueId };
|
||||
return this.leagueService.getLeagueOwnerSummary(query);
|
||||
}
|
||||
|
||||
@Get(':leagueId/config')
|
||||
@ApiOperation({ summary: 'Get league full configuration' })
|
||||
@ApiResponse({ status: 200, description: 'League configuration form model', type: LeagueConfigFormModelDto })
|
||||
async getLeagueFullConfig(
|
||||
@Param('leagueId') leagueId: string,
|
||||
): Promise<LeagueConfigFormModelDto | null> {
|
||||
const query: GetLeagueAdminConfigQuery = { leagueId };
|
||||
return this.leagueService.getLeagueFullConfig(query);
|
||||
}
|
||||
|
||||
@Get(':leagueId/protests')
|
||||
@ApiOperation({ summary: 'Get protests for a league' })
|
||||
@ApiResponse({ status: 200, description: 'List of protests for the league', type: LeagueAdminProtestsViewModel })
|
||||
async getLeagueProtests(@Param('leagueId') leagueId: string): Promise<LeagueAdminProtestsViewModel> {
|
||||
const query: GetLeagueProtestsQuery = { leagueId };
|
||||
return this.leagueService.getLeagueProtests(query);
|
||||
}
|
||||
|
||||
@Get(':leagueId/seasons')
|
||||
@ApiOperation({ summary: 'Get seasons for a league' })
|
||||
@ApiResponse({ status: 200, description: 'List of seasons for the league', type: [LeagueSeasonSummaryViewModel] })
|
||||
async getLeagueSeasons(@Param('leagueId') leagueId: string): Promise<LeagueSeasonSummaryViewModel[]> {
|
||||
const query: GetLeagueSeasonsQuery = { leagueId };
|
||||
return this.leagueService.getLeagueSeasons(query);
|
||||
}
|
||||
|
||||
@Get(':leagueId/memberships')
|
||||
@ApiOperation({ summary: 'Get league memberships' })
|
||||
@ApiResponse({ status: 200, description: 'List of league members', type: LeagueMembershipsViewModel })
|
||||
async getLeagueMemberships(@Param('leagueId') leagueId: string): Promise<LeagueMembershipsViewModel> {
|
||||
return this.leagueService.getLeagueMemberships(leagueId);
|
||||
}
|
||||
|
||||
@Get(':leagueId/standings')
|
||||
@ApiOperation({ summary: 'Get league standings' })
|
||||
@ApiResponse({ status: 200, description: 'League standings', type: LeagueStandingsViewModel })
|
||||
async getLeagueStandings(@Param('leagueId') leagueId: string): Promise<LeagueStandingsViewModel> {
|
||||
return this.leagueService.getLeagueStandings(leagueId);
|
||||
}
|
||||
|
||||
@Get(':leagueId/schedule')
|
||||
@ApiOperation({ summary: 'Get league schedule' })
|
||||
@ApiResponse({ status: 200, description: 'League schedule', type: LeagueScheduleViewModel })
|
||||
async getLeagueSchedule(@Param('leagueId') leagueId: string): Promise<LeagueScheduleViewModel> {
|
||||
return this.leagueService.getLeagueSchedule(leagueId);
|
||||
}
|
||||
|
||||
@Get(':leagueId/stats')
|
||||
@ApiOperation({ summary: 'Get league stats' })
|
||||
@ApiResponse({ status: 200, description: 'League stats', type: LeagueStatsViewModel })
|
||||
async getLeagueStats(@Param('leagueId') leagueId: string): Promise<LeagueStatsViewModel> {
|
||||
return this.leagueService.getLeagueStats(leagueId);
|
||||
}
|
||||
|
||||
@Get(':leagueId/admin')
|
||||
@ApiOperation({ summary: 'Get league admin data' })
|
||||
@ApiResponse({ status: 200, description: 'League admin data', type: LeagueAdminViewModel })
|
||||
async getLeagueAdmin(@Param('leagueId') leagueId: string): Promise<LeagueAdminViewModel> {
|
||||
return this.leagueService.getLeagueAdmin(leagueId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new league' })
|
||||
@ApiBody({ type: CreateLeagueInput })
|
||||
@ApiResponse({ status: 201, description: 'League created successfully', type: CreateLeagueOutput })
|
||||
async createLeague(@Body() input: CreateLeagueInput): Promise<CreateLeagueOutput> {
|
||||
return this.leagueService.createLeague(input);
|
||||
}
|
||||
}
|
||||
11
apps/api/src/domain/league/LeagueModule.ts
Normal file
11
apps/api/src/domain/league/LeagueModule.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LeagueService } from './LeagueService';
|
||||
import { LeagueController } from './LeagueController';
|
||||
import { LeagueProviders } from './LeagueProviders';
|
||||
|
||||
@Module({
|
||||
controllers: [LeagueController],
|
||||
providers: LeagueProviders,
|
||||
exports: [LeagueService],
|
||||
})
|
||||
export class LeagueModule {}
|
||||
129
apps/api/src/domain/league/LeagueProviders.ts
Normal file
129
apps/api/src/domain/league/LeagueProviders.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { LeagueService } from './LeagueService';
|
||||
|
||||
// Import core interfaces
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
|
||||
// Import concrete in-memory implementations
|
||||
import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
|
||||
import { InMemoryLeagueMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository';
|
||||
import { InMemoryLeagueStandingsRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueStandingsRepository';
|
||||
import { InMemorySeasonRepository } from '@adapters/racing/persistence/inmemory/InMemorySeasonRepository';
|
||||
import { InMemoryLeagueScoringConfigRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueScoringConfigRepository';
|
||||
import { InMemoryGameRepository } from '@adapters/racing/persistence/inmemory/InMemoryGameRepository';
|
||||
import { InMemoryProtestRepository } from '@adapters/racing/persistence/inmemory/InMemoryProtestRepository';
|
||||
import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRepository';
|
||||
import { InMemoryDriverRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverRepository';
|
||||
import { InMemoryStandingRepository } from '@adapters/racing/persistence/inmemory/InMemoryStandingRepository';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
|
||||
// Import use cases
|
||||
import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase';
|
||||
import { GetLeagueStandingsUseCase } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase';
|
||||
import { GetLeagueFullConfigUseCase } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase';
|
||||
import { CreateLeagueWithSeasonAndScoringUseCase } from '@core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase';
|
||||
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
||||
import { GetTotalLeaguesUseCase } from '@core/racing/application/use-cases/GetTotalLeaguesUseCase';
|
||||
import { GetLeagueJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase';
|
||||
import { ApproveLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase';
|
||||
import { RejectLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/RejectLeagueJoinRequestUseCase';
|
||||
import { RemoveLeagueMemberUseCase } from '@core/racing/application/use-cases/RemoveLeagueMemberUseCase';
|
||||
import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase';
|
||||
import { GetLeagueOwnerSummaryUseCase } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase';
|
||||
import { GetLeagueProtestsUseCase } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase';
|
||||
import { GetLeagueSeasonsUseCase } from '@core/racing/application/use-cases/GetLeagueSeasonsUseCase';
|
||||
import { GetLeagueMembershipsUseCase } from '@core/racing/application/use-cases/GetLeagueMembershipsUseCase';
|
||||
import { GetLeagueScheduleUseCase } from '@core/racing/application/use-cases/GetLeagueScheduleUseCase';
|
||||
import { GetLeagueStatsUseCase } from '@core/racing/application/use-cases/GetLeagueStatsUseCase';
|
||||
import { GetLeagueAdminPermissionsUseCase } from '@core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase';
|
||||
|
||||
// Define injection tokens
|
||||
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
|
||||
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
|
||||
export const LEAGUE_STANDINGS_REPOSITORY_TOKEN = 'ILeagueStandingsRepository';
|
||||
export const STANDING_REPOSITORY_TOKEN = 'IStandingRepository';
|
||||
export const SEASON_REPOSITORY_TOKEN = 'ISeasonRepository';
|
||||
export const LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN = 'ILeagueScoringConfigRepository';
|
||||
export const GAME_REPOSITORY_TOKEN = 'IGameRepository';
|
||||
export const PROTEST_REPOSITORY_TOKEN = 'IProtestRepository';
|
||||
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
|
||||
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
|
||||
export const LOGGER_TOKEN = 'Logger'; // Already defined in AuthProviders, but good to have here too
|
||||
|
||||
export const LeagueProviders: Provider[] = [
|
||||
LeagueService, // Provide the service itself
|
||||
{
|
||||
provide: LEAGUE_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryLeagueRepository(logger), // Factory for InMemoryLeagueRepository
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryLeagueMembershipRepository(logger), // Factory for InMemoryLeagueMembershipRepository
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_STANDINGS_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryLeagueStandingsRepository(logger), // Factory for InMemoryLeagueStandingsRepository
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: STANDING_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryStandingRepository(logger), // Factory for InMemoryStandingRepository
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: SEASON_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemorySeasonRepository(logger), // Factory for InMemorySeasonRepository
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryLeagueScoringConfigRepository(logger), // Factory for InMemoryLeagueScoringConfigRepository
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GAME_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryGameRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: PROTEST_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryProtestRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: RACE_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryRaceRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: DRIVER_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryDriverRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
// Use cases
|
||||
GetAllLeaguesWithCapacityUseCase,
|
||||
GetLeagueStandingsUseCase,
|
||||
GetLeagueStatsUseCase,
|
||||
GetLeagueFullConfigUseCase,
|
||||
CreateLeagueWithSeasonAndScoringUseCase,
|
||||
GetRaceProtestsUseCase,
|
||||
GetTotalLeaguesUseCase,
|
||||
GetLeagueJoinRequestsUseCase,
|
||||
ApproveLeagueJoinRequestUseCase,
|
||||
RejectLeagueJoinRequestUseCase,
|
||||
RemoveLeagueMemberUseCase,
|
||||
UpdateLeagueMemberRoleUseCase,
|
||||
GetLeagueOwnerSummaryUseCase,
|
||||
GetLeagueProtestsUseCase,
|
||||
GetLeagueSeasonsUseCase,
|
||||
GetLeagueMembershipsUseCase,
|
||||
GetLeagueScheduleUseCase,
|
||||
GetLeagueStatsUseCase,
|
||||
GetLeagueAdminPermissionsUseCase,
|
||||
];
|
||||
170
apps/api/src/domain/league/LeagueService.test.ts
Normal file
170
apps/api/src/domain/league/LeagueService.test.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { LeagueService } from './LeagueService';
|
||||
import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase';
|
||||
import { GetLeagueStandingsUseCase } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase';
|
||||
import { GetLeagueStatsUseCase } from '@core/racing/application/use-cases/GetLeagueStatsUseCase';
|
||||
import { GetLeagueFullConfigUseCase } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase';
|
||||
import { CreateLeagueWithSeasonAndScoringUseCase } from '@core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase';
|
||||
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
||||
import { GetTotalLeaguesUseCase } from '@core/racing/application/use-cases/GetTotalLeaguesUseCase';
|
||||
import { GetLeagueJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase';
|
||||
import { ApproveLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase';
|
||||
import { RejectLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/RejectLeagueJoinRequestUseCase';
|
||||
import { RemoveLeagueMemberUseCase } from '@core/racing/application/use-cases/RemoveLeagueMemberUseCase';
|
||||
import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase';
|
||||
import { GetLeagueOwnerSummaryUseCase } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase';
|
||||
import { GetLeagueProtestsUseCase } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
|
||||
describe('LeagueService', () => {
|
||||
let service: LeagueService;
|
||||
let mockGetTotalLeaguesUseCase: jest.Mocked<GetTotalLeaguesUseCase>;
|
||||
let mockGetLeagueJoinRequestsUseCase: jest.Mocked<GetLeagueJoinRequestsUseCase>;
|
||||
let mockApproveLeagueJoinRequestUseCase: jest.Mocked<ApproveLeagueJoinRequestUseCase>;
|
||||
let mockLogger: jest.Mocked<Logger>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetTotalLeaguesUseCase = {
|
||||
execute: jest.fn(),
|
||||
} as any;
|
||||
mockGetLeagueJoinRequestsUseCase = {
|
||||
execute: jest.fn(),
|
||||
} as any;
|
||||
mockApproveLeagueJoinRequestUseCase = {
|
||||
execute: jest.fn(),
|
||||
} as any;
|
||||
mockLogger = {
|
||||
debug: jest.fn(),
|
||||
} as any;
|
||||
|
||||
service = new LeagueService(
|
||||
{} as any, // mockGetAllLeaguesWithCapacityUseCase
|
||||
{} as any, // mockGetLeagueStandingsUseCase
|
||||
{} as any, // mockGetLeagueStatsUseCase
|
||||
{} as any, // mockGetLeagueFullConfigUseCase
|
||||
{} as any, // mockCreateLeagueWithSeasonAndScoringUseCase
|
||||
{} as any, // mockGetRaceProtestsUseCase
|
||||
mockGetTotalLeaguesUseCase,
|
||||
mockGetLeagueJoinRequestsUseCase,
|
||||
mockApproveLeagueJoinRequestUseCase,
|
||||
{} as any, // mockRejectLeagueJoinRequestUseCase
|
||||
{} as any, // mockRemoveLeagueMemberUseCase
|
||||
{} as any, // mockUpdateLeagueMemberRoleUseCase
|
||||
{} as any, // mockGetLeagueOwnerSummaryUseCase
|
||||
{} as any, // mockGetLeagueProtestsUseCase
|
||||
{} as any, // mockGetLeagueSeasonsUseCase
|
||||
{} as any, // mockGetLeagueMembershipsUseCase
|
||||
{} as any, // mockGetLeagueScheduleUseCase
|
||||
{} as any, // mockGetLeagueAdminPermissionsUseCase
|
||||
mockLogger,
|
||||
);
|
||||
});
|
||||
|
||||
it('should get total leagues', async () => {
|
||||
mockGetTotalLeaguesUseCase.execute.mockImplementation(async (params, presenter) => {
|
||||
presenter.present({ totalLeagues: 5 });
|
||||
});
|
||||
|
||||
const result = await service.getTotalLeagues();
|
||||
|
||||
expect(result).toEqual({ totalLeagues: 5 });
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[LeagueService] Fetching total leagues count.');
|
||||
});
|
||||
|
||||
it('should get league join requests', async () => {
|
||||
mockGetLeagueJoinRequestsUseCase.execute.mockImplementation(async (params, presenter) => {
|
||||
presenter.present({
|
||||
joinRequests: [{ id: 'req-1', leagueId: 'league-1', driverId: 'driver-1', requestedAt: new Date(), message: 'msg' }],
|
||||
drivers: [{ id: 'driver-1', name: 'Driver 1' }],
|
||||
});
|
||||
});
|
||||
|
||||
const result = await service.getLeagueJoinRequests('league-1');
|
||||
|
||||
expect(result).toEqual([{
|
||||
id: 'req-1',
|
||||
leagueId: 'league-1',
|
||||
driverId: 'driver-1',
|
||||
requestedAt: expect.any(Date),
|
||||
message: 'msg',
|
||||
driver: { id: 'driver-1', name: 'Driver 1' },
|
||||
}]);
|
||||
});
|
||||
|
||||
it('should approve league join request', async () => {
|
||||
mockApproveLeagueJoinRequestUseCase.execute.mockImplementation(async (params, presenter) => {
|
||||
presenter.present({ success: true, message: 'Join request approved.' });
|
||||
});
|
||||
|
||||
const result = await service.approveLeagueJoinRequest({ leagueId: 'league-1', requestId: 'req-1' });
|
||||
|
||||
expect(result).toEqual({ success: true, message: 'Join request approved.' });
|
||||
});
|
||||
|
||||
it('should reject league join request', async () => {
|
||||
const mockRejectUseCase = {
|
||||
execute: jest.fn().mockImplementation(async (params, presenter) => {
|
||||
presenter.present({ success: true, message: 'Join request rejected.' });
|
||||
}),
|
||||
} as any;
|
||||
|
||||
service = new LeagueService(
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
mockRejectUseCase,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
mockLogger,
|
||||
);
|
||||
|
||||
const result = await service.rejectLeagueJoinRequest({ requestId: 'req-1', leagueId: 'league-1' });
|
||||
|
||||
expect(result).toEqual({ success: true, message: 'Join request rejected.' });
|
||||
});
|
||||
|
||||
it('should remove league member', async () => {
|
||||
const mockRemoveUseCase = {
|
||||
execute: jest.fn().mockImplementation(async (params, presenter) => {
|
||||
presenter.present({ success: true });
|
||||
}),
|
||||
} as any;
|
||||
|
||||
service = new LeagueService(
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
mockRemoveUseCase,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
{} as any,
|
||||
mockLogger,
|
||||
);
|
||||
|
||||
const result = await service.removeLeagueMember({ leagueId: 'league-1', performerDriverId: 'performer-1', targetDriverId: 'driver-1' });
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
});
|
||||
237
apps/api/src/domain/league/LeagueService.ts
Normal file
237
apps/api/src/domain/league/LeagueService.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
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';
|
||||
|
||||
// Core imports
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
|
||||
// Use cases
|
||||
import { GetAllLeaguesWithCapacityUseCase } from '@core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase';
|
||||
import { GetLeagueStandingsUseCase } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase';
|
||||
import { GetLeagueStatsUseCase } from '@core/racing/application/use-cases/GetLeagueStatsUseCase';
|
||||
import { GetLeagueFullConfigUseCase } from '@core/racing/application/use-cases/GetLeagueFullConfigUseCase';
|
||||
import { CreateLeagueWithSeasonAndScoringUseCase } from '@core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase';
|
||||
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
|
||||
import { GetTotalLeaguesUseCase } from '@core/racing/application/use-cases/GetTotalLeaguesUseCase';
|
||||
import { GetLeagueJoinRequestsUseCase } from '@core/racing/application/use-cases/GetLeagueJoinRequestsUseCase';
|
||||
import { ApproveLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase';
|
||||
import { RejectLeagueJoinRequestUseCase } from '@core/racing/application/use-cases/RejectLeagueJoinRequestUseCase';
|
||||
import { RemoveLeagueMemberUseCase } from '@core/racing/application/use-cases/RemoveLeagueMemberUseCase';
|
||||
import { UpdateLeagueMemberRoleUseCase } from '@core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase';
|
||||
import { GetLeagueOwnerSummaryUseCase } from '@core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase';
|
||||
import { GetLeagueProtestsUseCase } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase';
|
||||
import { GetLeagueSeasonsUseCase } from '@core/racing/application/use-cases/GetLeagueSeasonsUseCase';
|
||||
import { GetLeagueMembershipsUseCase } from '@core/racing/application/use-cases/GetLeagueMembershipsUseCase';
|
||||
import { GetLeagueScheduleUseCase } from '@core/racing/application/use-cases/GetLeagueScheduleUseCase';
|
||||
import { GetLeagueAdminPermissionsUseCase } from '@core/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(
|
||||
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> {
|
||||
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> {
|
||||
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[]> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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 instanceof Error ? error : new Error(String(error)));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueProtests(query: GetLeagueProtestsQuery): Promise<LeagueAdminProtestsViewModel> {
|
||||
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[]> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
666
apps/api/src/domain/league/dto/LeagueDto.ts
Normal file
666
apps/api/src/domain/league/dto/LeagueDto.ts
Normal file
@@ -0,0 +1,666 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNumber, IsBoolean, IsDate, IsOptional, IsEnum, IsArray, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { DriverDto } from '../../driver/dto/DriverDto';
|
||||
import { RaceDto } from '../../race/dto/RaceDto';
|
||||
|
||||
|
||||
export class LeagueSettingsDto {
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
maxDrivers?: number;
|
||||
// Add other league settings properties as needed
|
||||
}
|
||||
|
||||
export class LeagueWithCapacityViewModel {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
// ... other properties of LeagueWithCapacityViewModel
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ownerId: string;
|
||||
|
||||
@ApiProperty({ type: () => LeagueSettingsDto })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueSettingsDto)
|
||||
settings: LeagueSettingsDto;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
createdAt: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
usedSlots: number;
|
||||
|
||||
@ApiProperty({ type: () => Object, nullable: true }) // Using Object for generic social links
|
||||
@IsOptional()
|
||||
socialLinks?: {
|
||||
discordUrl?: string;
|
||||
youtubeUrl?: string;
|
||||
websiteUrl?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class AllLeaguesWithCapacityViewModel {
|
||||
@ApiProperty({ type: [LeagueWithCapacityViewModel] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => LeagueWithCapacityViewModel)
|
||||
leagues: LeagueWithCapacityViewModel[];
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export class LeagueStatsDto {
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
totalLeagues: number;
|
||||
}
|
||||
|
||||
export class ProtestDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
raceId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
protestingDriverId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
accusedDriverId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
submittedAt: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@ApiProperty({ enum: ['pending', 'accepted', 'rejected'] })
|
||||
@IsEnum(['pending', 'accepted', 'rejected'])
|
||||
status: 'pending' | 'accepted' | 'rejected';
|
||||
}
|
||||
|
||||
export class SeasonDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
seasonId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
startDate?: Date;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
endDate?: Date;
|
||||
|
||||
@ApiProperty({ enum: ['planned', 'active', 'completed'] })
|
||||
@IsEnum(['planned', 'active', 'completed'])
|
||||
status: 'planned' | 'active' | 'completed';
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
isPrimary: boolean;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
seasonGroupId?: string;
|
||||
}
|
||||
|
||||
export class LeagueJoinRequestViewModel {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
driverId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
requestedAt: Date;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
message?: string;
|
||||
|
||||
@ApiProperty({ type: () => DriverDto, required: false })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => DriverDto)
|
||||
driver?: DriverDto;
|
||||
}
|
||||
|
||||
export class GetLeagueJoinRequestsQuery {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class ApproveJoinRequestInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
requestId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class ApproveJoinRequestOutput {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsString()
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class RejectJoinRequestInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
requestId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class RejectJoinRequestOutput {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsString()
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class GetLeagueAdminPermissionsInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
performerDriverId: string;
|
||||
}
|
||||
|
||||
export class LeagueAdminPermissionsViewModel {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
canRemoveMember: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
canUpdateRoles: boolean;
|
||||
}
|
||||
|
||||
export class RemoveLeagueMemberInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
performerDriverId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
targetDriverId: string;
|
||||
}
|
||||
|
||||
export class RemoveLeagueMemberOutput {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export class UpdateLeagueMemberRoleInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
performerDriverId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
targetDriverId: string;
|
||||
|
||||
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
|
||||
@IsEnum(['owner', 'manager', 'member'])
|
||||
newRole: 'owner' | 'manager' | 'member';
|
||||
}
|
||||
|
||||
export class UpdateLeagueMemberRoleOutput {
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export class GetLeagueOwnerSummaryQuery {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ownerId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class LeagueOwnerSummaryViewModel {
|
||||
@ApiProperty({ type: () => DriverDto })
|
||||
@ValidateNested()
|
||||
@Type(() => DriverDto)
|
||||
driver: DriverDto;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
rating: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
rank: number | null;
|
||||
}
|
||||
|
||||
export class LeagueConfigFormModelBasicsDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@ApiProperty({ enum: ['public', 'private'] })
|
||||
@IsEnum(['public', 'private'])
|
||||
visibility: 'public' | 'private';
|
||||
}
|
||||
|
||||
export class LeagueConfigFormModelStructureDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsEnum(['solo', 'team'])
|
||||
mode: 'solo' | 'team';
|
||||
}
|
||||
|
||||
export class LeagueConfigFormModelScoringDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
type: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
points: number;
|
||||
}
|
||||
|
||||
export class LeagueConfigFormModelDropPolicyDto {
|
||||
@ApiProperty({ enum: ['none', 'worst_n'] })
|
||||
@IsEnum(['none', 'worst_n'])
|
||||
strategy: 'none' | 'worst_n';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
n?: number;
|
||||
}
|
||||
|
||||
export class LeagueConfigFormModelStewardingDto {
|
||||
@ApiProperty({ enum: ['single_steward', 'committee_vote'] })
|
||||
@IsEnum(['single_steward', 'committee_vote'])
|
||||
decisionMode: 'single_steward' | 'committee_vote';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
requiredVotes?: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
requireDefense: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
defenseTimeLimit: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
voteTimeLimit: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
protestDeadlineHours: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
stewardingClosesHours: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
notifyAccusedOnProtest: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
notifyOnVoteRequired: boolean;
|
||||
}
|
||||
|
||||
export class LeagueConfigFormModelTimingsDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
raceDayOfWeek: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
raceTimeHour: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
raceTimeMinute: number;
|
||||
}
|
||||
|
||||
export class LeagueConfigFormModelDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
|
||||
@ApiProperty({ type: LeagueConfigFormModelBasicsDto })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueConfigFormModelBasicsDto)
|
||||
basics: LeagueConfigFormModelBasicsDto;
|
||||
|
||||
@ApiProperty({ type: LeagueConfigFormModelStructureDto })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueConfigFormModelStructureDto)
|
||||
structure: LeagueConfigFormModelStructureDto;
|
||||
|
||||
@ApiProperty({ type: [Object] })
|
||||
@IsArray()
|
||||
championships: any[];
|
||||
|
||||
@ApiProperty({ type: LeagueConfigFormModelScoringDto })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueConfigFormModelScoringDto)
|
||||
scoring: LeagueConfigFormModelScoringDto;
|
||||
|
||||
@ApiProperty({ type: LeagueConfigFormModelDropPolicyDto })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueConfigFormModelDropPolicyDto)
|
||||
dropPolicy: LeagueConfigFormModelDropPolicyDto;
|
||||
|
||||
@ApiProperty({ type: LeagueConfigFormModelTimingsDto })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueConfigFormModelTimingsDto)
|
||||
timings: LeagueConfigFormModelTimingsDto;
|
||||
|
||||
@ApiProperty({ type: LeagueConfigFormModelStewardingDto })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueConfigFormModelStewardingDto)
|
||||
stewarding: LeagueConfigFormModelStewardingDto;
|
||||
}
|
||||
|
||||
export class GetLeagueAdminConfigQuery {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class GetLeagueAdminConfigOutput {
|
||||
@ApiProperty({ type: () => LeagueConfigFormModelDto, nullable: true })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueConfigFormModelDto)
|
||||
form: LeagueConfigFormModelDto | null;
|
||||
}
|
||||
|
||||
export class LeagueAdminConfigViewModel {
|
||||
@ApiProperty({ type: () => LeagueConfigFormModelDto, nullable: true })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueConfigFormModelDto)
|
||||
form: LeagueConfigFormModelDto | null;
|
||||
}
|
||||
|
||||
export class GetLeagueProtestsQuery {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class LeagueAdminProtestsViewModel {
|
||||
@ApiProperty({ type: [ProtestDto] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ProtestDto)
|
||||
protests: ProtestDto[];
|
||||
|
||||
@ApiProperty({ type: () => RaceDto })
|
||||
@ValidateNested()
|
||||
@Type(() => RaceDto)
|
||||
racesById: { [raceId: string]: RaceDto };
|
||||
|
||||
@ApiProperty({ type: () => DriverDto })
|
||||
@ValidateNested()
|
||||
@Type(() => DriverDto)
|
||||
driversById: { [driverId: string]: DriverDto };
|
||||
}
|
||||
|
||||
export class GetLeagueSeasonsQuery {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
export class LeagueSeasonSummaryViewModel {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
seasonId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
status: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
startDate?: Date;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
endDate?: Date;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
isPrimary: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
isParallelActive: boolean;
|
||||
}
|
||||
|
||||
export class LeagueAdminViewModel {
|
||||
@ApiProperty({ type: [LeagueJoinRequestViewModel] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => LeagueJoinRequestViewModel)
|
||||
joinRequests: LeagueJoinRequestViewModel[];
|
||||
|
||||
@ApiProperty({ type: () => LeagueOwnerSummaryViewModel, nullable: true })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueOwnerSummaryViewModel)
|
||||
ownerSummary: LeagueOwnerSummaryViewModel | null;
|
||||
|
||||
@ApiProperty({ type: () => LeagueAdminConfigViewModel })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueAdminConfigViewModel)
|
||||
config: LeagueAdminConfigViewModel;
|
||||
|
||||
@ApiProperty({ type: () => LeagueAdminProtestsViewModel })
|
||||
@ValidateNested()
|
||||
@Type(() => LeagueAdminProtestsViewModel)
|
||||
protests: LeagueAdminProtestsViewModel;
|
||||
|
||||
@ApiProperty({ type: [LeagueSeasonSummaryViewModel] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => LeagueSeasonSummaryViewModel)
|
||||
seasons: LeagueSeasonSummaryViewModel[];
|
||||
}
|
||||
|
||||
export class LeagueMemberDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
driverId: string;
|
||||
|
||||
@ApiProperty({ type: () => DriverDto })
|
||||
@ValidateNested()
|
||||
@Type(() => DriverDto)
|
||||
driver: DriverDto;
|
||||
|
||||
@ApiProperty({ enum: ['owner', 'manager', 'member'] })
|
||||
@IsEnum(['owner', 'manager', 'member'])
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
|
||||
@ApiProperty()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
joinedAt: Date;
|
||||
}
|
||||
|
||||
export class LeagueMembershipsViewModel {
|
||||
@ApiProperty({ type: [LeagueMemberDto] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => LeagueMemberDto)
|
||||
members: LeagueMemberDto[];
|
||||
}
|
||||
|
||||
export class LeagueStandingDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
driverId: string;
|
||||
|
||||
@ApiProperty({ type: () => DriverDto })
|
||||
@ValidateNested()
|
||||
@Type(() => DriverDto)
|
||||
driver: DriverDto;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
points: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
rank: number;
|
||||
}
|
||||
|
||||
export class LeagueStandingsViewModel {
|
||||
@ApiProperty({ type: [LeagueStandingDto] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => LeagueStandingDto)
|
||||
standings: LeagueStandingDto[];
|
||||
}
|
||||
|
||||
export class LeagueScheduleViewModel {
|
||||
@ApiProperty({ type: [RaceDto] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => RaceDto)
|
||||
races: RaceDto[];
|
||||
}
|
||||
|
||||
export class LeagueStatsViewModel {
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
totalMembers: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
totalRaces: number;
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
averageRating: number;
|
||||
}
|
||||
|
||||
export class CreateLeagueInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@ApiProperty({ enum: ['public', 'private'] })
|
||||
@IsEnum(['public', 'private'])
|
||||
visibility: 'public' | 'private';
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
ownerId: string;
|
||||
}
|
||||
|
||||
export class CreateLeagueOutput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
leagueId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { IAllLeaguesWithCapacityPresenter, AllLeaguesWithCapacityResultDTO, AllLeaguesWithCapacityViewModel } from '@core/racing/application/presenters/IAllLeaguesWithCapacityPresenter';
|
||||
|
||||
export class AllLeaguesWithCapacityPresenter implements IAllLeaguesWithCapacityPresenter {
|
||||
private result: AllLeaguesWithCapacityViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: AllLeaguesWithCapacityResultDTO) {
|
||||
const leagues = dto.leagues.map(league => ({
|
||||
id: league.id,
|
||||
name: league.name,
|
||||
description: league.description,
|
||||
ownerId: league.ownerId,
|
||||
settings: { maxDrivers: league.settings.maxDrivers || 0 },
|
||||
createdAt: league.createdAt.toISOString(),
|
||||
usedSlots: dto.memberCounts.get(league.id) || 0,
|
||||
socialLinks: league.socialLinks,
|
||||
}));
|
||||
this.result = {
|
||||
leagues,
|
||||
totalCount: leagues.length,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): AllLeaguesWithCapacityViewModel | null {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IApproveLeagueJoinRequestPresenter, ApproveLeagueJoinRequestResultDTO, ApproveLeagueJoinRequestViewModel } from '@core/racing/application/presenters/IApproveLeagueJoinRequestPresenter';
|
||||
|
||||
export class ApproveLeagueJoinRequestPresenter implements IApproveLeagueJoinRequestPresenter {
|
||||
private result: ApproveLeagueJoinRequestViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: ApproveLeagueJoinRequestResultDTO) {
|
||||
this.result = dto;
|
||||
}
|
||||
|
||||
getViewModel(): ApproveLeagueJoinRequestViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { IGetLeagueAdminPermissionsPresenter, GetLeagueAdminPermissionsResultDTO, GetLeagueAdminPermissionsViewModel } from '@core/racing/application/presenters/IGetLeagueAdminPermissionsPresenter';
|
||||
|
||||
export class GetLeagueAdminPermissionsPresenter implements IGetLeagueAdminPermissionsPresenter {
|
||||
private result: GetLeagueAdminPermissionsViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: GetLeagueAdminPermissionsResultDTO) {
|
||||
this.result = dto;
|
||||
}
|
||||
|
||||
getViewModel(): GetLeagueAdminPermissionsViewModel | null {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { IGetLeagueMembershipsPresenter, GetLeagueMembershipsResultDTO, GetLeagueMembershipsViewModel } from '@core/racing/application/presenters/IGetLeagueMembershipsPresenter';
|
||||
import { LeagueMembershipsViewModel } from '../dto/LeagueDto';
|
||||
|
||||
export class GetLeagueMembershipsPresenter implements IGetLeagueMembershipsPresenter {
|
||||
private result: GetLeagueMembershipsViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: GetLeagueMembershipsResultDTO) {
|
||||
const driverMap = new Map(dto.drivers.map(d => [d.id, d]));
|
||||
const members = dto.memberships.map(membership => ({
|
||||
driverId: membership.driverId,
|
||||
driver: driverMap.get(membership.driverId)!,
|
||||
role: this.mapRole(membership.role) as 'owner' | 'manager' | 'member',
|
||||
joinedAt: membership.joinedAt,
|
||||
}));
|
||||
this.result = { memberships: { members } };
|
||||
}
|
||||
|
||||
private mapRole(role: string): 'owner' | 'manager' | 'member' {
|
||||
switch (role) {
|
||||
case 'owner':
|
||||
return 'owner';
|
||||
case 'admin':
|
||||
return 'manager'; // Map admin to manager for API
|
||||
case 'steward':
|
||||
return 'member'; // Map steward to member for API
|
||||
case 'member':
|
||||
return 'member';
|
||||
default:
|
||||
return 'member';
|
||||
}
|
||||
}
|
||||
|
||||
getViewModel(): GetLeagueMembershipsViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
|
||||
// API-specific method
|
||||
get apiViewModel(): LeagueMembershipsViewModel | null {
|
||||
if (!this.result?.memberships) return null;
|
||||
return this.result.memberships as LeagueMembershipsViewModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IGetLeagueOwnerSummaryPresenter, GetLeagueOwnerSummaryResultDTO, GetLeagueOwnerSummaryViewModel } from '@core/racing/application/presenters/IGetLeagueOwnerSummaryPresenter';
|
||||
|
||||
export class GetLeagueOwnerSummaryPresenter implements IGetLeagueOwnerSummaryPresenter {
|
||||
private result: GetLeagueOwnerSummaryViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: GetLeagueOwnerSummaryResultDTO) {
|
||||
this.result = { summary: dto.summary };
|
||||
}
|
||||
|
||||
getViewModel(): GetLeagueOwnerSummaryViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { IGetLeagueProtestsPresenter, GetLeagueProtestsResultDTO, GetLeagueProtestsViewModel } from '@core/racing/application/presenters/IGetLeagueProtestsPresenter';
|
||||
|
||||
export class GetLeagueProtestsPresenter implements IGetLeagueProtestsPresenter {
|
||||
private result: GetLeagueProtestsViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: GetLeagueProtestsResultDTO) {
|
||||
const racesById = {};
|
||||
dto.races.forEach(race => {
|
||||
racesById[race.id] = race;
|
||||
});
|
||||
const driversById = {};
|
||||
dto.drivers.forEach(driver => {
|
||||
driversById[driver.id] = driver;
|
||||
});
|
||||
this.result = {
|
||||
protests: dto.protests,
|
||||
racesById,
|
||||
driversById,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): GetLeagueProtestsViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { IGetLeagueSeasonsPresenter, GetLeagueSeasonsResultDTO, GetLeagueSeasonsViewModel } from '@core/racing/application/presenters/IGetLeagueSeasonsPresenter';
|
||||
|
||||
export class GetLeagueSeasonsPresenter implements IGetLeagueSeasonsPresenter {
|
||||
private result: GetLeagueSeasonsViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: GetLeagueSeasonsResultDTO) {
|
||||
const seasons = dto.seasons.map(season => ({
|
||||
seasonId: season.id,
|
||||
name: season.name,
|
||||
status: season.status,
|
||||
startDate: season.startDate,
|
||||
endDate: season.endDate,
|
||||
isPrimary: season.isPrimary,
|
||||
isParallelActive: season.isParallelActive,
|
||||
}));
|
||||
this.result = { seasons };
|
||||
}
|
||||
|
||||
getViewModel(): GetLeagueSeasonsViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { LeagueAdminViewModel } from '../dto/LeagueDto';
|
||||
|
||||
export class LeagueAdminPresenter {
|
||||
private result: LeagueAdminViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(data: {
|
||||
joinRequests: any[];
|
||||
ownerSummary: any;
|
||||
config: any;
|
||||
protests: any;
|
||||
seasons: any[];
|
||||
}) {
|
||||
this.result = {
|
||||
joinRequests: data.joinRequests,
|
||||
ownerSummary: data.ownerSummary,
|
||||
config: { form: data.config },
|
||||
protests: data.protests,
|
||||
seasons: data.seasons,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): LeagueAdminViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
109
apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts
Normal file
109
apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { ILeagueFullConfigPresenter, LeagueFullConfigData, LeagueConfigFormViewModel } from '@core/racing/application/presenters/ILeagueFullConfigPresenter';
|
||||
import { LeagueConfigFormModelDto } from '../dto/LeagueDto';
|
||||
|
||||
export class LeagueConfigPresenter implements ILeagueFullConfigPresenter {
|
||||
private result: LeagueConfigFormViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: LeagueFullConfigData) {
|
||||
// Map from LeagueFullConfigData to LeagueConfigFormViewModel
|
||||
const league = dto.league;
|
||||
const settings = league.settings;
|
||||
const stewarding = settings.stewarding;
|
||||
|
||||
this.result = {
|
||||
leagueId: league.id,
|
||||
basics: {
|
||||
name: league.name,
|
||||
description: league.description,
|
||||
visibility: 'public', // TODO: Map visibility from league
|
||||
gameId: 'iracing', // TODO: Map from game
|
||||
},
|
||||
structure: {
|
||||
mode: 'solo', // TODO: Map from league settings
|
||||
maxDrivers: settings.maxDrivers || 32,
|
||||
multiClassEnabled: false, // TODO: Map
|
||||
},
|
||||
championships: {
|
||||
enableDriverChampionship: true, // TODO: Map
|
||||
enableTeamChampionship: false,
|
||||
enableNationsChampionship: false,
|
||||
enableTrophyChampionship: false,
|
||||
},
|
||||
scoring: {
|
||||
customScoringEnabled: false, // TODO: Map
|
||||
},
|
||||
dropPolicy: {
|
||||
strategy: 'none', // TODO: Map
|
||||
},
|
||||
timings: {
|
||||
practiceMinutes: 30, // TODO: Map
|
||||
qualifyingMinutes: 15,
|
||||
mainRaceMinutes: 60,
|
||||
sessionCount: 1,
|
||||
roundsPlanned: 10, // TODO: Map
|
||||
},
|
||||
stewarding: {
|
||||
decisionMode: stewarding?.decisionMode || 'admin_only',
|
||||
requireDefense: stewarding?.requireDefense || false,
|
||||
defenseTimeLimit: stewarding?.defenseTimeLimit || 48,
|
||||
voteTimeLimit: stewarding?.voteTimeLimit || 72,
|
||||
protestDeadlineHours: stewarding?.protestDeadlineHours || 48,
|
||||
stewardingClosesHours: stewarding?.stewardingClosesHours || 168,
|
||||
notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest || true,
|
||||
notifyOnVoteRequired: stewarding?.notifyOnVoteRequired || true,
|
||||
requiredVotes: stewarding?.requiredVotes,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): LeagueConfigFormViewModel | null {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
// API-specific method to get the DTO
|
||||
get viewModel(): LeagueConfigFormModelDto | null {
|
||||
if (!this.result) return null;
|
||||
|
||||
// Map from LeagueConfigFormViewModel to LeagueConfigFormModelDto
|
||||
return {
|
||||
leagueId: this.result.leagueId,
|
||||
basics: {
|
||||
name: this.result.basics.name,
|
||||
description: this.result.basics.description,
|
||||
visibility: this.result.basics.visibility as 'public' | 'private',
|
||||
},
|
||||
structure: {
|
||||
mode: this.result.structure.mode as 'solo' | 'team',
|
||||
},
|
||||
championships: [], // TODO: Map championships
|
||||
scoring: {
|
||||
type: 'standard', // TODO: Map scoring type
|
||||
points: 25, // TODO: Map points
|
||||
},
|
||||
dropPolicy: {
|
||||
strategy: this.result.dropPolicy.strategy as 'none' | 'worst_n',
|
||||
n: this.result.dropPolicy.n,
|
||||
},
|
||||
timings: {
|
||||
raceDayOfWeek: 'sunday', // TODO: Map from timings
|
||||
raceTimeHour: 20,
|
||||
raceTimeMinute: 0,
|
||||
},
|
||||
stewarding: {
|
||||
decisionMode: this.result.stewarding.decisionMode === 'steward_vote' ? 'committee_vote' : 'single_steward',
|
||||
requireDefense: this.result.stewarding.requireDefense,
|
||||
defenseTimeLimit: this.result.stewarding.defenseTimeLimit,
|
||||
voteTimeLimit: this.result.stewarding.voteTimeLimit,
|
||||
protestDeadlineHours: this.result.stewarding.protestDeadlineHours,
|
||||
stewardingClosesHours: this.result.stewarding.stewardingClosesHours,
|
||||
notifyAccusedOnProtest: this.result.stewarding.notifyAccusedOnProtest,
|
||||
notifyOnVoteRequired: this.result.stewarding.notifyOnVoteRequired,
|
||||
requiredVotes: this.result.stewarding.requiredVotes,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { IGetLeagueJoinRequestsPresenter, GetLeagueJoinRequestsResultDTO, GetLeagueJoinRequestsViewModel } from '@core/racing/application/presenters/IGetLeagueJoinRequestsPresenter';
|
||||
|
||||
export class LeagueJoinRequestsPresenter implements IGetLeagueJoinRequestsPresenter {
|
||||
private result: GetLeagueJoinRequestsViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: GetLeagueJoinRequestsResultDTO) {
|
||||
const driverMap = new Map(dto.drivers.map(d => [d.id, d]));
|
||||
const joinRequests = dto.joinRequests.map(request => ({
|
||||
id: request.id,
|
||||
leagueId: request.leagueId,
|
||||
driverId: request.driverId,
|
||||
requestedAt: request.requestedAt,
|
||||
message: request.message,
|
||||
driver: driverMap.get(request.driverId) || null,
|
||||
}));
|
||||
this.result = { joinRequests };
|
||||
}
|
||||
|
||||
getViewModel(): GetLeagueJoinRequestsViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { IGetLeagueSchedulePresenter, GetLeagueScheduleResultDTO, LeagueScheduleViewModel } from '@core/racing/application/presenters/IGetLeagueSchedulePresenter';
|
||||
|
||||
export class LeagueSchedulePresenter implements IGetLeagueSchedulePresenter {
|
||||
private result: LeagueScheduleViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: GetLeagueScheduleResultDTO) {
|
||||
this.result = {
|
||||
races: dto.races.map(race => ({
|
||||
id: race.id,
|
||||
name: race.name,
|
||||
date: race.scheduledAt.toISOString(),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): LeagueScheduleViewModel | null {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ILeagueStandingsPresenter, LeagueStandingsResultDTO, LeagueStandingsViewModel } from '@core/racing/application/presenters/ILeagueStandingsPresenter';
|
||||
|
||||
export class LeagueStandingsPresenter implements ILeagueStandingsPresenter {
|
||||
private result: LeagueStandingsViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: LeagueStandingsResultDTO) {
|
||||
const driverMap = new Map(dto.drivers.map(d => [d.id, { id: d.id, name: d.name }]));
|
||||
const standings = dto.standings
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.map(standing => ({
|
||||
driverId: standing.driverId,
|
||||
driver: driverMap.get(standing.driverId)!,
|
||||
points: standing.points,
|
||||
rank: standing.position,
|
||||
}));
|
||||
this.result = { standings };
|
||||
}
|
||||
|
||||
getViewModel(): LeagueStandingsViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ILeagueStatsPresenter, LeagueStatsResultDTO, LeagueStatsViewModel } from '@core/racing/application/presenters/ILeagueStatsPresenter';
|
||||
|
||||
export class LeagueStatsPresenter implements ILeagueStatsPresenter {
|
||||
private result: LeagueStatsViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: LeagueStatsResultDTO) {
|
||||
this.result = dto;
|
||||
}
|
||||
|
||||
getViewModel(): LeagueStatsViewModel | null {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IRejectLeagueJoinRequestPresenter, RejectLeagueJoinRequestResultDTO, RejectLeagueJoinRequestViewModel } from '@core/racing/application/presenters/IRejectLeagueJoinRequestPresenter';
|
||||
|
||||
export class RejectLeagueJoinRequestPresenter implements IRejectLeagueJoinRequestPresenter {
|
||||
private result: RejectLeagueJoinRequestViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: RejectLeagueJoinRequestResultDTO) {
|
||||
this.result = dto;
|
||||
}
|
||||
|
||||
getViewModel(): RejectLeagueJoinRequestViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IRemoveLeagueMemberPresenter, RemoveLeagueMemberResultDTO, RemoveLeagueMemberViewModel } from '@core/racing/application/presenters/IRemoveLeagueMemberPresenter';
|
||||
|
||||
export class RemoveLeagueMemberPresenter implements IRemoveLeagueMemberPresenter {
|
||||
private result: RemoveLeagueMemberViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: RemoveLeagueMemberResultDTO) {
|
||||
this.result = dto;
|
||||
}
|
||||
|
||||
getViewModel(): RemoveLeagueMemberViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IGetTotalLeaguesPresenter, GetTotalLeaguesResultDTO, GetTotalLeaguesViewModel } from '@core/racing/application/presenters/IGetTotalLeaguesPresenter';
|
||||
import { LeagueStatsDto } from '../dto/LeagueDto';
|
||||
|
||||
export class TotalLeaguesPresenter implements IGetTotalLeaguesPresenter {
|
||||
private result: LeagueStatsDto | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: GetTotalLeaguesResultDTO) {
|
||||
this.result = {
|
||||
totalLeagues: dto.totalLeagues,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel(): LeagueStatsDto | null {
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IUpdateLeagueMemberRolePresenter, UpdateLeagueMemberRoleResultDTO, UpdateLeagueMemberRoleViewModel } from '@core/racing/application/presenters/IUpdateLeagueMemberRolePresenter';
|
||||
|
||||
export class UpdateLeagueMemberRolePresenter implements IUpdateLeagueMemberRolePresenter {
|
||||
private result: UpdateLeagueMemberRoleViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: UpdateLeagueMemberRoleResultDTO) {
|
||||
this.result = dto;
|
||||
}
|
||||
|
||||
getViewModel(): UpdateLeagueMemberRoleViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user