website refactor

This commit is contained in:
2026-01-20 22:31:14 +01:00
parent 51288234f4
commit 7cbec00474
52 changed files with 577 additions and 146 deletions

View File

@@ -5,6 +5,7 @@ import { TeamService } from './TeamService';
import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO';
import { CreateTeamOutputDTO } from './dtos/CreateTeamOutputDTO';
import { GetAllTeamsOutputDTO } from './dtos/GetAllTeamsOutputDTO';
import { GetTeamsLeaderboardOutputDTO } from './dtos/GetTeamsLeaderboardOutputDTO';
import { GetDriverTeamOutputDTO } from './dtos/GetDriverTeamOutputDTO';
import { GetTeamDetailsOutputDTO } from './dtos/GetTeamDetailsOutputDTO';
import { GetTeamJoinRequestsOutputDTO } from './dtos/GetTeamJoinRequestsOutputDTO';
@@ -32,6 +33,14 @@ export class TeamController {
return await this.teamService.getAll();
}
@Public()
@Get('leaderboard')
@ApiOperation({ summary: 'Get teams leaderboard' })
@ApiResponse({ status: 200, description: 'Teams leaderboard data', type: GetTeamsLeaderboardOutputDTO })
async getLeaderboard(): Promise<GetTeamsLeaderboardOutputDTO> {
return await this.teamService.getLeaderboard();
}
@Public()
@Get(':teamId')
@ApiOperation({ summary: 'Get team details' })

View File

@@ -18,6 +18,7 @@ import {
TEAM_REPOSITORY_TOKEN,
TEAM_STATS_REPOSITORY_TOKEN,
UPDATE_TEAM_USE_CASE_TOKEN,
GET_TEAMS_LEADERBOARD_USE_CASE_TOKEN,
} from './TeamTokens';
export {
@@ -34,6 +35,7 @@ import type { DriverRepository } from '@core/racing/domain/repositories/DriverRe
import type { TeamMembershipRepository } from '@core/racing/domain/repositories/TeamMembershipRepository';
import type { TeamRepository } from '@core/racing/domain/repositories/TeamRepository';
import type { TeamStatsRepository } from '@core/racing/domain/repositories/TeamStatsRepository';
import type { DriverStatsRepository } from '@core/racing/domain/repositories/DriverStatsRepository';
import type { Logger } from '@core/shared/domain/Logger';
// Import concrete implementations
@@ -52,9 +54,11 @@ import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/Get
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase';
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
import { GetTeamsLeaderboardUseCase } from '@core/racing/application/use-cases/GetTeamsLeaderboardUseCase';
// Import presenters
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
import { TeamsLeaderboardPresenter } from './presenters/TeamsLeaderboardPresenter';
export const TeamProviders: Provider[] = [
{
@@ -100,6 +104,10 @@ export const TeamProviders: Provider[] = [
},
inject: [MEDIA_RESOLVER_TOKEN],
},
{
provide: TeamsLeaderboardPresenter,
useClass: TeamsLeaderboardPresenter,
},
// Use Cases
{
provide: GET_ALL_TEAMS_USE_CASE_TOKEN,
@@ -155,4 +163,13 @@ export const TeamProviders: Provider[] = [
new JoinTeamUseCase(teamRepo, membershipRepo, logger),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GET_TEAMS_LEADERBOARD_USE_CASE_TOKEN,
useFactory: (teamRepo: TeamRepository, membershipRepo: TeamMembershipRepository, driverStatsRepo: DriverStatsRepository, logger: Logger) =>
new GetTeamsLeaderboardUseCase(teamRepo, membershipRepo, (driverId) => {
const stats = driverStatsRepo.getDriverStatsSync?.(driverId);
return stats ? { rating: stats.rating, wins: stats.wins, totalRaces: stats.totalRaces } : null;
}, logger),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, 'IDriverStatsRepository', LOGGER_TOKEN],
},
];

View File

@@ -134,6 +134,8 @@ describe('TeamService', () => {
new GetDriverTeamUseCase(teamRepository as never, membershipRepository as never, logger),
new GetTeamMembershipUseCase(membershipRepository as never, logger),
{ execute: vi.fn() } as never, // joinTeamUseCase
{ execute: vi.fn() } as never, // getTeamsLeaderboardUseCase
{ reset: vi.fn(), present: vi.fn(), getViewModel: vi.fn() } as never, // teamsLeaderboardPresenter
logger
);
});

View File

@@ -7,6 +7,7 @@ import { GetTeamDetailsOutputDTO } from './dtos/GetTeamDetailsOutputDTO';
import { GetTeamJoinRequestsOutputDTO } from './dtos/GetTeamJoinRequestsOutputDTO';
import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO';
import { GetTeamMembersOutputDTO } from './dtos/GetTeamMembersOutputDTO';
import { GetTeamsLeaderboardOutputDTO } from './dtos/GetTeamsLeaderboardOutputDTO';
import { UpdateTeamInputDTO } from './dtos/UpdateTeamInputDTO';
import { UpdateTeamOutputDTO } from './dtos/UpdateTeamOutputDTO';
@@ -21,6 +22,7 @@ import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTea
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
import { GetTeamsLeaderboardUseCase } from '@core/racing/application/use-cases/GetTeamsLeaderboardUseCase';
import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase';
import { UpdateTeamInput, UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
@@ -32,12 +34,15 @@ import {
GET_TEAM_DETAILS_USE_CASE_TOKEN,
GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN,
GET_TEAM_MEMBERS_USE_CASE_TOKEN,
GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN,
GET_TEAM_MEMBERS_USE_CASE_TOKEN as GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN,
GET_TEAMS_LEADERBOARD_USE_CASE_TOKEN,
JOIN_TEAM_USE_CASE_TOKEN,
LOGGER_TOKEN,
UPDATE_TEAM_USE_CASE_TOKEN,
} from './TeamTokens';
import { TeamsLeaderboardPresenter } from './presenters/TeamsLeaderboardPresenter';
@Injectable()
export class TeamService {
constructor(
@@ -50,6 +55,8 @@ export class TeamService {
@Inject(GET_DRIVER_TEAM_USE_CASE_TOKEN) private readonly getDriverTeamUseCase: GetDriverTeamUseCase,
@Inject(GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN) private readonly getTeamMembershipUseCase: GetTeamMembershipUseCase,
@Inject(JOIN_TEAM_USE_CASE_TOKEN) private readonly joinTeamUseCase: JoinTeamUseCase,
@Inject(GET_TEAMS_LEADERBOARD_USE_CASE_TOKEN) private readonly getTeamsLeaderboardUseCase: GetTeamsLeaderboardUseCase,
@Inject(TeamsLeaderboardPresenter) private readonly teamsLeaderboardPresenter: TeamsLeaderboardPresenter,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
@@ -85,6 +92,20 @@ export class TeamService {
};
}
async getLeaderboard(): Promise<GetTeamsLeaderboardOutputDTO> {
this.logger.debug('[TeamService] Fetching teams leaderboard.');
const result = await this.getTeamsLeaderboardUseCase.execute({ leagueId: 'global' });
if (result.isErr()) {
this.logger.error('Error fetching teams leaderboard', new Error(result.unwrapErr().details?.message || 'Unknown error'));
return { teams: [], recruitingCount: 0, groupsBySkillLevel: { beginner: [], intermediate: [], advanced: [], pro: [] }, topTeams: [] };
}
this.teamsLeaderboardPresenter.reset();
this.teamsLeaderboardPresenter.present(result.unwrap());
return this.teamsLeaderboardPresenter.getViewModel()!;
}
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);

View File

@@ -16,4 +16,5 @@ export const CREATE_TEAM_USE_CASE_TOKEN = Symbol('CREATE_TEAM_USE_CASE_TOKEN');
export const UPDATE_TEAM_USE_CASE_TOKEN = Symbol('UPDATE_TEAM_USE_CASE_TOKEN');
export const GET_DRIVER_TEAM_USE_CASE_TOKEN = Symbol('GET_DRIVER_TEAM_USE_CASE_TOKEN');
export const GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN = Symbol('GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN');
export const JOIN_TEAM_USE_CASE_TOKEN = Symbol('JOIN_TEAM_USE_CASE_TOKEN');
export const JOIN_TEAM_USE_CASE_TOKEN = Symbol('JOIN_TEAM_USE_CASE_TOKEN');
export const GET_TEAMS_LEADERBOARD_USE_CASE_TOKEN = Symbol('GET_TEAMS_LEADERBOARD_USE_CASE_TOKEN');

View File

@@ -9,6 +9,12 @@ export class TeamLeaderboardItemDTO {
@ApiProperty()
name!: string;
@ApiProperty()
tag!: string;
@ApiProperty({ nullable: true })
logoUrl!: string | null;
@ApiProperty()
memberCount!: number;

View File

@@ -14,6 +14,8 @@ export class TeamsLeaderboardPresenter implements UseCaseOutputPort<GetTeamsLead
teams: result.items.map(item => ({
id: item.team.id,
name: item.team.name.toString(),
tag: item.team.tag.toString(),
logoUrl: null, // MediaResolver would be needed here
memberCount: item.memberCount,
rating: item.rating,
totalWins: item.totalWins,
@@ -28,6 +30,8 @@ export class TeamsLeaderboardPresenter implements UseCaseOutputPort<GetTeamsLead
beginner: result.groupsBySkillLevel.beginner.map(item => ({
id: item.team.id,
name: item.team.name.toString(),
tag: item.team.tag.toString(),
logoUrl: null,
memberCount: item.memberCount,
rating: item.rating,
totalWins: item.totalWins,
@@ -40,6 +44,8 @@ export class TeamsLeaderboardPresenter implements UseCaseOutputPort<GetTeamsLead
intermediate: result.groupsBySkillLevel.intermediate.map(item => ({
id: item.team.id,
name: item.team.name.toString(),
tag: item.team.tag.toString(),
logoUrl: null,
memberCount: item.memberCount,
rating: item.rating,
totalWins: item.totalWins,
@@ -52,6 +58,8 @@ export class TeamsLeaderboardPresenter implements UseCaseOutputPort<GetTeamsLead
advanced: result.groupsBySkillLevel.advanced.map(item => ({
id: item.team.id,
name: item.team.name.toString(),
tag: item.team.tag.toString(),
logoUrl: null,
memberCount: item.memberCount,
rating: item.rating,
totalWins: item.totalWins,
@@ -64,6 +72,8 @@ export class TeamsLeaderboardPresenter implements UseCaseOutputPort<GetTeamsLead
pro: result.groupsBySkillLevel.pro.map(item => ({
id: item.team.id,
name: item.team.name.toString(),
tag: item.team.tag.toString(),
logoUrl: null,
memberCount: item.memberCount,
rating: item.rating,
totalWins: item.totalWins,
@@ -77,6 +87,8 @@ export class TeamsLeaderboardPresenter implements UseCaseOutputPort<GetTeamsLead
topTeams: result.topItems.map(item => ({
id: item.team.id,
name: item.team.name.toString(),
tag: item.team.tag.toString(),
logoUrl: null,
memberCount: item.memberCount,
rating: item.rating,
totalWins: item.totalWins,