refactor core presenters

This commit is contained in:
2025-12-19 19:42:19 +01:00
parent 8116fe888f
commit 94fc538f44
228 changed files with 2817 additions and 3097 deletions

View File

@@ -0,0 +1,58 @@
import { ApiProperty } from '@nestjs/swagger';
export type SkillLevel = 'beginner' | 'intermediate' | 'advanced' | 'pro';
class TeamLeaderboardItemDTO {
@ApiProperty()
id: string;
@ApiProperty()
name: string;
@ApiProperty()
memberCount: number;
@ApiProperty({ nullable: true })
rating: number | null;
@ApiProperty()
totalWins: number;
@ApiProperty()
totalRaces: number;
@ApiProperty({ enum: ['beginner', 'intermediate', 'advanced', 'pro'] })
performanceLevel: SkillLevel;
@ApiProperty()
isRecruiting: boolean;
@ApiProperty()
createdAt: string;
@ApiProperty({ required: false })
description?: string;
@ApiProperty({ enum: ['endurance', 'sprint', 'mixed'], required: false })
specialization?: 'endurance' | 'sprint' | 'mixed';
@ApiProperty({ required: false })
region?: string;
@ApiProperty({ type: [String], required: false })
languages?: string[];
}
export class GetTeamsLeaderboardOutputDTO {
@ApiProperty({ type: [TeamLeaderboardItemDTO] })
teams: TeamLeaderboardItemDTO[];
@ApiProperty()
recruitingCount: number;
@ApiProperty({ type: 'object', additionalProperties: { type: 'array', items: { $ref: '#/components/schemas/TeamLeaderboardItemDTO' } } })
groupsBySkillLevel: Record<SkillLevel, TeamLeaderboardItemDTO[]>;
@ApiProperty({ type: [TeamLeaderboardItemDTO] })
topTeams: TeamLeaderboardItemDTO[];
}

View File

@@ -1,33 +1,33 @@
import { IAllTeamsPresenter, AllTeamsResultDTO, AllTeamsViewModel, TeamListItemViewModel } from '@core/racing/application/presenters/IAllTeamsPresenter';
import { GetAllTeamsOutputPort } from '@core/racing/application/ports/output/GetAllTeamsOutputPort';
import { GetAllTeamsOutputDTO } from '../dtos/GetAllTeamsOutputDTO';
export class AllTeamsPresenter implements IAllTeamsPresenter {
private result: AllTeamsViewModel | null = null;
export class AllTeamsPresenter {
private result: GetAllTeamsOutputDTO | null = null;
reset() {
this.result = null;
}
present(dto: AllTeamsResultDTO) {
const teams: TeamListItemViewModel[] = dto.teams.map(team => ({
id: team.id,
name: team.name,
tag: team.tag,
description: team.description,
memberCount: team.memberCount,
leagues: team.leagues || [],
}));
async present(output: GetAllTeamsOutputPort) {
this.result = {
teams,
totalCount: teams.length,
teams: output.teams.map(team => ({
id: team.id,
name: team.name,
tag: team.tag,
description: team.description,
memberCount: team.memberCount,
leagues: team.leagues || [],
// Note: specialization, region, languages not available in output port
})),
totalCount: output.totalCount || output.teams.length,
};
}
getViewModel(): AllTeamsViewModel | null {
getViewModel(): GetAllTeamsOutputDTO | null {
return this.result;
}
get viewModel(): AllTeamsViewModel {
get viewModel(): GetAllTeamsOutputDTO {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}

View File

@@ -1,41 +1,38 @@
import { IDriverTeamPresenter, DriverTeamResultDTO, DriverTeamViewModel } from '@core/racing/application/presenters/IDriverTeamPresenter';
import { DriverTeamOutputPort } from '@core/racing/application/ports/output/DriverTeamOutputPort';
import { GetDriverTeamOutputDTO } from '../dtos/GetDriverTeamOutputDTO';
export class DriverTeamPresenter implements IDriverTeamPresenter {
private result: DriverTeamViewModel | null = null;
export class DriverTeamPresenter {
private result: GetDriverTeamOutputDTO | null = null;
reset() {
this.result = null;
}
present(dto: DriverTeamResultDTO) {
const isOwner = dto.team.ownerId === dto.driverId;
const canManage = isOwner || dto.membership.role === 'owner' || dto.membership.role === 'manager';
async present(output: DriverTeamOutputPort) {
const isOwner = output.team.ownerId === output.driverId;
const canManage = isOwner || output.membership.role === 'owner' || output.membership.role === 'manager';
this.result = {
team: {
id: dto.team.id,
name: dto.team.name,
tag: dto.team.tag,
description: dto.team.description || '',
ownerId: dto.team.ownerId,
leagues: dto.team.leagues || [],
id: output.team.id,
name: output.team.name,
tag: output.team.tag,
description: output.team.description || '',
ownerId: output.team.ownerId,
leagues: output.team.leagues || [],
createdAt: output.team.createdAt.toISOString(),
},
membership: {
role: dto.membership.role as 'owner' | 'manager' | 'member',
joinedAt: dto.membership.joinedAt.toISOString(),
isActive: dto.membership.status === 'active',
role: output.membership.role === 'driver' ? 'member' : output.membership.role,
joinedAt: output.membership.joinedAt.toISOString(),
isActive: output.membership.status === 'active',
},
isOwner,
canManage,
};
}
getViewModel(): DriverTeamViewModel | null {
return this.result;
}
get viewModel(): DriverTeamViewModel {
if (!this.result) throw new Error('Presenter not presented');
getViewModel(): GetDriverTeamOutputDTO | null {
return this.result;
}
}

View File

@@ -1,50 +1,36 @@
import {
ITeamDetailsPresenter,
TeamDetailsResultDTO,
TeamDetailsViewModel,
} from '@core/racing/application/presenters/ITeamDetailsPresenter';
import type { GetTeamDetailsOutputPort } from '@core/racing/application/ports/output/GetTeamDetailsOutputPort';
import type { GetTeamDetailsOutputDTO } from '../dtos/GetTeamDetailsOutputDTO';
export class TeamDetailsPresenter implements ITeamDetailsPresenter {
private result: TeamDetailsViewModel | null = null;
export class TeamDetailsPresenter {
private result: GetTeamDetailsOutputDTO | null = null;
reset() {
this.result = null;
}
present(dto: TeamDetailsResultDTO) {
const { team, membership } = dto;
const canManage =
membership !== null &&
(membership.role === 'owner' || membership.role === 'manager');
async present(outputPort: GetTeamDetailsOutputPort): Promise<void> {
this.result = {
team: {
id: team.id,
name: team.name,
tag: team.tag,
description: team.description,
ownerId: team.ownerId,
leagues: team.leagues || [],
createdAt: team.createdAt?.toISOString() || new Date().toISOString(),
id: outputPort.team.id,
name: outputPort.team.name,
tag: outputPort.team.tag,
description: outputPort.team.description,
ownerId: outputPort.team.ownerId,
leagues: outputPort.team.leagues,
createdAt: outputPort.team.createdAt.toISOString(),
},
membership: membership
membership: outputPort.membership
? {
role: membership.role as 'owner' | 'manager' | 'member',
joinedAt: membership.joinedAt.toISOString(),
isActive: membership.status === 'active',
role: outputPort.membership.role,
joinedAt: outputPort.membership.joinedAt.toISOString(),
isActive: outputPort.membership.isActive,
}
: null,
canManage,
canManage: outputPort.canManage,
};
}
getViewModel(): TeamDetailsViewModel | null {
return this.result;
}
get viewModel(): TeamDetailsViewModel {
if (!this.result) throw new Error('Presenter not presented');
getViewModel(): GetTeamDetailsOutputDTO | null {
return this.result;
}
}

View File

@@ -1,43 +1,30 @@
import {
ITeamJoinRequestsPresenter,
TeamJoinRequestsResultDTO,
TeamJoinRequestsViewModel,
TeamJoinRequestViewModel,
} from '@core/racing/application/presenters/ITeamJoinRequestsPresenter';
import type { TeamJoinRequestsOutputPort } from '@core/racing/application/ports/output/TeamJoinRequestsOutputPort';
import type { GetTeamJoinRequestsOutputDTO } from '../dtos/GetTeamJoinRequestsOutputDTO';
export class TeamJoinRequestsPresenter implements ITeamJoinRequestsPresenter {
private result: TeamJoinRequestsViewModel | null = null;
export class TeamJoinRequestsPresenter {
private result: GetTeamJoinRequestsOutputDTO | null = null;
reset() {
this.result = null;
}
present(dto: TeamJoinRequestsResultDTO) {
const { requests, driverNames, avatarUrls } = dto;
const requestViewModels: TeamJoinRequestViewModel[] = requests.map((request) => ({
requestId: request.id,
driverId: request.driverId,
driverName: driverNames[request.driverId] || 'Unknown',
teamId: request.teamId,
status: 'pending' as const,
requestedAt: request.requestedAt.toISOString(),
avatarUrl: avatarUrls[request.driverId] || '',
}));
async present(outputPort: TeamJoinRequestsOutputPort): Promise<void> {
this.result = {
requests: requestViewModels,
pendingCount: requestViewModels.length,
totalCount: requestViewModels.length,
requests: outputPort.requests.map(request => ({
requestId: request.requestId,
driverId: request.driverId,
driverName: request.driverName,
teamId: request.teamId,
status: request.status,
requestedAt: request.requestedAt.toISOString(),
avatarUrl: request.avatarUrl,
})),
pendingCount: outputPort.pendingCount,
totalCount: outputPort.totalCount,
};
}
getViewModel(): TeamJoinRequestsViewModel | null {
return this.result;
}
get viewModel(): TeamJoinRequestsViewModel {
if (!this.result) throw new Error('Presenter not presented');
getViewModel(): GetTeamJoinRequestsOutputDTO | null {
return this.result;
}
}

View File

@@ -1,48 +1,31 @@
import {
ITeamMembersPresenter,
TeamMembersResultDTO,
TeamMembersViewModel,
TeamMemberViewModel,
} from '@core/racing/application/presenters/ITeamMembersPresenter';
import type { TeamMembersOutputPort } from '@core/racing/application/ports/output/TeamMembersOutputPort';
import type { GetTeamMembersOutputDTO } from '../dtos/GetTeamMembersOutputDTO';
export class TeamMembersPresenter implements ITeamMembersPresenter {
private result: TeamMembersViewModel | null = null;
export class TeamMembersPresenter {
private result: GetTeamMembersOutputDTO | null = null;
reset() {
this.result = null;
}
present(dto: TeamMembersResultDTO) {
const { memberships, driverNames, avatarUrls } = dto;
const members: TeamMemberViewModel[] = memberships.map((membership) => ({
driverId: membership.driverId,
driverName: driverNames[membership.driverId] || 'Unknown',
role: membership.role as 'owner' | 'manager' | 'member',
joinedAt: membership.joinedAt.toISOString(),
isActive: membership.status === 'active',
avatarUrl: avatarUrls[membership.driverId] || '',
}));
const ownerCount = members.filter((m) => m.role === 'owner').length;
const managerCount = members.filter((m) => m.role === 'manager').length;
const memberCount = members.filter((m) => m.role === 'member').length;
async present(outputPort: TeamMembersOutputPort): Promise<void> {
this.result = {
members,
totalCount: members.length,
ownerCount,
managerCount,
memberCount,
members: outputPort.members.map(member => ({
driverId: member.driverId,
driverName: member.driverName,
role: member.role,
joinedAt: member.joinedAt.toISOString(),
isActive: member.isActive,
avatarUrl: member.avatarUrl,
})),
totalCount: outputPort.totalCount,
ownerCount: outputPort.ownerCount,
managerCount: outputPort.managerCount,
memberCount: outputPort.memberCount,
};
}
getViewModel(): TeamMembersViewModel | null {
return this.result;
}
get viewModel(): TeamMembersViewModel {
if (!this.result) throw new Error('Presenter not presented');
getViewModel(): GetTeamMembersOutputDTO | null {
return this.result;
}
}

View File

@@ -1,32 +1,112 @@
import { ITeamsLeaderboardPresenter, TeamsLeaderboardResultDTO, TeamsLeaderboardViewModel, TeamLeaderboardItemViewModel } from '@core/racing/application/presenters/ITeamsLeaderboardPresenter';
import type { TeamsLeaderboardOutputPort } from '@core/racing/application/ports/output/TeamsLeaderboardOutputPort';
import type { GetTeamsLeaderboardOutputDTO } from '../dtos/GetTeamsLeaderboardOutputDTO';
export class TeamsLeaderboardPresenter implements ITeamsLeaderboardPresenter {
private result: TeamsLeaderboardViewModel | null = null;
export class TeamsLeaderboardPresenter {
private result: GetTeamsLeaderboardOutputDTO | null = null;
reset() {
this.result = null;
}
present(dto: TeamsLeaderboardResultDTO) {
async present(outputPort: TeamsLeaderboardOutputPort): Promise<void> {
this.result = {
teams: dto.teams as TeamLeaderboardItemViewModel[],
recruitingCount: dto.recruitingCount,
teams: outputPort.teams.map(team => ({
id: team.id,
name: team.name,
memberCount: team.memberCount,
rating: team.rating,
totalWins: team.totalWins,
totalRaces: team.totalRaces,
performanceLevel: team.performanceLevel,
isRecruiting: team.isRecruiting,
createdAt: team.createdAt.toISOString(),
description: team.description,
specialization: team.specialization,
region: team.region,
languages: team.languages,
})),
recruitingCount: outputPort.recruitingCount,
groupsBySkillLevel: {
beginner: [],
intermediate: [],
advanced: [],
pro: [],
beginner: outputPort.groupsBySkillLevel.beginner.map(team => ({
id: team.id,
name: team.name,
memberCount: team.memberCount,
rating: team.rating,
totalWins: team.totalWins,
totalRaces: team.totalRaces,
performanceLevel: team.performanceLevel,
isRecruiting: team.isRecruiting,
createdAt: team.createdAt.toISOString(),
description: team.description,
specialization: team.specialization,
region: team.region,
languages: team.languages,
})),
intermediate: outputPort.groupsBySkillLevel.intermediate.map(team => ({
id: team.id,
name: team.name,
memberCount: team.memberCount,
rating: team.rating,
totalWins: team.totalWins,
totalRaces: team.totalRaces,
performanceLevel: team.performanceLevel,
isRecruiting: team.isRecruiting,
createdAt: team.createdAt.toISOString(),
description: team.description,
specialization: team.specialization,
region: team.region,
languages: team.languages,
})),
advanced: outputPort.groupsBySkillLevel.advanced.map(team => ({
id: team.id,
name: team.name,
memberCount: team.memberCount,
rating: team.rating,
totalWins: team.totalWins,
totalRaces: team.totalRaces,
performanceLevel: team.performanceLevel,
isRecruiting: team.isRecruiting,
createdAt: team.createdAt.toISOString(),
description: team.description,
specialization: team.specialization,
region: team.region,
languages: team.languages,
})),
pro: outputPort.groupsBySkillLevel.pro.map(team => ({
id: team.id,
name: team.name,
memberCount: team.memberCount,
rating: team.rating,
totalWins: team.totalWins,
totalRaces: team.totalRaces,
performanceLevel: team.performanceLevel,
isRecruiting: team.isRecruiting,
createdAt: team.createdAt.toISOString(),
description: team.description,
specialization: team.specialization,
region: team.region,
languages: team.languages,
})),
},
topTeams: (dto.teams as TeamLeaderboardItemViewModel[]).slice(0, 10),
topTeams: outputPort.topTeams.map(team => ({
id: team.id,
name: team.name,
memberCount: team.memberCount,
rating: team.rating,
totalWins: team.totalWins,
totalRaces: team.totalRaces,
performanceLevel: team.performanceLevel,
isRecruiting: team.isRecruiting,
createdAt: team.createdAt.toISOString(),
description: team.description,
specialization: team.specialization,
region: team.region,
languages: team.languages,
})),
};
}
getViewModel(): TeamsLeaderboardViewModel | null {
return this.result;
}
get viewModel(): TeamsLeaderboardViewModel {
if (!this.result) throw new Error('Presenter not presented');
getViewModel(): GetTeamsLeaderboardOutputDTO | null {
return this.result;
}
}