presenter refactoring

This commit is contained in:
2025-12-20 17:06:11 +01:00
parent 92be9d2e1b
commit e9d6f90bb2
109 changed files with 4159 additions and 1283 deletions

View File

@@ -1,6 +1,18 @@
import type { ApproveLeagueJoinRequestResultPort } from '@core/racing/application/ports/output/ApproveLeagueJoinRequestResultPort';
import type { ApproveLeagueJoinRequestDTO } from '../dtos/ApproveLeagueJoinRequestDTO';
export function mapApproveLeagueJoinRequestPortToDTO(port: ApproveLeagueJoinRequestResultPort): ApproveLeagueJoinRequestDTO {
return port;
export class ApproveLeagueJoinRequestPresenter {
private result: ApproveLeagueJoinRequestDTO | null = null;
reset() {
this.result = null;
}
present(output: ApproveLeagueJoinRequestResultPort) {
this.result = output;
}
getViewModel(): ApproveLeagueJoinRequestDTO | null {
return this.result;
}
}

View File

@@ -0,0 +1,22 @@
import type { GetLeagueAdminPermissionsOutputPort } from '@core/racing/application/ports/output/GetLeagueAdminPermissionsOutputPort';
import { LeagueAdminPermissionsDTO } from '../dtos/LeagueAdminPermissionsDTO';
import type { Presenter } from '@core/shared/presentation';
export class GetLeagueAdminPermissionsPresenter implements Presenter<GetLeagueAdminPermissionsOutputPort, LeagueAdminPermissionsDTO> {
private result: LeagueAdminPermissionsDTO | null = null;
reset(): void {
this.result = null;
}
present(port: GetLeagueAdminPermissionsOutputPort): void {
this.result = {
canRemoveMember: port.canRemoveMember,
canUpdateRoles: port.canUpdateRoles,
};
}
getViewModel(): LeagueAdminPermissionsDTO | null {
return this.result;
}
}

View File

@@ -0,0 +1,28 @@
import { GetLeagueMembershipsPresenter } from './GetLeagueMembershipsPresenter';
import type { GetLeagueMembershipsOutputPort } from '@core/racing/application/ports/output/GetLeagueMembershipsOutputPort';
describe('GetLeagueMembershipsPresenter', () => {
it('presents memberships correctly', () => {
const presenter = new GetLeagueMembershipsPresenter();
const output: GetLeagueMembershipsOutputPort = {
memberships: {
members: [
{
driverId: 'driver-1',
driver: { id: 'driver-1', name: 'John Doe' },
role: 'member',
joinedAt: new Date('2023-01-01'),
},
],
},
};
presenter.present(output);
const vm = presenter.getViewModel();
expect(vm).not.toBeNull();
expect(vm!.memberships.members).toHaveLength(1);
expect(vm!.memberships.members[0].driverId).toBe('driver-1');
expect(vm!.memberships.members[0].driver.name).toBe('John Doe');
});
});

View File

@@ -0,0 +1,32 @@
import type { GetLeagueMembershipsOutputPort } from '@core/racing/application/ports/output/GetLeagueMembershipsOutputPort';
import { LeagueMembershipsDTO, LeagueMemberDTO } from '../dtos/LeagueMembershipsDTO';
export interface GetLeagueMembershipsViewModel {
memberships: LeagueMembershipsDTO;
}
export class GetLeagueMembershipsPresenter {
private result: GetLeagueMembershipsViewModel | null = null;
reset() {
this.result = null;
}
present(output: GetLeagueMembershipsOutputPort) {
const members: LeagueMemberDTO[] = output.memberships.members.map(member => ({
driverId: member.driverId,
driver: member.driver,
role: member.role,
joinedAt: member.joinedAt,
}));
this.result = {
memberships: {
members,
},
};
}
getViewModel(): GetLeagueMembershipsViewModel | null {
return this.result;
}
}

View File

@@ -1,19 +1,34 @@
import { GetLeagueOwnerSummaryOutputPort } from '@core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort';
import { LeagueOwnerSummaryDTO } from '../dtos/LeagueOwnerSummaryDTO';
export function mapGetLeagueOwnerSummaryOutputPortToDTO(output: GetLeagueOwnerSummaryOutputPort): LeagueOwnerSummaryDTO | null {
if (!output.summary) return null;
export class GetLeagueOwnerSummaryPresenter {
private result: LeagueOwnerSummaryDTO | null = null;
return {
driver: {
id: output.summary.driver.id,
iracingId: output.summary.driver.iracingId,
name: output.summary.driver.name,
country: output.summary.driver.country,
bio: output.summary.driver.bio,
joinedAt: output.summary.driver.joinedAt,
},
rating: output.summary.rating,
rank: output.summary.rank,
};
reset() {
this.result = null;
}
present(output: GetLeagueOwnerSummaryOutputPort) {
if (!output.summary) {
this.result = null;
return;
}
this.result = {
driver: {
id: output.summary.driver.id,
iracingId: output.summary.driver.iracingId,
name: output.summary.driver.name,
country: output.summary.driver.country,
bio: output.summary.driver.bio,
joinedAt: output.summary.driver.joinedAt,
},
rating: output.summary.rating,
rank: output.summary.rank,
};
}
getViewModel(): LeagueOwnerSummaryDTO | null {
return this.result;
}
}

View File

@@ -20,49 +20,65 @@ function mapProtestStatus(status: ProtestOutputPort['status']): ProtestDTO['stat
}
}
export function mapGetLeagueProtestsOutputPortToDTO(output: GetLeagueProtestsOutputPort, leagueName?: string): LeagueAdminProtestsDTO {
const protests: ProtestDTO[] = output.protests.map((protest) => {
const race = output.racesById[protest.raceId];
export class GetLeagueProtestsPresenter {
private result: LeagueAdminProtestsDTO | null = null;
return {
id: protest.id,
leagueId: race?.leagueId,
raceId: protest.raceId,
protestingDriverId: protest.protestingDriverId,
accusedDriverId: protest.accusedDriverId,
submittedAt: new Date(protest.filedAt),
description: protest.incident.description,
status: mapProtestStatus(protest.status),
};
});
reset() {
this.result = null;
}
const racesById: { [raceId: string]: RaceDTO } = {};
for (const raceId in output.racesById) {
const race = output.racesById[raceId];
racesById[raceId] = {
id: race.id,
name: race.track,
date: race.scheduledAt,
leagueName,
present(output: GetLeagueProtestsOutputPort, leagueName?: string) {
const protests: ProtestDTO[] = output.protests.map((protest) => {
const race = output.racesById[protest.raceId];
return {
id: protest.id,
leagueId: race?.leagueId || '',
raceId: protest.raceId,
protestingDriverId: protest.protestingDriverId,
accusedDriverId: protest.accusedDriverId,
submittedAt: new Date(protest.filedAt),
description: protest.incident.description,
status: mapProtestStatus(protest.status),
};
});
const racesById: { [raceId: string]: RaceDTO } = {};
for (const raceId in output.racesById) {
const race = output.racesById[raceId];
if (race) {
racesById[raceId] = {
id: race.id,
name: race.track,
date: race.scheduledAt.toISOString(),
leagueName,
};
}
}
const driversById: { [driverId: string]: DriverDTO } = {};
for (const driverId in output.driversById) {
const driver = output.driversById[driverId];
if (driver) {
driversById[driverId] = {
id: driver.id,
iracingId: driver.iracingId,
name: driver.name,
country: driver.country,
bio: driver.bio,
joinedAt: driver.joinedAt,
};
}
}
this.result = {
protests,
racesById,
driversById,
};
}
const driversById: { [driverId: string]: DriverDTO } = {};
for (const driverId in output.driversById) {
const driver = output.driversById[driverId];
driversById[driverId] = {
id: driver.id,
iracingId: driver.iracingId,
name: driver.name,
country: driver.country,
bio: driver.bio,
joinedAt: driver.joinedAt,
};
getViewModel(): LeagueAdminProtestsDTO | null {
return this.result;
}
return {
protests,
racesById,
driversById,
};
}

View File

@@ -1,14 +1,26 @@
import { GetLeagueSeasonsOutputPort } from '@core/racing/application/ports/output/GetLeagueSeasonsOutputPort';
import { LeagueSeasonSummaryDTO } from '../dtos/LeagueSeasonSummaryDTO';
export function mapGetLeagueSeasonsOutputPortToDTO(output: GetLeagueSeasonsOutputPort): LeagueSeasonSummaryDTO[] {
return output.seasons.map(season => ({
seasonId: season.seasonId,
name: season.name,
status: season.status,
startDate: season.startDate,
endDate: season.endDate,
isPrimary: season.isPrimary,
isParallelActive: season.isParallelActive,
}));
export class GetLeagueSeasonsPresenter {
private result: LeagueSeasonSummaryDTO[] | null = null;
reset() {
this.result = null;
}
present(output: GetLeagueSeasonsOutputPort) {
this.result = output.seasons.map(season => ({
seasonId: season.seasonId,
name: season.name,
status: season.status,
startDate: season.startDate,
endDate: season.endDate,
isPrimary: season.isPrimary,
isParallelActive: season.isParallelActive,
}));
}
getViewModel(): LeagueSeasonSummaryDTO[] | null {
return this.result;
}
}

View File

@@ -0,0 +1,20 @@
import type { GetSeasonSponsorshipsOutputPort } from '@core/racing/application/ports/output/GetSeasonSponsorshipsOutputPort';
import { GetSeasonSponsorshipsOutputDTO } from '../dtos/GetSeasonSponsorshipsOutputDTO';
export class GetSeasonSponsorshipsPresenter {
private result: GetSeasonSponsorshipsOutputDTO | null = null;
reset() {
this.result = null;
}
present(output: GetSeasonSponsorshipsOutputPort) {
this.result = {
sponsorships: output?.sponsorships ?? [],
};
}
getViewModel(): GetSeasonSponsorshipsOutputDTO | null {
return this.result;
}
}

View File

@@ -1,9 +1,21 @@
import type { JoinLeagueOutputPort } from '@core/racing/application/ports/output/JoinLeagueOutputPort';
import type { JoinLeagueOutputDTO } from '../dtos/JoinLeagueOutputDTO';
export function mapJoinLeagueOutputPortToDTO(port: JoinLeagueOutputPort): JoinLeagueOutputDTO {
return {
success: true,
membershipId: port.membershipId,
};
export class JoinLeaguePresenter {
private result: JoinLeagueOutputDTO | null = null;
reset() {
this.result = null;
}
present(output: JoinLeagueOutputPort) {
this.result = {
success: true,
membershipId: output.membershipId,
};
}
getViewModel(): JoinLeagueOutputDTO | null {
return this.result;
}
}

View File

@@ -0,0 +1,28 @@
import { LeagueJoinRequestsPresenter } from './LeagueJoinRequestsPresenter';
import type { GetLeagueJoinRequestsOutputPort } from '@core/racing/application/ports/output/GetLeagueJoinRequestsOutputPort';
describe('LeagueJoinRequestsPresenter', () => {
it('presents join requests correctly', () => {
const presenter = new LeagueJoinRequestsPresenter();
const output: GetLeagueJoinRequestsOutputPort = {
joinRequests: [
{
id: 'req-1',
leagueId: 'league-1',
driverId: 'driver-1',
requestedAt: new Date('2023-01-01'),
message: 'Please accept me',
driver: { id: 'driver-1', name: 'John Doe' },
},
],
};
presenter.present(output);
const vm = presenter.getViewModel();
expect(vm).not.toBeNull();
expect(vm!.joinRequests).toHaveLength(1);
expect(vm!.joinRequests[0].id).toBe('req-1');
expect(vm!.joinRequests[0].driver.name).toBe('John Doe');
});
});

View File

@@ -0,0 +1,32 @@
import type { GetLeagueJoinRequestsOutputPort } from '@core/racing/application/ports/output/GetLeagueJoinRequestsOutputPort';
import { LeagueJoinRequestWithDriverDTO } from '../dtos/LeagueJoinRequestWithDriverDTO';
export interface LeagueJoinRequestsViewModel {
joinRequests: LeagueJoinRequestWithDriverDTO[];
}
export class LeagueJoinRequestsPresenter {
private result: LeagueJoinRequestsViewModel | null = null;
reset() {
this.result = null;
}
present(output: GetLeagueJoinRequestsOutputPort) {
const joinRequests: LeagueJoinRequestWithDriverDTO[] = output.joinRequests.map(request => ({
id: request.id,
leagueId: request.leagueId,
driverId: request.driverId,
requestedAt: request.requestedAt,
message: request.message,
driver: request.driver,
}));
this.result = {
joinRequests,
};
}
getViewModel(): LeagueJoinRequestsViewModel | null {
return this.result;
}
}

View File

@@ -0,0 +1,42 @@
import { LeagueOwnerSummaryPresenter } from './LeagueOwnerSummaryPresenter';
import type { GetLeagueOwnerSummaryOutputPort } from '@core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort';
describe('LeagueOwnerSummaryPresenter', () => {
it('presents owner summary correctly', () => {
const presenter = new LeagueOwnerSummaryPresenter();
const output: GetLeagueOwnerSummaryOutputPort = {
summary: {
driver: {
id: 'driver-1',
iracingId: '12345',
name: 'John Doe',
country: 'US',
bio: 'Racing enthusiast',
joinedAt: '2023-01-01',
},
rating: 1500,
rank: 100,
},
};
presenter.present(output);
const vm = presenter.getViewModel();
expect(vm).not.toBeNull();
expect(vm!.driver.id).toBe('driver-1');
expect(vm!.rating).toBe(1500);
expect(vm!.rank).toBe(100);
});
it('handles null summary', () => {
const presenter = new LeagueOwnerSummaryPresenter();
const output: GetLeagueOwnerSummaryOutputPort = {
summary: null,
};
presenter.present(output);
const vm = presenter.getViewModel();
expect(vm).toBeNull();
});
});

View File

@@ -0,0 +1,33 @@
import type { GetLeagueOwnerSummaryOutputPort } from '@core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort';
import { LeagueOwnerSummaryDTO } from '../dtos/LeagueOwnerSummaryDTO';
export class LeagueOwnerSummaryPresenter {
private result: LeagueOwnerSummaryDTO | null = null;
reset() {
this.result = null;
}
present(output: GetLeagueOwnerSummaryOutputPort) {
if (!output.summary) {
this.result = null;
return;
}
this.result = {
driver: {
id: output.summary.driver.id,
iracingId: output.summary.driver.iracingId,
name: output.summary.driver.name,
country: output.summary.driver.country,
bio: output.summary.driver.bio,
joinedAt: output.summary.driver.joinedAt,
},
rating: output.summary.rating,
rank: output.summary.rank,
};
}
getViewModel(): LeagueOwnerSummaryDTO | null {
return this.result;
}
}

View File

@@ -2,22 +2,46 @@ import { GetLeagueScheduleOutputPort } from '@core/racing/application/ports/outp
import { LeagueScheduleDTO } from '../dtos/LeagueScheduleDTO';
import { RaceDTO } from '../../race/dtos/RaceDTO';
export function mapGetLeagueScheduleOutputPortToDTO(output: GetLeagueScheduleOutputPort, leagueName?: string): LeagueScheduleDTO {
return {
races: output.races.map<RaceDTO>(race => ({
export class LeagueSchedulePresenter {
private result: LeagueScheduleDTO | null = null;
reset() {
this.result = null;
}
present(output: GetLeagueScheduleOutputPort, leagueName?: string) {
this.result = {
races: output.races.map<RaceDTO>(race => ({
id: race.id,
name: race.name,
date: race.scheduledAt.toISOString(),
leagueName,
})),
};
}
getViewModel(): LeagueScheduleDTO | null {
return this.result;
}
}
export class LeagueRacesPresenter {
private result: RaceDTO[] | null = null;
reset() {
this.result = null;
}
present(output: GetLeagueScheduleOutputPort, leagueName?: string) {
this.result = output.races.map<RaceDTO>(race => ({
id: race.id,
name: race.name,
date: race.scheduledAt.toISOString(),
leagueName,
})),
};
}
}));
}
export function mapGetLeagueScheduleOutputPortToRaceDTOs(output: GetLeagueScheduleOutputPort, leagueName?: string): RaceDTO[] {
return output.races.map<RaceDTO>(race => ({
id: race.id,
name: race.name,
date: race.scheduledAt.toISOString(),
leagueName,
}));
getViewModel(): RaceDTO[] | null {
return this.result;
}
}

View File

@@ -1,9 +1,21 @@
import type { RejectLeagueJoinRequestOutputPort } from '@core/racing/application/ports/output/RejectLeagueJoinRequestOutputPort';
import type { RejectJoinRequestOutputDTO } from '../dtos/RejectJoinRequestOutputDTO';
export function mapRejectLeagueJoinRequestOutputPortToDTO(port: RejectLeagueJoinRequestOutputPort): RejectJoinRequestOutputDTO {
return {
success: port.success,
message: port.message,
};
export class RejectLeagueJoinRequestPresenter {
private result: RejectJoinRequestOutputDTO | null = null;
reset() {
this.result = null;
}
present(output: RejectLeagueJoinRequestOutputPort) {
this.result = {
success: output.success,
message: output.message,
};
}
getViewModel(): RejectJoinRequestOutputDTO | null {
return this.result;
}
}

View File

@@ -1,8 +1,20 @@
import type { RemoveLeagueMemberOutputPort } from '@core/racing/application/ports/output/RemoveLeagueMemberOutputPort';
import type { RemoveLeagueMemberOutputDTO } from '../dtos/RemoveLeagueMemberOutputDTO';
export function mapRemoveLeagueMemberOutputPortToDTO(port: RemoveLeagueMemberOutputPort): RemoveLeagueMemberOutputDTO {
return {
success: port.success,
};
export class RemoveLeagueMemberPresenter {
private result: RemoveLeagueMemberOutputDTO | null = null;
reset() {
this.result = null;
}
present(output: RemoveLeagueMemberOutputPort) {
this.result = {
success: output.success,
};
}
getViewModel(): RemoveLeagueMemberOutputDTO | null {
return this.result;
}
}

View File

@@ -1,8 +1,20 @@
import type { TransferLeagueOwnershipOutputPort } from '@core/racing/application/ports/output/TransferLeagueOwnershipOutputPort';
import type { TransferLeagueOwnershipOutputDTO } from '../dtos/TransferLeagueOwnershipOutputDTO';
export function mapTransferLeagueOwnershipOutputPortToDTO(port: TransferLeagueOwnershipOutputPort): TransferLeagueOwnershipOutputDTO {
return {
success: port.success,
};
export class TransferLeagueOwnershipPresenter {
private result: TransferLeagueOwnershipOutputDTO | null = null;
reset() {
this.result = null;
}
present(output: TransferLeagueOwnershipOutputPort) {
this.result = {
success: output.success,
};
}
getViewModel(): TransferLeagueOwnershipOutputDTO | null {
return this.result;
}
}

View File

@@ -1,8 +1,20 @@
import type { UpdateLeagueMemberRoleOutputPort } from '@core/racing/application/ports/output/UpdateLeagueMemberRoleOutputPort';
import type { UpdateLeagueMemberRoleOutputDTO } from '../dtos/UpdateLeagueMemberRoleOutputDTO';
export function mapUpdateLeagueMemberRoleOutputPortToDTO(port: UpdateLeagueMemberRoleOutputPort): UpdateLeagueMemberRoleOutputDTO {
return {
success: port.success,
};
export class UpdateLeagueMemberRolePresenter {
private result: UpdateLeagueMemberRoleOutputDTO | null = null;
reset() {
this.result = null;
}
present(output: UpdateLeagueMemberRoleOutputPort) {
this.result = {
success: output.success,
};
}
getViewModel(): UpdateLeagueMemberRoleOutputDTO | null {
return this.result;
}
}