diff --git a/apps/api/src/domain/league/presenters/ApproveLeagueJoinRequestPresenter.ts b/apps/api/src/domain/league/presenters/ApproveLeagueJoinRequestPresenter.ts index a2f46d6da..f66a39559 100644 --- a/apps/api/src/domain/league/presenters/ApproveLeagueJoinRequestPresenter.ts +++ b/apps/api/src/domain/league/presenters/ApproveLeagueJoinRequestPresenter.ts @@ -1,4 +1,4 @@ -import { IApproveLeagueJoinRequestPresenter, ApproveLeagueJoinRequestResultDTO, ApproveLeagueJoinRequestViewModel } from '@core/racing/application/presenters/IApproveLeagueJoinRequestPresenter'; +import { IApproveLeagueJoinRequestPresenter, ApproveLeagueJoinRequestResultPort, ApproveLeagueJoinRequestViewModel } from '@core/racing/application/presenters/IApproveLeagueJoinRequestPresenter'; export class ApproveLeagueJoinRequestPresenter implements IApproveLeagueJoinRequestPresenter { private result: ApproveLeagueJoinRequestViewModel | null = null; @@ -7,7 +7,7 @@ export class ApproveLeagueJoinRequestPresenter implements IApproveLeagueJoinRequ this.result = null; } - present(dto: ApproveLeagueJoinRequestResultDTO) { + present(dto: ApproveLeagueJoinRequestResultPort) { this.result = dto; } diff --git a/apps/api/src/domain/sponsor/SponsorController.ts b/apps/api/src/domain/sponsor/SponsorController.ts index af2a796b4..d47641838 100644 --- a/apps/api/src/domain/sponsor/SponsorController.ts +++ b/apps/api/src/domain/sponsor/SponsorController.ts @@ -13,7 +13,7 @@ import { GetSponsorOutputDTO } from './dtos/GetSponsorOutputDTO'; import { GetPendingSponsorshipRequestsOutputDTO } from './dtos/GetPendingSponsorshipRequestsOutputDTO'; import { AcceptSponsorshipRequestInputDTO } from './dtos/AcceptSponsorshipRequestInputDTO'; import { RejectSponsorshipRequestInputDTO } from './dtos/RejectSponsorshipRequestInputDTO'; -import type { AcceptSponsorshipRequestResultDTO } from '@core/racing/application/dtos/AcceptSponsorshipRequestResultDTO'; +import type { AcceptSponsorshipRequestResultPort } from '@core/racing/application/ports/output/AcceptSponsorshipRequestResultPort'; import type { RejectSponsorshipRequestResultDTO } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase'; @ApiTags('sponsors') @@ -80,7 +80,7 @@ export class SponsorController { @ApiResponse({ status: 200, description: 'Sponsorship request accepted' }) @ApiResponse({ status: 400, description: 'Invalid request' }) @ApiResponse({ status: 404, description: 'Request not found' }) - async acceptSponsorshipRequest(@Param('requestId') requestId: string, @Body() input: AcceptSponsorshipRequestInputDTO): Promise { + async acceptSponsorshipRequest(@Param('requestId') requestId: string, @Body() input: AcceptSponsorshipRequestInputDTO): Promise { return this.sponsorService.acceptSponsorshipRequest(requestId, input.respondedBy); } diff --git a/apps/api/src/domain/sponsor/SponsorService.ts b/apps/api/src/domain/sponsor/SponsorService.ts index fe94fc5b5..7896bba95 100644 --- a/apps/api/src/domain/sponsor/SponsorService.ts +++ b/apps/api/src/domain/sponsor/SponsorService.ts @@ -23,7 +23,7 @@ import { GetPendingSponsorshipRequestsUseCase, GetPendingSponsorshipRequestsDTO import { AcceptSponsorshipRequestUseCase } from '@core/racing/application/use-cases/AcceptSponsorshipRequestUseCase'; import { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase'; import type { SponsorableEntityType } from '@core/racing/domain/entities/SponsorshipRequest'; -import type { AcceptSponsorshipRequestResultDTO } from '@core/racing/application/dtos/AcceptSponsorshipRequestResultDTO'; +import type { AcceptSponsorshipRequestResultPort } from '@core/racing/application/ports/output/AcceptSponsorshipRequestResultPort'; import type { RejectSponsorshipRequestResultDTO } from '@core/racing/application/use-cases/RejectSponsorshipRequestUseCase'; @@ -123,7 +123,7 @@ export class SponsorService { return result.value as GetPendingSponsorshipRequestsOutputDTO; } - async acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise { + async acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise { this.logger.debug('[SponsorService] Accepting sponsorship request.', { requestId, respondedBy }); const result = await this.acceptSponsorshipRequestUseCase.execute({ requestId, respondedBy }); diff --git a/apps/api/src/domain/sponsor/presenters/CreateSponsorPresenter.ts b/apps/api/src/domain/sponsor/presenters/CreateSponsorPresenter.ts index d7384fd1a..8ec2aad69 100644 --- a/apps/api/src/domain/sponsor/presenters/CreateSponsorPresenter.ts +++ b/apps/api/src/domain/sponsor/presenters/CreateSponsorPresenter.ts @@ -1,4 +1,4 @@ -import { CreateSponsorViewModel, CreateSponsorResultDTO, ICreateSponsorPresenter } from '@core/racing/application/presenters/ICreateSponsorPresenter'; +import { CreateSponsorViewModel, CreateSponsorOutputPort, ICreateSponsorPresenter } from '@core/racing/application/presenters/ICreateSponsorPresenter'; export class CreateSponsorPresenter implements ICreateSponsorPresenter { private result: CreateSponsorViewModel | null = null; @@ -7,7 +7,7 @@ export class CreateSponsorPresenter implements ICreateSponsorPresenter { this.result = null; } - present(dto: CreateSponsorResultDTO) { + present(dto: CreateSponsorOutputPort) { this.result = dto; } diff --git a/core/racing/application/dtos/ApplyForSponsorshipDTO.ts b/core/racing/application/dtos/ApplyForSponsorshipDTO.ts deleted file mode 100644 index c2db72f12..000000000 --- a/core/racing/application/dtos/ApplyForSponsorshipDTO.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest'; -import type { SponsorshipTier } from '../../domain/entities/SeasonSponsorship'; -import type { Currency } from '../../domain/value-objects/Money'; - -export interface ApplyForSponsorshipDTO { - sponsorId: string; - entityType: SponsorableEntityType; - entityId: string; - tier: SponsorshipTier; - offeredAmount: number; // in cents - currency?: Currency; - message?: string; -} \ No newline at end of file diff --git a/core/racing/application/dtos/ApplyPenaltyCommand.ts b/core/racing/application/dtos/ApplyPenaltyCommand.ts deleted file mode 100644 index f276a2e66..000000000 --- a/core/racing/application/dtos/ApplyPenaltyCommand.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { PenaltyType } from '../../domain/entities/Penalty'; - -export interface ApplyPenaltyCommand { - raceId: string; - driverId: string; - stewardId: string; - type: PenaltyType; - value?: number; - reason: string; - protestId?: string; - notes?: string; -} \ No newline at end of file diff --git a/core/racing/application/dtos/ApproveLeagueJoinRequestResultDTO.ts b/core/racing/application/dtos/ApproveLeagueJoinRequestResultDTO.ts deleted file mode 100644 index 8c30d5f37..000000000 --- a/core/racing/application/dtos/ApproveLeagueJoinRequestResultDTO.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ApproveLeagueJoinRequestResultDTO { - success: boolean; - message: string; -} \ No newline at end of file diff --git a/core/racing/application/dtos/CreateTeamResultDTO.ts b/core/racing/application/dtos/CreateTeamResultDTO.ts deleted file mode 100644 index 6d3584fee..000000000 --- a/core/racing/application/dtos/CreateTeamResultDTO.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Team } from '../../domain/entities/Team'; - -export interface CreateTeamResultDTO { - team: Team; -} \ No newline at end of file diff --git a/core/racing/application/dtos/GetAllTeamsQueryResultDTO.ts b/core/racing/application/dtos/GetAllTeamsQueryResultDTO.ts deleted file mode 100644 index 15f92139b..000000000 --- a/core/racing/application/dtos/GetAllTeamsQueryResultDTO.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Team } from '../../domain/entities/Team'; - -export type GetAllTeamsQueryResultDTO = Team[]; \ No newline at end of file diff --git a/core/racing/application/dtos/GetDriverTeamQueryResultDTO.ts b/core/racing/application/dtos/GetDriverTeamQueryResultDTO.ts deleted file mode 100644 index f4a7d6db2..000000000 --- a/core/racing/application/dtos/GetDriverTeamQueryResultDTO.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Team } from '../../domain/entities/Team'; -import type { TeamMembership } from '../../domain/types/TeamMembership'; - -export interface GetDriverTeamQueryResultDTO { - team: Team; - membership: TeamMembership; -} \ No newline at end of file diff --git a/core/racing/application/dtos/GetEntitySponsorshipPricingDTO.ts b/core/racing/application/dtos/GetEntitySponsorshipPricingDTO.ts deleted file mode 100644 index b9278c8f6..000000000 --- a/core/racing/application/dtos/GetEntitySponsorshipPricingDTO.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest'; - -export interface GetEntitySponsorshipPricingDTO { - entityType: SponsorableEntityType; - entityId: string; -} \ No newline at end of file diff --git a/core/racing/application/dtos/GetEntitySponsorshipPricingResultDTO.ts b/core/racing/application/dtos/GetEntitySponsorshipPricingResultDTO.ts deleted file mode 100644 index ae9de119d..000000000 --- a/core/racing/application/dtos/GetEntitySponsorshipPricingResultDTO.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest'; -import type { SponsorshipSlotDTO } from './SponsorshipSlotDTO'; - -export interface GetEntitySponsorshipPricingResultDTO { - entityType: SponsorableEntityType; - entityId: string; - acceptingApplications: boolean; - customRequirements?: string; - mainSlot?: SponsorshipSlotDTO; - secondarySlot?: SponsorshipSlotDTO; -} \ No newline at end of file diff --git a/core/racing/application/dtos/GetLeagueMembershipsResultDTO.ts b/core/racing/application/dtos/GetLeagueMembershipsResultDTO.ts deleted file mode 100644 index 5992e33bf..000000000 --- a/core/racing/application/dtos/GetLeagueMembershipsResultDTO.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { LeagueMembership } from '../../domain/entities/LeagueMembership'; - -export interface GetLeagueMembershipsResultDTO { - memberships: LeagueMembership[]; - drivers: { id: string; name: string }[]; -} \ No newline at end of file diff --git a/core/racing/application/dtos/GetLeagueProtestsResultDTO.ts b/core/racing/application/dtos/GetLeagueProtestsResultDTO.ts deleted file mode 100644 index 28b08983f..000000000 --- a/core/racing/application/dtos/GetLeagueProtestsResultDTO.ts +++ /dev/null @@ -1,26 +0,0 @@ -export interface ProtestDTO { - id: string; - raceId: string; - protestingDriverId: string; - accusedDriverId: string; - submittedAt: Date; - description: string; - status: string; -} - -export interface RaceDTO { - id: string; - name: string; - date: string; -} - -export interface DriverDTO { - id: string; - name: string; -} - -export interface GetLeagueProtestsResultDTO { - protests: ProtestDTO[]; - races: RaceDTO[]; - drivers: DriverDTO[]; -} \ No newline at end of file diff --git a/core/racing/application/dtos/GetTeamDetailsQueryResultDTO.ts b/core/racing/application/dtos/GetTeamDetailsQueryResultDTO.ts deleted file mode 100644 index 0375aa221..000000000 --- a/core/racing/application/dtos/GetTeamDetailsQueryResultDTO.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Team } from '../../domain/entities/Team'; -import type { TeamMembership } from '../../domain/types/TeamMembership'; - -export interface GetTeamDetailsQueryResultDTO { - team: Team; - membership: TeamMembership | null; -} \ No newline at end of file diff --git a/core/racing/application/dtos/LeagueConfigFormDTO.ts b/core/racing/application/dtos/LeagueConfigFormDTO.ts deleted file mode 100644 index bfd975697..000000000 --- a/core/racing/application/dtos/LeagueConfigFormDTO.ts +++ /dev/null @@ -1,148 +0,0 @@ -import type { StewardingDecisionMode } from '../../domain/entities/League'; -import type { LeagueVisibilityType } from '../../domain/value-objects/LeagueVisibility'; - -export type LeagueStructureMode = 'solo' | 'fixedTeams'; - -// TODO this is way too much for a DTO. it must be pure InputPort or OutputPort - -/** - * League visibility determines public visibility and ranking status. - * - 'ranked': Public, competitive, affects driver ratings. Requires min 10 drivers. - * - 'unranked': Private, casual with friends. No rating impact. Any number of drivers. - * - * For backward compatibility, 'public'/'private' are also supported in the form, - * but the domain uses 'ranked'/'unranked'. - */ -export type LeagueVisibilityFormValue = LeagueVisibilityType | 'public' | 'private'; - -export interface LeagueStructureFormDTO { - mode: LeagueStructureMode; - maxDrivers: number; - maxTeams?: number; - driversPerTeam?: number; - multiClassEnabled?: boolean; -} - -export interface LeagueChampionshipsFormDTO { - enableDriverChampionship: boolean; - enableTeamChampionship: boolean; - enableNationsChampionship: boolean; - enableTrophyChampionship: boolean; -} - -export interface LeagueScoringFormDTO { - patternId?: string; // e.g. 'sprint-main-driver', 'club-ladder-solo' - // For now, keep customScoring optional and simple: - customScoringEnabled?: boolean; -} - -export interface LeagueDropPolicyFormDTO { - strategy: 'none' | 'bestNResults' | 'dropWorstN'; - n?: number; -} - -export interface LeagueTimingsFormDTO { - practiceMinutes?: number; - qualifyingMinutes: number; - sprintRaceMinutes?: number; - mainRaceMinutes: number; - sessionCount: number; - roundsPlanned?: number; - - seasonStartDate?: string; // ISO date YYYY-MM-DD - seasonEndDate?: string; // ISO date YYYY-MM-DD - raceStartTime?: string; // "HH:MM" 24h - timezoneId?: string; // IANA ID, e.g. "Europe/Berlin", or "track" for track local time - recurrenceStrategy?: 'weekly' | 'everyNWeeks' | 'monthlyNthWeekday'; - intervalWeeks?: number; - weekdays?: import('../../domain/types/Weekday').Weekday[]; - monthlyOrdinal?: 1 | 2 | 3 | 4; - monthlyWeekday?: import('../../domain/types/Weekday').Weekday; -} - -/** - * Stewarding configuration for protests and penalties. - */ -export interface LeagueStewardingFormDTO { - /** - * How protest decisions are made - */ - decisionMode: StewardingDecisionMode; - /** - * Number of votes required to uphold/reject a protest - * Used with steward_vote, member_vote, steward_veto, member_veto modes - */ - requiredVotes?: number; - /** - * Whether to require a defense from the accused before deciding - */ - requireDefense: boolean; - /** - * Time limit (hours) for accused to submit defense - */ - defenseTimeLimit: number; - /** - * Time limit (hours) for voting to complete - */ - voteTimeLimit: number; - /** - * Time limit (hours) after race ends when protests can be filed - */ - protestDeadlineHours: number; - /** - * Time limit (hours) after race ends when stewarding is closed - */ - stewardingClosesHours: number; - /** - * Whether to notify the accused when a protest is filed - */ - notifyAccusedOnProtest: boolean; - /** - * Whether to notify eligible voters when a vote is required - */ - notifyOnVoteRequired: boolean; -} - -export interface LeagueConfigFormModel { - leagueId?: string; // present for admin, omitted for create - basics: { - name: string; - description?: string; - /** - * League visibility/ranking mode. - * - 'ranked' (or legacy 'public'): Competitive, public, affects ratings. Min 10 drivers. - * - 'unranked' (or legacy 'private'): Casual with friends, no rating impact. - */ - visibility: LeagueVisibilityFormValue; - gameId: string; - /** - * League logo as base64 data URL (optional). - * Format: data:image/png;base64,... or data:image/jpeg;base64,... - */ - logoDataUrl?: string; - }; - structure: LeagueStructureFormDTO; - championships: LeagueChampionshipsFormDTO; - scoring: LeagueScoringFormDTO; - dropPolicy: LeagueDropPolicyFormDTO; - timings: LeagueTimingsFormDTO; - stewarding: LeagueStewardingFormDTO; -} - -/** - * Helper to normalize visibility values to new terminology. - * Maps 'public' -> 'ranked' and 'private' -> 'unranked'. - */ -export function normalizeVisibility(value: LeagueVisibilityFormValue): LeagueVisibilityType { - if (value === 'public' || value === 'ranked') return 'ranked'; - return 'unranked'; -} - -/** - * Helper to convert new terminology to legacy for backward compatibility. - * Maps 'ranked' -> 'public' and 'unranked' -> 'private'. - */ -export function toLegacyVisibility(value: LeagueVisibilityFormValue): 'public' | 'private' { - if (value === 'ranked' || value === 'public') return 'public'; - return 'private'; -} \ No newline at end of file diff --git a/core/racing/application/dtos/LeagueScheduleDTO.ts b/core/racing/application/dtos/LeagueScheduleDTO.ts deleted file mode 100644 index bb607e636..000000000 --- a/core/racing/application/dtos/LeagueScheduleDTO.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { LeagueTimingsFormDTO } from './LeagueConfigFormDTO'; -import type { Weekday } from '../../domain/types/Weekday'; -import { RaceTimeOfDay } from '../../domain/value-objects/RaceTimeOfDay'; -import { LeagueTimezone } from '../../domain/value-objects/LeagueTimezone'; -import { WeekdaySet } from '../../domain/value-objects/WeekdaySet'; -import { MonthlyRecurrencePattern } from '../../domain/value-objects/MonthlyRecurrencePattern'; -import type { RecurrenceStrategy } from '../../domain/value-objects/RecurrenceStrategy'; -import { RecurrenceStrategyFactory } from '../../domain/value-objects/RecurrenceStrategy'; -import { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule'; -import { BusinessRuleViolationError } from '../errors/RacingApplicationError'; - -export interface LeagueScheduleDTO { - seasonStartDate: string; - raceStartTime: string; - timezoneId: string; - recurrenceStrategy: 'weekly' | 'everyNWeeks' | 'monthlyNthWeekday'; - intervalWeeks?: number | undefined; - weekdays?: Weekday[] | undefined; - monthlyOrdinal?: 1 | 2 | 3 | 4 | undefined; - monthlyWeekday?: Weekday | undefined; - plannedRounds: number; -} - -export interface LeagueSchedulePreviewDTO { - rounds: Array<{ roundNumber: number; scheduledAt: string; timezoneId: string }>; - summary: string; -} - -export function leagueTimingsToScheduleDTO( - timings: LeagueTimingsFormDTO, -): LeagueScheduleDTO | null { - if ( - !timings.seasonStartDate || - !timings.raceStartTime || - !timings.timezoneId || - !timings.recurrenceStrategy || - !timings.roundsPlanned - ) { - return null; - } - - return { - seasonStartDate: timings.seasonStartDate, - raceStartTime: timings.raceStartTime, - timezoneId: timings.timezoneId, - recurrenceStrategy: timings.recurrenceStrategy, - intervalWeeks: timings.intervalWeeks, - weekdays: timings.weekdays, - monthlyOrdinal: timings.monthlyOrdinal, - monthlyWeekday: timings.monthlyWeekday, - plannedRounds: timings.roundsPlanned, - }; -} - -export function scheduleDTOToSeasonSchedule(dto: LeagueScheduleDTO): SeasonSchedule { - if (!dto.seasonStartDate) { - throw new BusinessRuleViolationError('seasonStartDate is required'); - } - if (!dto.raceStartTime) { - throw new BusinessRuleViolationError('raceStartTime is required'); - } - if (!dto.timezoneId) { - throw new BusinessRuleViolationError('timezoneId is required'); - } - if (!dto.recurrenceStrategy) { - throw new BusinessRuleViolationError('recurrenceStrategy is required'); - } - if (!Number.isInteger(dto.plannedRounds) || dto.plannedRounds <= 0) { - throw new BusinessRuleViolationError('plannedRounds must be a positive integer'); - } - - const startDate = new Date(dto.seasonStartDate); - if (Number.isNaN(startDate.getTime())) { - throw new BusinessRuleViolationError( - `seasonStartDate must be a valid date, got "${dto.seasonStartDate}"`, - ); - } - - const timeOfDay = RaceTimeOfDay.fromString(dto.raceStartTime); - const timezone = new LeagueTimezone(dto.timezoneId); - - let recurrence: RecurrenceStrategy; - - if (dto.recurrenceStrategy === 'weekly') { - if (!dto.weekdays || dto.weekdays.length === 0) { - throw new BusinessRuleViolationError('weekdays are required for weekly recurrence'); - } - recurrence = RecurrenceStrategyFactory.weekly(new WeekdaySet(dto.weekdays)); - } else if (dto.recurrenceStrategy === 'everyNWeeks') { - if (!dto.weekdays || dto.weekdays.length === 0) { - throw new BusinessRuleViolationError('weekdays are required for everyNWeeks recurrence'); - } - if (dto.intervalWeeks == null) { - throw new BusinessRuleViolationError( - 'intervalWeeks is required for everyNWeeks recurrence', - ); - } - recurrence = RecurrenceStrategyFactory.everyNWeeks( - dto.intervalWeeks, - new WeekdaySet(dto.weekdays), - ); - } else if (dto.recurrenceStrategy === 'monthlyNthWeekday') { - if (!dto.monthlyOrdinal || !dto.monthlyWeekday) { - throw new BusinessRuleViolationError( - 'monthlyOrdinal and monthlyWeekday are required for monthlyNthWeekday', - ); - } - const pattern = new MonthlyRecurrencePattern(dto.monthlyOrdinal, dto.monthlyWeekday); - recurrence = RecurrenceStrategyFactory.monthlyNthWeekday(pattern); - } else { - throw new BusinessRuleViolationError(`Unknown recurrenceStrategy "${dto.recurrenceStrategy}"`); - } - - return new SeasonSchedule({ - startDate, - timeOfDay, - timezone, - recurrence, - plannedRounds: dto.plannedRounds, - }); -} \ No newline at end of file diff --git a/core/racing/application/dtos/LeagueSummaryDTO.ts b/core/racing/application/dtos/LeagueSummaryDTO.ts deleted file mode 100644 index a3a621755..000000000 --- a/core/racing/application/dtos/LeagueSummaryDTO.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface LeagueSummaryScoringDTO { - gameId: string; - gameName: string; - primaryChampionshipType: 'driver' | 'team' | 'nations' | 'trophy'; - scoringPresetId: string; - scoringPresetName: string; - dropPolicySummary: string; - /** - * Human-readable scoring pattern summary combining preset name and drop policy, - * e.g. "Sprint + Main • Best 6 results of 8 count towards the championship." - */ - scoringPatternSummary: string; -} - -export interface LeagueSummaryDTO { - id: string; - name: string; - description?: string; - createdAt: Date; - ownerId: string; - maxDrivers?: number; - usedDriverSlots?: number; - maxTeams?: number; - usedTeamSlots?: number; - /** - * Human-readable structure summary derived from capacity and (future) team settings, - * e.g. "Solo • 24 drivers" or "Teams • 12 × 2 drivers". - */ - structureSummary?: string; - /** - * Human-readable scoring pattern summary for list views, - * e.g. "Sprint + Main • Best 6 results of 8 count towards the championship." - */ - scoringPatternSummary?: string; - /** - * Human-readable timing summary for list views, - * e.g. "30 min Quali • 40 min Race". - */ - timingSummary?: string; - scoring?: LeagueSummaryScoringDTO; -} \ No newline at end of file diff --git a/core/racing/application/dtos/RaceRegistrationQueryDTO.ts b/core/racing/application/dtos/RaceRegistrationQueryDTO.ts deleted file mode 100644 index dc57baa0e..000000000 --- a/core/racing/application/dtos/RaceRegistrationQueryDTO.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface IsDriverRegisteredForRaceQueryParamsDTO { - raceId: string; - driverId: string; -} - -export interface GetRaceRegistrationsQueryParamsDTO { - raceId: string; -} \ No newline at end of file diff --git a/core/racing/application/index.ts b/core/racing/application/index.ts index 172265d9e..62ca1e56f 100644 --- a/core/racing/application/index.ts +++ b/core/racing/application/index.ts @@ -66,7 +66,6 @@ export type { TeamMembershipStatus, } from '../domain/types/TeamMembership'; -export type { DriverDTO } from './dto/DriverDTO'; export type { LeagueDTO } from './dto/LeagueDTO'; export type { RaceDTO } from './dto/RaceDTO'; export type { ResultDTO } from './dto/ResultDTO'; @@ -76,10 +75,8 @@ export type { LeagueScheduleDTO, LeagueSchedulePreviewDTO, } from './dto/LeagueScheduleDTO'; -export type { - ChampionshipStandingsDTO, - ChampionshipStandingsRowDTO, -} from './dto/ChampionshipStandingsDTO'; +export type { ChampionshipStandingsOutputPort } from './ports/output/ChampionshipStandingsOutputPort'; +export type { ChampionshipStandingsRowOutputPort } from './ports/output/ChampionshipStandingsRowOutputPort'; export type { LeagueConfigFormModel, LeagueStructureFormDTO, diff --git a/core/racing/application/mappers/EntityMappers.ts b/core/racing/application/mappers/EntityMappers.ts deleted file mode 100644 index 5df4b7092..000000000 --- a/core/racing/application/mappers/EntityMappers.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Application Layer: Entity to DTO Mappers - * - * Transforms domain entities to plain objects for crossing architectural boundaries. - * These mappers handle the Server Component -> Client Component boundary in Next.js 15. - */ - -import { Driver } from '../../domain/entities/Driver'; -import { League } from '../../domain/entities/League'; -import { Race } from '../../domain/entities/Race'; -import { Result } from '../../domain/entities/Result'; -import { Standing } from '../../domain/entities/Standing'; -import type { DriverDTO } from '../dto/DriverDTO'; -import type { LeagueDTO } from '../dto/LeagueDTO'; -import type { RaceDTO } from '../dto/RaceDTO'; -import type { ResultDTO } from '../dto/ResultDTO'; -import type { StandingDTO } from '../dto/StandingDTO'; - -export class EntityMappers { - static toDriverDTO(driver: Driver | null): DriverDTO | null { - if (!driver) return null; - return { - id: driver.id, - iracingId: driver.iracingId, - name: driver.name, - country: driver.country, - bio: driver.bio ?? '', - joinedAt: driver.joinedAt.toISOString(), - }; - } - - static toLeagueDTO(league: League | null): LeagueDTO | null { - if (!league) return null; - - const socialLinks = - league.socialLinks !== undefined - ? { - ...(league.socialLinks.discordUrl !== undefined - ? { discordUrl: league.socialLinks.discordUrl } - : {}), - ...(league.socialLinks.youtubeUrl !== undefined - ? { youtubeUrl: league.socialLinks.youtubeUrl } - : {}), - ...(league.socialLinks.websiteUrl !== undefined - ? { websiteUrl: league.socialLinks.websiteUrl } - : {}), - } - : undefined; - - return { - id: league.id, - name: league.name, - description: league.description, - ownerId: league.ownerId, - settings: league.settings, - createdAt: league.createdAt.toISOString(), - ...(socialLinks !== undefined ? { socialLinks } : {}), - }; - } - - static toLeagueDTOs(leagues: League[]): LeagueDTO[] { - return leagues.map((league) => { - const socialLinks = - league.socialLinks !== undefined - ? { - ...(league.socialLinks.discordUrl !== undefined - ? { discordUrl: league.socialLinks.discordUrl } - : {}), - ...(league.socialLinks.youtubeUrl !== undefined - ? { youtubeUrl: league.socialLinks.youtubeUrl } - : {}), - ...(league.socialLinks.websiteUrl !== undefined - ? { websiteUrl: league.socialLinks.websiteUrl } - : {}), - } - : undefined; - - return { - id: league.id, - name: league.name, - description: league.description, - ownerId: league.ownerId, - settings: league.settings, - createdAt: league.createdAt.toISOString(), - ...(socialLinks !== undefined ? { socialLinks } : {}), - }; - }); - } - - static toRaceDTO(race: Race | null): RaceDTO | null { - if (!race) return null; - - const sessionTypeMap = { - practice: 'practice' as const, - qualifying: 'qualifying' as const, - q1: 'qualifying' as const, - q2: 'qualifying' as const, - q3: 'qualifying' as const, - sprint: 'race' as const, - main: 'race' as const, - timeTrial: 'practice' as const, - }; - - return { - id: race.id, - leagueId: race.leagueId, - scheduledAt: race.scheduledAt.toISOString(), - track: race.track, - trackId: race.trackId ?? '', - car: race.car, - carId: race.carId ?? '', - sessionType: sessionTypeMap[race.sessionType.value], - status: race.status, - ...(race.strengthOfField !== undefined - ? { strengthOfField: race.strengthOfField } - : {}), - ...(race.registeredCount !== undefined - ? { registeredCount: race.registeredCount } - : {}), - ...(race.maxParticipants !== undefined - ? { maxParticipants: race.maxParticipants } - : {}), - }; - } - - static toRaceDTOs(races: Race[]): RaceDTO[] { - const sessionTypeMap = { - practice: 'practice' as const, - qualifying: 'qualifying' as const, - q1: 'qualifying' as const, - q2: 'qualifying' as const, - q3: 'qualifying' as const, - sprint: 'race' as const, - main: 'race' as const, - timeTrial: 'practice' as const, - }; - - return races.map((race) => ({ - id: race.id, - leagueId: race.leagueId, - scheduledAt: race.scheduledAt.toISOString(), - track: race.track, - trackId: race.trackId ?? '', - car: race.car, - carId: race.carId ?? '', - sessionType: sessionTypeMap[race.sessionType.value], - status: race.status, - ...(race.strengthOfField !== undefined - ? { strengthOfField: race.strengthOfField } - : {}), - ...(race.registeredCount !== undefined - ? { registeredCount: race.registeredCount } - : {}), - ...(race.maxParticipants !== undefined - ? { maxParticipants: race.maxParticipants } - : {}), - })); - } - - static toResultDTO(result: Result | null): ResultDTO | null { - if (!result) return null; - return { - id: result.id, - raceId: result.raceId, - driverId: result.driverId, - position: result.position, - fastestLap: result.fastestLap, - incidents: result.incidents, - startPosition: result.startPosition, - }; - } - - static toResultDTOs(results: Result[]): ResultDTO[] { - return results.map(result => ({ - id: result.id, - raceId: result.raceId, - driverId: result.driverId, - position: result.position, - fastestLap: result.fastestLap, - incidents: result.incidents, - startPosition: result.startPosition, - })); - } - - static toStandingDTO(standing: Standing | null): StandingDTO | null { - if (!standing) return null; - return { - leagueId: standing.leagueId, - driverId: standing.driverId, - points: standing.points, - wins: standing.wins, - position: standing.position, - racesCompleted: standing.racesCompleted, - }; - } - - static toStandingDTOs(standings: Standing[]): StandingDTO[] { - return standings.map(standing => ({ - leagueId: standing.leagueId, - driverId: standing.driverId, - points: standing.points, - wins: standing.wins, - position: standing.position, - racesCompleted: standing.racesCompleted, - })); - } -} \ No newline at end of file diff --git a/core/racing/application/mappers/index.ts b/core/racing/application/mappers/index.ts deleted file mode 100644 index 18f79e8bd..000000000 --- a/core/racing/application/mappers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Mappers for converting between domain entities and DTOs -// Example: driverToDTO, leagueToDTO, etc. \ No newline at end of file diff --git a/core/racing/application/ports/DriverRatingPort.ts b/core/racing/application/ports/DriverRatingPort.ts deleted file mode 100644 index fe47290a5..000000000 --- a/core/racing/application/ports/DriverRatingPort.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface DriverRatingPort { - getRating(driverId: string): { rating: number | null; ratingChange: number | null }; -} \ No newline at end of file diff --git a/core/racing/application/ports/DriverRatingProvider.ts b/core/racing/application/ports/DriverRatingProvider.ts deleted file mode 100644 index 3df6fc051..000000000 --- a/core/racing/application/ports/DriverRatingProvider.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Application Port: DriverRatingProvider - * - * Port for looking up driver ratings. - * Implemented by infrastructure adapters that connect to rating systems. - */ - -export interface DriverRatingProvider { - /** - * Get the rating for a single driver - * Returns null if driver has no rating - */ - getRating(driverId: string): number | null; - - /** - * Get ratings for multiple drivers - * Returns a map of driverId -> rating - */ - getRatings(driverIds: string[]): Map; -} \ No newline at end of file diff --git a/core/racing/application/ports/IImageServicePort.ts b/core/racing/application/ports/IImageServicePort.ts deleted file mode 100644 index 409287137..000000000 --- a/core/racing/application/ports/IImageServicePort.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Application Port: IImageServicePort - * - * Abstraction used by racing application use cases to obtain image URLs - * for drivers, teams and leagues without depending on UI/media layers. - */ -export interface IImageServicePort { - getDriverAvatar(driverId: string): string; - getTeamLogo(teamId: string): string; - getLeagueCover(leagueId: string): string; - getLeagueLogo(leagueId: string): string; -} \ No newline at end of file diff --git a/core/racing/application/ports/ILiveryCompositor.ts b/core/racing/application/ports/ILiveryCompositor.ts deleted file mode 100644 index d970c7e5f..000000000 --- a/core/racing/application/ports/ILiveryCompositor.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Application Port: ILiveryCompositor - * - * Defines interface for livery image composition. - * Infrastructure will provide image processing implementation. - */ - -import type { LiveryDecal } from '../../domain/value-objects/LiveryDecal'; - -export interface CompositionResult { - success: boolean; - composedImageUrl?: string; - error?: string; - timestamp: Date; -} - -export interface ILiveryCompositor { - /** - * Composite a livery by layering decals on base image - */ - composeLivery( - baseImageUrl: string, - decals: LiveryDecal[] - ): Promise; - - /** - * Generate a livery pack (.zip) for all drivers in a season - */ - generateLiveryPack( - seasonId: string, - liveryData: Array<{ - driverId: string; - driverName: string; - carId: string; - composedImageUrl: string; - }> - ): Promise; - - /** - * Validate livery image (check for logos/text) - */ - validateLiveryImage(imageUrl: string): Promise<{ - isValid: boolean; - violations?: string[]; - }>; -} \ No newline at end of file diff --git a/core/racing/application/ports/ILiveryStorage.ts b/core/racing/application/ports/ILiveryStorage.ts deleted file mode 100644 index 1e1c82c8d..000000000 --- a/core/racing/application/ports/ILiveryStorage.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Application Port: ILiveryStorage - * - * Defines interface for livery image storage. - * Infrastructure will provide cloud storage adapter. - */ - -export interface UploadResult { - success: boolean; - imageUrl?: string; - error?: string; - timestamp: Date; -} - -export interface ILiveryStorage { - /** - * Upload a livery image - */ - upload( - imageData: Buffer | string, - fileName: string, - metadata?: Record - ): Promise; - - /** - * Download a livery image - */ - download(imageUrl: string): Promise; - - /** - * Delete a livery image - */ - delete(imageUrl: string): Promise; - - /** - * Generate a signed URL for temporary access - */ - generateSignedUrl(imageUrl: string, expiresInSeconds: number): Promise; -} \ No newline at end of file diff --git a/core/racing/application/ports/IPaymentGateway.ts b/core/racing/application/ports/IPaymentGateway.ts deleted file mode 100644 index 7524dc47d..000000000 --- a/core/racing/application/ports/IPaymentGateway.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Application Port: IPaymentGateway - * - * Defines interface for payment processing. - * Infrastructure will provide mock or real implementation. - */ - -import type { Money } from '../../domain/value-objects/Money'; - -export interface PaymentResult { - success: boolean; - transactionId?: string; - error?: string; - timestamp: Date; -} - -export interface RefundResult { - success: boolean; - refundId?: string; - error?: string; - timestamp: Date; -} - -export interface IPaymentGateway { - /** - * Process a payment - */ - processPayment( - amount: Money, - payerId: string, - description: string, - metadata?: Record - ): Promise; - - /** - * Refund a payment - */ - refund( - originalTransactionId: string, - amount: Money, - reason: string - ): Promise; - - /** - * Verify payment status - */ - verifyPayment(transactionId: string): Promise; -} \ No newline at end of file diff --git a/core/racing/application/ports/LeagueScoringPresetProvider.ts b/core/racing/application/ports/LeagueScoringPresetProvider.ts deleted file mode 100644 index 3b9f4a674..000000000 --- a/core/racing/application/ports/LeagueScoringPresetProvider.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig'; - -export type LeagueScoringPresetPrimaryChampionshipType = - | 'driver' - | 'team' - | 'nations' - | 'trophy'; - -export interface LeagueScoringPresetDTO { - id: string; - name: string; - description: string; - primaryChampionshipType: LeagueScoringPresetPrimaryChampionshipType; - sessionSummary: string; - bonusSummary: string; - dropPolicySummary: string; -} - -/** - * Provider abstraction for league scoring presets used by application-layer queries. - * - * In-memory implementation is backed by the preset registry in - * InMemoryScoringRepositories. - */ -export interface LeagueScoringPresetProvider { - listPresets(): LeagueScoringPresetDTO[]; - getPresetById(id: string): LeagueScoringPresetDTO | undefined; - createScoringConfigFromPreset(presetId: string, seasonId: string): LeagueScoringConfig; -} \ No newline at end of file diff --git a/core/racing/application/ports/input/GetDriverAvatarInputPort.ts b/core/racing/application/ports/input/GetDriverAvatarInputPort.ts new file mode 100644 index 000000000..3a04c4691 --- /dev/null +++ b/core/racing/application/ports/input/GetDriverAvatarInputPort.ts @@ -0,0 +1,3 @@ +export interface GetDriverAvatarInputPort { + driverId: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/GetDriverRatingInputPort.ts b/core/racing/application/ports/input/GetDriverRatingInputPort.ts new file mode 100644 index 000000000..16d26ff3a --- /dev/null +++ b/core/racing/application/ports/input/GetDriverRatingInputPort.ts @@ -0,0 +1,3 @@ +export interface GetDriverRatingInputPort { + driverId: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/GetEntitySponsorshipPricingInputPort.ts b/core/racing/application/ports/input/GetEntitySponsorshipPricingInputPort.ts new file mode 100644 index 000000000..49ae550d5 --- /dev/null +++ b/core/racing/application/ports/input/GetEntitySponsorshipPricingInputPort.ts @@ -0,0 +1,6 @@ +import type { SponsorableEntityType } from '../../../domain/entities/SponsorshipRequest'; + +export interface GetEntitySponsorshipPricingInputPort { + entityType: SponsorableEntityType; + entityId: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/GetLeagueCoverInputPort.ts b/core/racing/application/ports/input/GetLeagueCoverInputPort.ts new file mode 100644 index 000000000..f694b10fd --- /dev/null +++ b/core/racing/application/ports/input/GetLeagueCoverInputPort.ts @@ -0,0 +1,3 @@ +export interface GetLeagueCoverInputPort { + leagueId: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/GetLeagueLogoInputPort.ts b/core/racing/application/ports/input/GetLeagueLogoInputPort.ts new file mode 100644 index 000000000..308ec5b5a --- /dev/null +++ b/core/racing/application/ports/input/GetLeagueLogoInputPort.ts @@ -0,0 +1,3 @@ +export interface GetLeagueLogoInputPort { + leagueId: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/GetLeagueScoringPresetByIdInputPort.ts b/core/racing/application/ports/input/GetLeagueScoringPresetByIdInputPort.ts new file mode 100644 index 000000000..aa42b9c52 --- /dev/null +++ b/core/racing/application/ports/input/GetLeagueScoringPresetByIdInputPort.ts @@ -0,0 +1,3 @@ +export interface GetLeagueScoringPresetByIdInputPort { + presetId: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/GetTeamLogoInputPort.ts b/core/racing/application/ports/input/GetTeamLogoInputPort.ts new file mode 100644 index 000000000..de7b69cae --- /dev/null +++ b/core/racing/application/ports/input/GetTeamLogoInputPort.ts @@ -0,0 +1,3 @@ +export interface GetTeamLogoInputPort { + teamId: string; +} \ No newline at end of file diff --git a/core/racing/application/dtos/LeagueVisibilityInput.ts b/core/racing/application/ports/input/LeagueVisibilityInputPort.ts similarity index 71% rename from core/racing/application/dtos/LeagueVisibilityInput.ts rename to core/racing/application/ports/input/LeagueVisibilityInputPort.ts index 1e2f58e11..fa1473e54 100644 --- a/core/racing/application/dtos/LeagueVisibilityInput.ts +++ b/core/racing/application/ports/input/LeagueVisibilityInputPort.ts @@ -3,4 +3,4 @@ * - 'ranked' (or legacy 'public'): Competitive, public, affects driver ratings. Min 10 drivers. * - 'unranked' (or legacy 'private'): Casual with friends, no rating impact. */ -export type LeagueVisibilityInput = 'ranked' | 'unranked' | 'public' | 'private'; \ No newline at end of file +export type LeagueVisibilityInputPort = 'ranked' | 'unranked' | 'public' | 'private'; \ No newline at end of file diff --git a/core/racing/application/ports/input/ListLeagueScoringPresetsInputPort.ts b/core/racing/application/ports/input/ListLeagueScoringPresetsInputPort.ts new file mode 100644 index 000000000..d8853845f --- /dev/null +++ b/core/racing/application/ports/input/ListLeagueScoringPresetsInputPort.ts @@ -0,0 +1,3 @@ +export interface ListLeagueScoringPresetsInputPort { + // Empty interface for query with no parameters +} \ No newline at end of file diff --git a/core/racing/application/ports/input/ProcessPaymentInputPort.ts b/core/racing/application/ports/input/ProcessPaymentInputPort.ts new file mode 100644 index 000000000..e78ee75d3 --- /dev/null +++ b/core/racing/application/ports/input/ProcessPaymentInputPort.ts @@ -0,0 +1,6 @@ +export interface ProcessPaymentInputPort { + amount: number; // in cents + payerId: string; + description: string; + metadata?: Record; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/RaceRegistrationInputPort.ts b/core/racing/application/ports/input/RaceRegistrationInputPort.ts new file mode 100644 index 000000000..ba37d0c04 --- /dev/null +++ b/core/racing/application/ports/input/RaceRegistrationInputPort.ts @@ -0,0 +1,8 @@ +export interface IsDriverRegisteredForRaceInputPort { + raceId: string; + driverId: string; +} + +export interface GetRaceRegistrationsInputPort { + raceId: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/RefundPaymentInputPort.ts b/core/racing/application/ports/input/RefundPaymentInputPort.ts new file mode 100644 index 000000000..ffe936a9b --- /dev/null +++ b/core/racing/application/ports/input/RefundPaymentInputPort.ts @@ -0,0 +1,7 @@ +import type { Money } from '../../domain/value-objects/Money'; + +export interface RefundPaymentInputPort { + originalTransactionId: string; + amount: Money; + reason: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/input/VerifyPaymentInputPort.ts b/core/racing/application/ports/input/VerifyPaymentInputPort.ts new file mode 100644 index 000000000..281c5395a --- /dev/null +++ b/core/racing/application/ports/input/VerifyPaymentInputPort.ts @@ -0,0 +1,3 @@ +export interface VerifyPaymentInputPort { + transactionId: string; +} \ No newline at end of file diff --git a/core/racing/application/dtos/AcceptSponsorshipRequestResultDTO.ts b/core/racing/application/ports/output/AcceptSponsorshipOutputPort.ts similarity index 71% rename from core/racing/application/dtos/AcceptSponsorshipRequestResultDTO.ts rename to core/racing/application/ports/output/AcceptSponsorshipOutputPort.ts index 9f18b315b..f9cfe47d4 100644 --- a/core/racing/application/dtos/AcceptSponsorshipRequestResultDTO.ts +++ b/core/racing/application/ports/output/AcceptSponsorshipOutputPort.ts @@ -1,4 +1,4 @@ -export interface AcceptSponsorshipRequestResultDTO { +export interface AcceptSponsorshipOutputPort { requestId: string; sponsorshipId: string; status: 'accepted'; diff --git a/core/racing/application/dtos/ApplyForSponsorshipResultDTO.ts b/core/racing/application/ports/output/ApplyForSponsorshipResultPort.ts similarity index 55% rename from core/racing/application/dtos/ApplyForSponsorshipResultDTO.ts rename to core/racing/application/ports/output/ApplyForSponsorshipResultPort.ts index 8e26c3fe2..05d0e5076 100644 --- a/core/racing/application/dtos/ApplyForSponsorshipResultDTO.ts +++ b/core/racing/application/ports/output/ApplyForSponsorshipResultPort.ts @@ -1,4 +1,4 @@ -export interface ApplyForSponsorshipResultDTO { +export interface ApplyForSponsorshipResultPort { requestId: string; status: 'pending'; createdAt: Date; diff --git a/core/racing/application/ports/output/ApproveLeagueJoinRequestResultPort.ts b/core/racing/application/ports/output/ApproveLeagueJoinRequestResultPort.ts new file mode 100644 index 000000000..116a0f58c --- /dev/null +++ b/core/racing/application/ports/output/ApproveLeagueJoinRequestResultPort.ts @@ -0,0 +1,4 @@ +export interface ApproveLeagueJoinRequestResultPort { + success: boolean; + message: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/ChampionshipStandingsOutputPort.ts b/core/racing/application/ports/output/ChampionshipStandingsOutputPort.ts new file mode 100644 index 000000000..6d3e2b61b --- /dev/null +++ b/core/racing/application/ports/output/ChampionshipStandingsOutputPort.ts @@ -0,0 +1,8 @@ +import type { ChampionshipStandingsRowOutputPort } from './ChampionshipStandingsRowOutputPort'; + +export interface ChampionshipStandingsOutputPort { + seasonId: string; + championshipId: string; + championshipName: string; + rows: ChampionshipStandingsRowOutputPort[]; +} \ No newline at end of file diff --git a/core/racing/application/dtos/ChampionshipStandingsDTO.ts b/core/racing/application/ports/output/ChampionshipStandingsRowOutputPort.ts similarity index 50% rename from core/racing/application/dtos/ChampionshipStandingsDTO.ts rename to core/racing/application/ports/output/ChampionshipStandingsRowOutputPort.ts index 4f64e068f..c5fba3239 100644 --- a/core/racing/application/dtos/ChampionshipStandingsDTO.ts +++ b/core/racing/application/ports/output/ChampionshipStandingsRowOutputPort.ts @@ -1,16 +1,9 @@ import type { ParticipantRef } from '@core/racing/domain/types/ParticipantRef'; -export interface ChampionshipStandingsRowDTO { +export interface ChampionshipStandingsRowOutputPort { participant: ParticipantRef; position: number; totalPoints: number; resultsCounted: number; resultsDropped: number; -} - -export interface ChampionshipStandingsDTO { - seasonId: string; - championshipId: string; - championshipName: string; - rows: ChampionshipStandingsRowDTO[]; } \ No newline at end of file diff --git a/core/racing/application/dtos/CreateLeagueWithSeasonAndScoringResultDTO.ts b/core/racing/application/ports/output/CreateLeagueWithSeasonAndScoringOutputPort.ts similarity index 61% rename from core/racing/application/dtos/CreateLeagueWithSeasonAndScoringResultDTO.ts rename to core/racing/application/ports/output/CreateLeagueWithSeasonAndScoringOutputPort.ts index 34c1aebd6..6b9a99459 100644 --- a/core/racing/application/dtos/CreateLeagueWithSeasonAndScoringResultDTO.ts +++ b/core/racing/application/ports/output/CreateLeagueWithSeasonAndScoringOutputPort.ts @@ -1,4 +1,4 @@ -export interface CreateLeagueWithSeasonAndScoringResultDTO { +export interface CreateLeagueWithSeasonAndScoringOutputPort { leagueId: string; seasonId: string; scoringPresetId?: string; diff --git a/core/racing/application/dtos/CreateSponsorResultDTO.ts b/core/racing/application/ports/output/CreateSponsorOutputPort.ts similarity index 77% rename from core/racing/application/dtos/CreateSponsorResultDTO.ts rename to core/racing/application/ports/output/CreateSponsorOutputPort.ts index 04e31cf60..bf3fa2a9c 100644 --- a/core/racing/application/dtos/CreateSponsorResultDTO.ts +++ b/core/racing/application/ports/output/CreateSponsorOutputPort.ts @@ -1,4 +1,4 @@ -export interface CreateSponsorResultDTO { +export interface CreateSponsorOutputPort { sponsor: { id: string; name: string; diff --git a/core/racing/application/ports/output/CreateTeamOutputPort.ts b/core/racing/application/ports/output/CreateTeamOutputPort.ts new file mode 100644 index 000000000..ebca1f65c --- /dev/null +++ b/core/racing/application/ports/output/CreateTeamOutputPort.ts @@ -0,0 +1,5 @@ +import type { Team } from '../../../domain/entities/Team'; + +export interface CreateTeamOutputPort { + team: Team; +} \ No newline at end of file diff --git a/core/racing/application/dtos/DriverDTO.ts b/core/racing/application/ports/output/DriverOutputPort.ts similarity index 74% rename from core/racing/application/dtos/DriverDTO.ts rename to core/racing/application/ports/output/DriverOutputPort.ts index 7a6eb856d..1c953bb67 100644 --- a/core/racing/application/dtos/DriverDTO.ts +++ b/core/racing/application/ports/output/DriverOutputPort.ts @@ -1,8 +1,8 @@ -export type DriverDTO = { +export interface DriverOutputPort { id: string; iracingId: string; name: string; country: string; bio?: string; joinedAt: string; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/core/racing/application/ports/output/GetAllTeamsOutputPort.ts b/core/racing/application/ports/output/GetAllTeamsOutputPort.ts new file mode 100644 index 000000000..6ae68acc9 --- /dev/null +++ b/core/racing/application/ports/output/GetAllTeamsOutputPort.ts @@ -0,0 +1,3 @@ +import type { Team } from '../../../domain/entities/Team'; + +export type GetAllTeamsOutputPort = Team[]; \ No newline at end of file diff --git a/core/racing/application/ports/output/GetDriverAvatarOutputPort.ts b/core/racing/application/ports/output/GetDriverAvatarOutputPort.ts new file mode 100644 index 000000000..0fc7fb4ff --- /dev/null +++ b/core/racing/application/ports/output/GetDriverAvatarOutputPort.ts @@ -0,0 +1,3 @@ +export interface GetDriverAvatarOutputPort { + avatarUrl: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/GetDriverRatingOutputPort.ts b/core/racing/application/ports/output/GetDriverRatingOutputPort.ts new file mode 100644 index 000000000..eb8663618 --- /dev/null +++ b/core/racing/application/ports/output/GetDriverRatingOutputPort.ts @@ -0,0 +1,4 @@ +export interface GetDriverRatingOutputPort { + rating: number | null; + ratingChange: number | null; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/GetDriverTeamOutputPort.ts b/core/racing/application/ports/output/GetDriverTeamOutputPort.ts new file mode 100644 index 000000000..53c0bc759 --- /dev/null +++ b/core/racing/application/ports/output/GetDriverTeamOutputPort.ts @@ -0,0 +1,7 @@ +import type { Team } from '../../../domain/entities/Team'; +import type { TeamMembership } from '../../../domain/types/TeamMembership'; + +export interface GetDriverTeamOutputPort { + team: Team; + membership: TeamMembership; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/GetEntitySponsorshipPricingOutputPort.ts b/core/racing/application/ports/output/GetEntitySponsorshipPricingOutputPort.ts new file mode 100644 index 000000000..48bae6c7b --- /dev/null +++ b/core/racing/application/ports/output/GetEntitySponsorshipPricingOutputPort.ts @@ -0,0 +1,11 @@ +import type { SponsorableEntityType } from '../../../domain/entities/SponsorshipRequest'; +import type { SponsorshipSlotDTO } from './SponsorshipSlotOutputPort'; + +export interface GetEntitySponsorshipPricingOutputPort { + entityType: SponsorableEntityType; + entityId: string; + acceptingApplications: boolean; + customRequirements?: string; + mainSlot?: SponsorshipSlotDTO; + secondarySlot?: SponsorshipSlotDTO; +} \ No newline at end of file diff --git a/core/racing/application/dtos/GetLeagueAdminResultDTO.ts b/core/racing/application/ports/output/GetLeagueAdminOutputPort.ts similarity index 74% rename from core/racing/application/dtos/GetLeagueAdminResultDTO.ts rename to core/racing/application/ports/output/GetLeagueAdminOutputPort.ts index 23010d5c3..6d8338113 100644 --- a/core/racing/application/dtos/GetLeagueAdminResultDTO.ts +++ b/core/racing/application/ports/output/GetLeagueAdminOutputPort.ts @@ -1,4 +1,4 @@ -export interface GetLeagueAdminResultDTO { +export interface GetLeagueAdminOutputPort { league: { id: string; ownerId: string; diff --git a/core/racing/application/dtos/GetLeagueAdminPermissionsResultDTO.ts b/core/racing/application/ports/output/GetLeagueAdminPermissionsOutputPort.ts similarity index 50% rename from core/racing/application/dtos/GetLeagueAdminPermissionsResultDTO.ts rename to core/racing/application/ports/output/GetLeagueAdminPermissionsOutputPort.ts index a712d8d6a..45b35179b 100644 --- a/core/racing/application/dtos/GetLeagueAdminPermissionsResultDTO.ts +++ b/core/racing/application/ports/output/GetLeagueAdminPermissionsOutputPort.ts @@ -1,4 +1,4 @@ -export interface GetLeagueAdminPermissionsResultDTO { +export interface GetLeagueAdminPermissionsOutputPort { canRemoveMember: boolean; canUpdateRoles: boolean; } \ No newline at end of file diff --git a/core/racing/application/ports/output/GetLeagueCoverOutputPort.ts b/core/racing/application/ports/output/GetLeagueCoverOutputPort.ts new file mode 100644 index 000000000..db1187e32 --- /dev/null +++ b/core/racing/application/ports/output/GetLeagueCoverOutputPort.ts @@ -0,0 +1,3 @@ +export interface GetLeagueCoverOutputPort { + coverUrl: string; +} \ No newline at end of file diff --git a/core/racing/application/dtos/GetLeagueJoinRequestsResultDTO.ts b/core/racing/application/ports/output/GetLeagueJoinRequestsOutputPort.ts similarity index 79% rename from core/racing/application/dtos/GetLeagueJoinRequestsResultDTO.ts rename to core/racing/application/ports/output/GetLeagueJoinRequestsOutputPort.ts index 2c9143c61..7a4e5bafa 100644 --- a/core/racing/application/dtos/GetLeagueJoinRequestsResultDTO.ts +++ b/core/racing/application/ports/output/GetLeagueJoinRequestsOutputPort.ts @@ -1,4 +1,4 @@ -export interface GetLeagueJoinRequestsResultDTO { +export interface GetLeagueJoinRequestsOutputPort { joinRequests: Array<{ id: string; leagueId: string; diff --git a/core/racing/application/ports/output/GetLeagueLogoOutputPort.ts b/core/racing/application/ports/output/GetLeagueLogoOutputPort.ts new file mode 100644 index 000000000..e7af07fc9 --- /dev/null +++ b/core/racing/application/ports/output/GetLeagueLogoOutputPort.ts @@ -0,0 +1,3 @@ +export interface GetLeagueLogoOutputPort { + logoUrl: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/GetLeagueMembershipsOutputPort.ts b/core/racing/application/ports/output/GetLeagueMembershipsOutputPort.ts new file mode 100644 index 000000000..d446adfed --- /dev/null +++ b/core/racing/application/ports/output/GetLeagueMembershipsOutputPort.ts @@ -0,0 +1,6 @@ +import type { LeagueMembership } from '../../../domain/entities/LeagueMembership'; + +export interface GetLeagueMembershipsOutputPort { + memberships: LeagueMembership[]; + drivers: { id: string; name: string }[]; +} \ No newline at end of file diff --git a/core/racing/application/dtos/GetLeagueOwnerSummaryResultDTO.ts b/core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort.ts similarity index 64% rename from core/racing/application/dtos/GetLeagueOwnerSummaryResultDTO.ts rename to core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort.ts index f8edd157a..320db097c 100644 --- a/core/racing/application/dtos/GetLeagueOwnerSummaryResultDTO.ts +++ b/core/racing/application/ports/output/GetLeagueOwnerSummaryOutputPort.ts @@ -1,3 +1,3 @@ -export interface GetLeagueOwnerSummaryResultDTO { +export interface GetLeagueOwnerSummaryOutputPort { summary: { driver: { id: string; name: string }; rating: number; rank: number } | null; } \ No newline at end of file diff --git a/core/racing/application/ports/output/GetLeagueProtestsOutputPort.ts b/core/racing/application/ports/output/GetLeagueProtestsOutputPort.ts new file mode 100644 index 000000000..8c0d1e8e1 --- /dev/null +++ b/core/racing/application/ports/output/GetLeagueProtestsOutputPort.ts @@ -0,0 +1,18 @@ +import type { ProtestOutputPort } from './ProtestOutputPort'; + +export interface RaceOutputPort { + id: string; + name: string; + date: string; +} + +export interface DriverOutputPort { + id: string; + name: string; +} + +export interface GetLeagueProtestsOutputPort { + protests: ProtestOutputPort[]; + races: RaceOutputPort[]; + drivers: DriverOutputPort[]; +} \ No newline at end of file diff --git a/core/racing/application/dtos/GetLeagueScheduleResultDTO.ts b/core/racing/application/ports/output/GetLeagueScheduleOutputPort.ts similarity index 63% rename from core/racing/application/dtos/GetLeagueScheduleResultDTO.ts rename to core/racing/application/ports/output/GetLeagueScheduleOutputPort.ts index 502844671..816075048 100644 --- a/core/racing/application/dtos/GetLeagueScheduleResultDTO.ts +++ b/core/racing/application/ports/output/GetLeagueScheduleOutputPort.ts @@ -1,4 +1,4 @@ -export interface GetLeagueScheduleResultDTO { +export interface GetLeagueScheduleOutputPort { races: Array<{ id: string; name: string; diff --git a/core/racing/application/ports/output/GetTeamDetailsOutputPort.ts b/core/racing/application/ports/output/GetTeamDetailsOutputPort.ts new file mode 100644 index 000000000..e97e6ef10 --- /dev/null +++ b/core/racing/application/ports/output/GetTeamDetailsOutputPort.ts @@ -0,0 +1,7 @@ +import type { Team } from '../../../domain/entities/Team'; +import type { TeamMembership } from '../../../domain/types/TeamMembership'; + +export interface GetTeamDetailsOutputPort { + team: Team; + membership: TeamMembership | null; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/GetTeamLogoOutputPort.ts b/core/racing/application/ports/output/GetTeamLogoOutputPort.ts new file mode 100644 index 000000000..c9c457864 --- /dev/null +++ b/core/racing/application/ports/output/GetTeamLogoOutputPort.ts @@ -0,0 +1,3 @@ +export interface GetTeamLogoOutputPort { + logoUrl: string; +} \ No newline at end of file diff --git a/core/racing/application/dtos/LeagueDriverSeasonStatsDTO.ts b/core/racing/application/ports/output/LeagueDriverSeasonStatsOutputPort.ts similarity index 88% rename from core/racing/application/dtos/LeagueDriverSeasonStatsDTO.ts rename to core/racing/application/ports/output/LeagueDriverSeasonStatsOutputPort.ts index 442af8ff5..3edff3ce1 100644 --- a/core/racing/application/dtos/LeagueDriverSeasonStatsDTO.ts +++ b/core/racing/application/ports/output/LeagueDriverSeasonStatsOutputPort.ts @@ -1,4 +1,4 @@ -export type LeagueDriverSeasonStatsDTO = { +export interface LeagueDriverSeasonStatsOutputPort { leagueId: string; driverId: string; position: number; @@ -17,4 +17,4 @@ export type LeagueDriverSeasonStatsDTO = { avgFinish: number | null; rating: number | null; ratingChange: number | null; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/core/racing/application/dtos/LeagueDTO.ts b/core/racing/application/ports/output/LeagueOutputPort.ts similarity index 69% rename from core/racing/application/dtos/LeagueDTO.ts rename to core/racing/application/ports/output/LeagueOutputPort.ts index feb316e78..4f176f465 100644 --- a/core/racing/application/dtos/LeagueDTO.ts +++ b/core/racing/application/ports/output/LeagueOutputPort.ts @@ -1,4 +1,4 @@ -export type LeagueDTO = { +export interface LeagueOutputPort { id: string; name: string; description: string; @@ -16,9 +16,5 @@ export type LeagueDTO = { youtubeUrl?: string; websiteUrl?: string; }; - /** - * Number of active driver slots currently used in this league. - * Populated by capacity-aware queries such as GetAllLeaguesWithCapacityQuery. - */ usedSlots?: number; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/core/racing/application/ports/output/LeagueScheduleOutputPort.ts b/core/racing/application/ports/output/LeagueScheduleOutputPort.ts new file mode 100644 index 000000000..a7d45c6e1 --- /dev/null +++ b/core/racing/application/ports/output/LeagueScheduleOutputPort.ts @@ -0,0 +1,18 @@ +import type { Weekday } from '../../../domain/types/Weekday'; + +export interface LeagueScheduleOutputPort { + seasonStartDate: string; + raceStartTime: string; + timezoneId: string; + recurrenceStrategy: 'weekly' | 'everyNWeeks' | 'monthlyNthWeekday'; + intervalWeeks?: number; + weekdays?: Weekday[]; + monthlyOrdinal?: 1 | 2 | 3 | 4; + monthlyWeekday?: Weekday; + plannedRounds: number; +} + +export interface LeagueSchedulePreviewOutputPort { + rounds: Array<{ roundNumber: number; scheduledAt: string; timezoneId: string }>; + summary: string; +} \ No newline at end of file diff --git a/core/racing/application/dtos/LeagueScoringConfigDTO.ts b/core/racing/application/ports/output/LeagueScoringConfigOutputPort.ts similarity index 72% rename from core/racing/application/dtos/LeagueScoringConfigDTO.ts rename to core/racing/application/ports/output/LeagueScoringConfigOutputPort.ts index 3a20d36fe..f168453a1 100644 --- a/core/racing/application/dtos/LeagueScoringConfigDTO.ts +++ b/core/racing/application/ports/output/LeagueScoringConfigOutputPort.ts @@ -1,4 +1,4 @@ -export interface LeagueScoringChampionshipDTO { +export interface LeagueScoringChampionshipOutputPort { id: string; name: string; type: 'driver' | 'team' | 'nations' | 'trophy'; @@ -8,7 +8,7 @@ export interface LeagueScoringChampionshipDTO { dropPolicyDescription: string; } -export interface LeagueScoringConfigDTO { +export interface LeagueScoringConfigOutputPort { leagueId: string; seasonId: string; gameId: string; @@ -16,5 +16,5 @@ export interface LeagueScoringConfigDTO { scoringPresetId?: string; scoringPresetName?: string; dropPolicySummary: string; - championships: LeagueScoringChampionshipDTO[]; + championships: LeagueScoringChampionshipOutputPort[]; } \ No newline at end of file diff --git a/core/racing/application/ports/output/LeagueScoringPresetOutputPort.ts b/core/racing/application/ports/output/LeagueScoringPresetOutputPort.ts new file mode 100644 index 000000000..f47fa3ae1 --- /dev/null +++ b/core/racing/application/ports/output/LeagueScoringPresetOutputPort.ts @@ -0,0 +1,15 @@ +export type LeagueScoringPresetPrimaryChampionshipType = + | 'driver' + | 'team' + | 'nations' + | 'trophy'; + +export interface LeagueScoringPresetOutputPort { + id: string; + name: string; + description: string; + primaryChampionshipType: LeagueScoringPresetPrimaryChampionshipType; + sessionSummary: string; + bonusSummary: string; + dropPolicySummary: string; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/LeagueSummaryOutputPort.ts b/core/racing/application/ports/output/LeagueSummaryOutputPort.ts new file mode 100644 index 000000000..6e73c4dca --- /dev/null +++ b/core/racing/application/ports/output/LeagueSummaryOutputPort.ts @@ -0,0 +1,25 @@ +export interface LeagueSummaryScoringOutputPort { + gameId: string; + gameName: string; + primaryChampionshipType: 'driver' | 'team' | 'nations' | 'trophy'; + scoringPresetId: string; + scoringPresetName: string; + dropPolicySummary: string; + scoringPatternSummary: string; +} + +export interface LeagueSummaryOutputPort { + id: string; + name: string; + description?: string; + createdAt: Date; + ownerId: string; + maxDrivers?: number; + usedDriverSlots?: number; + maxTeams?: number; + usedTeamSlots?: number; + structureSummary?: string; + scoringPatternSummary?: string; + timingSummary?: string; + scoring?: LeagueSummaryScoringOutputPort; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/ProcessPaymentOutputPort.ts b/core/racing/application/ports/output/ProcessPaymentOutputPort.ts new file mode 100644 index 000000000..7b6bd41a5 --- /dev/null +++ b/core/racing/application/ports/output/ProcessPaymentOutputPort.ts @@ -0,0 +1,6 @@ +export interface ProcessPaymentOutputPort { + success: boolean; + transactionId?: string; + error?: string; + timestamp: Date; +} \ No newline at end of file diff --git a/core/racing/application/ports/output/ProtestOutputPort.ts b/core/racing/application/ports/output/ProtestOutputPort.ts new file mode 100644 index 000000000..0c4302995 --- /dev/null +++ b/core/racing/application/ports/output/ProtestOutputPort.ts @@ -0,0 +1,9 @@ +export interface ProtestOutputPort { + id: string; + raceId: string; + protestingDriverId: string; + accusedDriverId: string; + submittedAt: Date; + description: string; + status: string; +} \ No newline at end of file diff --git a/core/racing/application/dtos/RaceDTO.ts b/core/racing/application/ports/output/RaceOutputPort.ts similarity index 90% rename from core/racing/application/dtos/RaceDTO.ts rename to core/racing/application/ports/output/RaceOutputPort.ts index f1fa98e80..03de80943 100644 --- a/core/racing/application/dtos/RaceDTO.ts +++ b/core/racing/application/ports/output/RaceOutputPort.ts @@ -1,4 +1,4 @@ -export type RaceDTO = { +export interface RaceOutputPort { id: string; leagueId: string; scheduledAt: string; @@ -11,4 +11,4 @@ export type RaceDTO = { strengthOfField?: number; registeredCount?: number; maxParticipants?: number; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/core/racing/application/ports/output/RefundPaymentOutputPort.ts b/core/racing/application/ports/output/RefundPaymentOutputPort.ts new file mode 100644 index 000000000..8a217cebd --- /dev/null +++ b/core/racing/application/ports/output/RefundPaymentOutputPort.ts @@ -0,0 +1,6 @@ +export interface RefundPaymentOutputPort { + success: boolean; + refundId?: string; + error?: string; + timestamp: Date; +} \ No newline at end of file diff --git a/core/racing/application/dtos/ResultDTO.ts b/core/racing/application/ports/output/ResultOutputPort.ts similarity index 79% rename from core/racing/application/dtos/ResultDTO.ts rename to core/racing/application/ports/output/ResultOutputPort.ts index d316cba2c..bead31e99 100644 --- a/core/racing/application/dtos/ResultDTO.ts +++ b/core/racing/application/ports/output/ResultOutputPort.ts @@ -1,4 +1,4 @@ -export type ResultDTO = { +export interface ResultOutputPort { id: string; raceId: string; driverId: string; @@ -6,4 +6,4 @@ export type ResultDTO = { fastestLap: number; incidents: number; startPosition: number; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/core/racing/application/dtos/SponsorshipSlotDTO.ts b/core/racing/application/ports/output/SponsorshipSlotOutputPort.ts similarity index 61% rename from core/racing/application/dtos/SponsorshipSlotDTO.ts rename to core/racing/application/ports/output/SponsorshipSlotOutputPort.ts index 3bfeacd70..386be8f03 100644 --- a/core/racing/application/dtos/SponsorshipSlotDTO.ts +++ b/core/racing/application/ports/output/SponsorshipSlotOutputPort.ts @@ -1,6 +1,6 @@ -import type { SponsorshipTier } from '../../domain/entities/SeasonSponsorship'; +import type { SponsorshipTier } from '../../../domain/entities/SeasonSponsorship'; -export interface SponsorshipSlotDTO { +export interface SponsorshipSlotOutputPort { tier: SponsorshipTier; price: number; currency: string; diff --git a/core/racing/application/dtos/StandingDTO.ts b/core/racing/application/ports/output/StandingOutputPort.ts similarity index 75% rename from core/racing/application/dtos/StandingDTO.ts rename to core/racing/application/ports/output/StandingOutputPort.ts index bd85a36a8..8e9b1a158 100644 --- a/core/racing/application/dtos/StandingDTO.ts +++ b/core/racing/application/ports/output/StandingOutputPort.ts @@ -1,8 +1,8 @@ -export type StandingDTO = { +export interface StandingOutputPort { leagueId: string; driverId: string; points: number; wins: number; position: number; racesCompleted: number; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/core/racing/application/ports/output/VerifyPaymentOutputPort.ts b/core/racing/application/ports/output/VerifyPaymentOutputPort.ts new file mode 100644 index 000000000..433db40e9 --- /dev/null +++ b/core/racing/application/ports/output/VerifyPaymentOutputPort.ts @@ -0,0 +1,6 @@ +export interface VerifyPaymentOutputPort { + success: boolean; + transactionId?: string; + error?: string; + timestamp: Date; +} \ No newline at end of file diff --git a/core/racing/application/presenters/IAllLeaguesWithCapacityAndScoringPresenter.ts b/core/racing/application/presenters/IAllLeaguesWithCapacityAndScoringPresenter.ts index 70d179769..c83102d74 100644 --- a/core/racing/application/presenters/IAllLeaguesWithCapacityAndScoringPresenter.ts +++ b/core/racing/application/presenters/IAllLeaguesWithCapacityAndScoringPresenter.ts @@ -2,7 +2,7 @@ import type { League } from '../../domain/entities/League'; import type { Season } from '../../domain/entities/Season'; import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig'; import type { Game } from '../../domain/entities/Game'; -import type { LeagueScoringPresetDTO } from '../ports/LeagueScoringPresetProvider'; +import type { LeagueScoringPresetOutputPort } from '../ports/output/LeagueScoringPresetOutputPort'; import type { Presenter } from '@core/shared/presentation'; export interface LeagueSummaryViewModel { @@ -40,7 +40,7 @@ export interface LeagueEnrichedData { season?: Season; scoringConfig?: LeagueScoringConfig; game?: Game; - preset?: LeagueScoringPresetDTO; + preset?: LeagueScoringPresetOutputPort; } export interface IAllLeaguesWithCapacityAndScoringPresenter diff --git a/core/racing/application/presenters/IApproveLeagueJoinRequestPresenter.ts b/core/racing/application/presenters/IApproveLeagueJoinRequestPresenter.ts index 64ad292af..40b698fd0 100644 --- a/core/racing/application/presenters/IApproveLeagueJoinRequestPresenter.ts +++ b/core/racing/application/presenters/IApproveLeagueJoinRequestPresenter.ts @@ -5,9 +5,9 @@ export interface ApproveLeagueJoinRequestViewModel { message: string; } -export interface ApproveLeagueJoinRequestResultDTO { +export interface ApproveLeagueJoinRequestResultPort { success: boolean; message: string; } -export interface IApproveLeagueJoinRequestPresenter extends Presenter {} \ No newline at end of file +export interface IApproveLeagueJoinRequestPresenter extends Presenter {} \ No newline at end of file diff --git a/core/racing/application/presenters/ICreateSponsorPresenter.ts b/core/racing/application/presenters/ICreateSponsorPresenter.ts index 453e4216b..1dbbc19c6 100644 --- a/core/racing/application/presenters/ICreateSponsorPresenter.ts +++ b/core/racing/application/presenters/ICreateSponsorPresenter.ts @@ -13,8 +13,8 @@ export interface CreateSponsorViewModel { sponsor: SponsorDto; } -export interface CreateSponsorResultDTO { +export interface CreateSponsorOutputPort { sponsor: SponsorDto; } -export interface ICreateSponsorPresenter extends Presenter {} \ No newline at end of file +export interface ICreateSponsorPresenter extends Presenter {} \ No newline at end of file diff --git a/core/racing/application/presenters/ILeagueScoringConfigPresenter.ts b/core/racing/application/presenters/ILeagueScoringConfigPresenter.ts index 3a8c4ac9a..54f3bd7d2 100644 --- a/core/racing/application/presenters/ILeagueScoringConfigPresenter.ts +++ b/core/racing/application/presenters/ILeagueScoringConfigPresenter.ts @@ -1,5 +1,5 @@ import type { ChampionshipConfig } from '../../domain/types/ChampionshipConfig'; -import type { LeagueScoringPresetDTO } from '../ports/LeagueScoringPresetProvider'; +import type { LeagueScoringPresetOutputPort } from '../ports/output/LeagueScoringPresetOutputPort'; import type { Presenter } from '@core/shared/presentation'; export interface LeagueScoringChampionshipViewModel { @@ -29,7 +29,7 @@ export interface LeagueScoringConfigData { gameId: string; gameName: string; scoringPresetId?: string; - preset?: LeagueScoringPresetDTO; + preset?: LeagueScoringPresetOutputPort; championships: ChampionshipConfig[]; } diff --git a/core/racing/application/presenters/ILeagueScoringPresetsPresenter.ts b/core/racing/application/presenters/ILeagueScoringPresetsPresenter.ts index 645431756..8607bcfab 100644 --- a/core/racing/application/presenters/ILeagueScoringPresetsPresenter.ts +++ b/core/racing/application/presenters/ILeagueScoringPresetsPresenter.ts @@ -1,13 +1,13 @@ -import type { LeagueScoringPresetDTO } from '../ports/LeagueScoringPresetProvider'; +import type { LeagueScoringPresetOutputPort } from '../ports/output/LeagueScoringPresetOutputPort'; import type { Presenter } from '@core/shared/presentation'; export interface LeagueScoringPresetsViewModel { - presets: LeagueScoringPresetDTO[]; + presets: LeagueScoringPresetOutputPort[]; totalCount: number; } export interface LeagueScoringPresetsResultDTO { - presets: LeagueScoringPresetDTO[]; + presets: LeagueScoringPresetOutputPort[]; } export interface ILeagueScoringPresetsPresenter diff --git a/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts b/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts index e46575e61..7a45004d5 100644 --- a/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts +++ b/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts @@ -10,7 +10,6 @@ import type { ISponsorshipRequestRepository } from '../../domain/repositories/IS import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository'; import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; import type { INotificationService } from '@core/notifications/application/ports/INotificationService'; -import type { IPaymentGateway } from '../ports/IPaymentGateway'; import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository'; import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository'; import { SeasonSponsorship } from '../../domain/entities/SeasonSponsorship'; @@ -18,22 +17,24 @@ import type { AsyncUseCase } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { AcceptSponsorshipRequestDTO } from '../dto/AcceptSponsorshipRequestDTO'; -import type { AcceptSponsorshipRequestResultDTO } from '../dto/AcceptSponsorshipRequestResultDTO'; +import type { AcceptSponsorshipOutputPort } from '../ports/output/AcceptSponsorshipOutputPort'; +import type { ProcessPaymentInputPort } from '../ports/input/ProcessPaymentInputPort'; +import type { ProcessPaymentOutputPort } from '../ports/output/ProcessPaymentOutputPort'; export class AcceptSponsorshipRequestUseCase - implements AsyncUseCase { + implements AsyncUseCase { constructor( private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository, private readonly seasonSponsorshipRepo: ISeasonSponsorshipRepository, private readonly seasonRepository: ISeasonRepository, private readonly notificationService: INotificationService, - private readonly paymentGateway: IPaymentGateway, + private readonly paymentProcessor: (input: ProcessPaymentInputPort) => Promise, private readonly walletRepository: IWalletRepository, private readonly leagueWalletRepository: ILeagueWalletRepository, private readonly logger: Logger, ) {} - async execute(dto: AcceptSponsorshipRequestDTO): Promise>> { + async execute(dto: AcceptSponsorshipRequestDTO): Promise>> { this.logger.debug(`Attempting to accept sponsorship request: ${dto.requestId}`, { requestId: dto.requestId, respondedBy: dto.respondedBy }); // Find the request @@ -92,13 +93,15 @@ export class AcceptSponsorshipRequestUseCase }, }); - // Process payment - const paymentResult = await this.paymentGateway.processPayment( - request.offeredAmount, - request.sponsorId, - `Sponsorship payment for ${request.entityType} ${request.entityId}`, - { requestId: request.id } - ); + // Process payment using clean input/output ports with primitive types + const paymentInput: ProcessPaymentInputPort = { + amount: request.offeredAmount.amount, // Extract primitive number from value object + payerId: request.sponsorId, + description: `Sponsorship payment for ${request.entityType} ${request.entityId}`, + metadata: { requestId: request.id } + }; + + const paymentResult = await this.paymentProcessor(paymentInput); if (!paymentResult.success) { this.logger.error(`Payment failed for sponsorship request ${request.id}: ${paymentResult.error}`, undefined, { requestId: request.id }); return Result.err({ code: 'PAYMENT_PROCESSING_FAILED' }); @@ -142,4 +145,4 @@ export class AcceptSponsorshipRequestUseCase netAmount: acceptedRequest.getNetAmount().amount, }); } -} +} \ No newline at end of file diff --git a/core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts b/core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts index 54688475c..4e874ca85 100644 --- a/core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts +++ b/core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts @@ -14,11 +14,11 @@ import type { AsyncUseCase } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { Logger } from '@core/shared/application'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { ApplyForSponsorshipDTO } from '../dto/ApplyForSponsorshipDTO'; -import type { ApplyForSponsorshipResultDTO } from '../dto/ApplyForSponsorshipResultDTO'; +import type { ApplyForSponsorshipPort } from '../ports/input/ApplyForSponsorshipPort'; +import type { ApplyForSponsorshipResultPort } from '../ports/output/ApplyForSponsorshipResultPort'; export class ApplyForSponsorshipUseCase - implements AsyncUseCase + implements AsyncUseCase { constructor( private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository, @@ -27,7 +27,7 @@ export class ApplyForSponsorshipUseCase private readonly logger: Logger, ) {} - async execute(dto: ApplyForSponsorshipDTO): Promise>> { + async execute(dto: ApplyForSponsorshipPort): Promise>> { this.logger.debug('Attempting to apply for sponsorship', { dto }); // Validate sponsor exists diff --git a/core/racing/application/use-cases/ApplyPenaltyUseCase.ts b/core/racing/application/use-cases/ApplyPenaltyUseCase.ts index 365b06010..f9fbbc82b 100644 --- a/core/racing/application/use-cases/ApplyPenaltyUseCase.ts +++ b/core/racing/application/use-cases/ApplyPenaltyUseCase.ts @@ -15,10 +15,10 @@ import type { AsyncUseCase } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { Logger } from '@core/shared/application'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { ApplyPenaltyCommand } from '../dto/ApplyPenaltyCommand'; +import type { ApplyPenaltyCommandPort } from '../ports/input/ApplyPenaltyCommandPort'; export class ApplyPenaltyUseCase - implements AsyncUseCase { + implements AsyncUseCase { constructor( private readonly penaltyRepository: IPenaltyRepository, private readonly protestRepository: IProtestRepository, @@ -27,7 +27,7 @@ export class ApplyPenaltyUseCase private readonly logger: Logger, ) {} - async execute(command: ApplyPenaltyCommand): Promise>> { + async execute(command: ApplyPenaltyCommandPort): Promise>> { this.logger.debug('ApplyPenaltyUseCase: Executing with command', command); // Validate race exists diff --git a/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts b/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts index 790ca56f8..e2d24d960 100644 --- a/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts +++ b/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts @@ -4,13 +4,13 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC import type { AsyncUseCase } from '@core/shared/application'; import { randomUUID } from 'crypto'; import type { ApproveLeagueJoinRequestUseCaseParams } from '../dto/ApproveLeagueJoinRequestUseCaseParams'; -import type { ApproveLeagueJoinRequestResultDTO } from '../dto/ApproveLeagueJoinRequestResultDTO'; +import type { ApproveLeagueJoinRequestResultPort } from '../ports/output/ApproveLeagueJoinRequestResultPort'; import { JoinedAt } from '../../domain/value-objects/JoinedAt'; -export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase { +export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase { constructor(private readonly leagueMembershipRepository: ILeagueMembershipRepository) {} - async execute(params: ApproveLeagueJoinRequestUseCaseParams): Promise>> { + async execute(params: ApproveLeagueJoinRequestUseCaseParams): Promise>> { const requests = await this.leagueMembershipRepository.getJoinRequests(params.leagueId); const request = requests.find(r => r.id === params.requestId); if (!request) { @@ -25,7 +25,7 @@ export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase { + implements AsyncUseCase { constructor( private readonly leagueRepository: ILeagueRepository, private readonly seasonRepository: ISeasonRepository, @@ -51,7 +51,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase async execute( command: CreateLeagueWithSeasonAndScoringCommand, - ): Promise>> { + ): Promise>> { this.logger.debug('Executing CreateLeagueWithSeasonAndScoringUseCase', { command }); const validation = this.validate(command); if (validation.isErr()) { @@ -112,7 +112,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase await this.leagueScoringConfigRepository.save(finalConfig); this.logger.info(`Scoring configuration saved for season ${seasonId}.`); - const result: CreateLeagueWithSeasonAndScoringResultDTO = { + const result: CreateLeagueWithSeasonAndScoringOutputPort = { leagueId: league.id.toString(), seasonId, scoringPresetId: preset.id, diff --git a/core/racing/application/use-cases/CreateSponsorUseCase.ts b/core/racing/application/use-cases/CreateSponsorUseCase.ts index f434d2225..c40177f84 100644 --- a/core/racing/application/use-cases/CreateSponsorUseCase.ts +++ b/core/racing/application/use-cases/CreateSponsorUseCase.ts @@ -10,7 +10,7 @@ import type { AsyncUseCase } from '@core/shared/application'; import type { Logger } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { CreateSponsorResultDTO } from '../dto/CreateSponsorResultDTO'; +import type { CreateSponsorOutputPort } from '../ports/output/CreateSponsorOutputPort'; export interface CreateSponsorCommand { name: string; @@ -20,7 +20,7 @@ export interface CreateSponsorCommand { } export class CreateSponsorUseCase - implements AsyncUseCase + implements AsyncUseCase { constructor( private readonly sponsorRepository: ISponsorRepository, @@ -29,7 +29,7 @@ export class CreateSponsorUseCase async execute( command: CreateSponsorCommand, - ): Promise>> { + ): Promise>> { this.logger.debug('Executing CreateSponsorUseCase', { command }); const validation = this.validate(command); if (validation.isErr()) { @@ -51,7 +51,7 @@ export class CreateSponsorUseCase await this.sponsorRepository.create(sponsor); this.logger.info(`Sponsor ${sponsor.name} (${sponsor.id}) created successfully.`); - const result: CreateSponsorResultDTO = { + const result: CreateSponsorOutputPort = { sponsor: { id: sponsor.id, name: sponsor.name, diff --git a/core/racing/application/use-cases/CreateTeamUseCase.ts b/core/racing/application/use-cases/CreateTeamUseCase.ts index f757f9252..a56dc89fe 100644 --- a/core/racing/application/use-cases/CreateTeamUseCase.ts +++ b/core/racing/application/use-cases/CreateTeamUseCase.ts @@ -16,6 +16,7 @@ import type { AsyncUseCase } from '@core/shared/application'; import type { Logger } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; +import type { CreateTeamOutputPort } from '../ports/output/CreateTeamOutputPort'; export interface CreateTeamCommandDTO { name: string; @@ -25,12 +26,8 @@ export interface CreateTeamCommandDTO { leagues: string[]; } -export interface CreateTeamResultDTO { - team: Team; -} - export class CreateTeamUseCase - implements AsyncUseCase + implements AsyncUseCase { constructor( private readonly teamRepository: ITeamRepository, @@ -40,7 +37,7 @@ export class CreateTeamUseCase async execute( command: CreateTeamCommandDTO, - ): Promise>> { + ): Promise>> { this.logger.debug('Executing CreateTeamUseCase', { command }); const { name, tag, description, ownerId, leagues } = command; @@ -80,7 +77,7 @@ export class CreateTeamUseCase await this.membershipRepository.saveMembership(membership); this.logger.debug('Team membership created successfully.'); - const result: CreateTeamResultDTO = { team: createdTeam }; + const result: CreateTeamOutputPort = { team: createdTeam }; this.logger.debug('CreateTeamUseCase completed successfully.', { result }); return Result.ok(result); } catch (error) { diff --git a/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts b/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts index e29212446..cbef29020 100644 --- a/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts +++ b/core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts @@ -11,11 +11,11 @@ import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISe import type { AsyncUseCase, Logger } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { GetEntitySponsorshipPricingDTO } from '../dto/GetEntitySponsorshipPricingDTO'; -import type { GetEntitySponsorshipPricingResultDTO } from '../dto/GetEntitySponsorshipPricingResultDTO'; +import type { GetEntitySponsorshipPricingInputPort } from '../ports/input/GetEntitySponsorshipPricingInputPort'; +import type { GetEntitySponsorshipPricingOutputPort } from '../ports/output/GetEntitySponsorshipPricingOutputPort'; export class GetEntitySponsorshipPricingUseCase - implements AsyncUseCase + implements AsyncUseCase { constructor( private readonly sponsorshipPricingRepo: ISponsorshipPricingRepository, @@ -24,7 +24,7 @@ export class GetEntitySponsorshipPricingUseCase private readonly logger: Logger, ) {} - async execute(dto: GetEntitySponsorshipPricingDTO): Promise>> { + async execute(dto: GetEntitySponsorshipPricingInputPort): Promise>> { this.logger.debug(`Executing GetEntitySponsorshipPricingUseCase for entityType: ${dto.entityType}, entityId: ${dto.entityId}`); try { const pricing = await this.sponsorshipPricingRepo.findByEntity(dto.entityType, dto.entityId); @@ -53,7 +53,7 @@ export class GetEntitySponsorshipPricingUseCase filledSecondarySlots = activeSponsorships.filter(s => s.tier === 'secondary').length; } - const result: GetEntitySponsorshipPricingResultDTO = { + const result: GetEntitySponsorshipPricingOutputPort = { entityType: dto.entityType, entityId: dto.entityId, acceptingApplications: pricing.acceptingApplications, diff --git a/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts b/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts index 9c8ee0dd3..ef824ef15 100644 --- a/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts @@ -2,15 +2,15 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; import type { AsyncUseCase } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; -import type { GetLeagueAdminPermissionsResultDTO } from '../dto/GetLeagueAdminPermissionsResultDTO'; +import type { GetLeagueAdminPermissionsOutputPort } from '../ports/output/GetLeagueAdminPermissionsOutputPort'; -export class GetLeagueAdminPermissionsUseCase implements AsyncUseCase<{ leagueId: string; performerDriverId: string }, GetLeagueAdminPermissionsResultDTO, 'NO_ERROR'> { +export class GetLeagueAdminPermissionsUseCase implements AsyncUseCase<{ leagueId: string; performerDriverId: string }, GetLeagueAdminPermissionsOutputPort, 'NO_ERROR'> { constructor( private readonly leagueRepository: ILeagueRepository, private readonly leagueMembershipRepository: ILeagueMembershipRepository, ) {} - async execute(params: { leagueId: string; performerDriverId: string }): Promise> { + async execute(params: { leagueId: string; performerDriverId: string }): Promise> { const league = await this.leagueRepository.findById(params.leagueId); if (!league) { return Result.ok({ canRemoveMember: false, canUpdateRoles: false }); diff --git a/core/racing/application/use-cases/GetLeagueAdminUseCase.ts b/core/racing/application/use-cases/GetLeagueAdminUseCase.ts index ad9669f6f..bd527dc68 100644 --- a/core/racing/application/use-cases/GetLeagueAdminUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueAdminUseCase.ts @@ -2,20 +2,20 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit import type { AsyncUseCase } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { GetLeagueAdminResultDTO } from '../dto/GetLeagueAdminResultDTO'; +import type { GetLeagueAdminOutputPort } from '../ports/output/GetLeagueAdminOutputPort'; -export class GetLeagueAdminUseCase implements AsyncUseCase<{ leagueId: string }, GetLeagueAdminResultDTO, 'LEAGUE_NOT_FOUND'> { +export class GetLeagueAdminUseCase implements AsyncUseCase<{ leagueId: string }, GetLeagueAdminOutputPort, 'LEAGUE_NOT_FOUND'> { constructor( private readonly leagueRepository: ILeagueRepository, ) {} - async execute(params: { leagueId: string }): Promise>> { + async execute(params: { leagueId: string }): Promise>> { const league = await this.leagueRepository.findById(params.leagueId); if (!league) { return Result.err({ code: 'LEAGUE_NOT_FOUND', details: { message: 'League not found' } }); } - const dto: GetLeagueAdminResultDTO = { + const dto: GetLeagueAdminOutputPort = { league: { id: league.id, ownerId: league.ownerId, diff --git a/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts b/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts index e397b2892..d8bc820bb 100644 --- a/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts @@ -3,19 +3,19 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit import type { AsyncUseCase } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { GetLeagueJoinRequestsResultDTO } from '../dto/GetLeagueJoinRequestsResultDTO'; +import type { GetLeagueJoinRequestsOutputPort } from '../ports/output/GetLeagueJoinRequestsOutputPort'; export interface GetLeagueJoinRequestsUseCaseParams { leagueId: string; } -export class GetLeagueJoinRequestsUseCase implements AsyncUseCase { +export class GetLeagueJoinRequestsUseCase implements AsyncUseCase { constructor( private readonly leagueMembershipRepository: ILeagueMembershipRepository, private readonly driverRepository: IDriverRepository, ) {} - async execute(params: GetLeagueJoinRequestsUseCaseParams): Promise>> { + async execute(params: GetLeagueJoinRequestsUseCaseParams): Promise>> { const joinRequests = await this.leagueMembershipRepository.getJoinRequests(params.leagueId); const driverIds = [...new Set(joinRequests.map(r => r.driverId))]; const drivers = await Promise.all(driverIds.map(id => this.driverRepository.findById(id))); diff --git a/core/racing/application/use-cases/GetLeagueMembershipsUseCase.ts b/core/racing/application/use-cases/GetLeagueMembershipsUseCase.ts index 0ef33de8f..3b1f27e0e 100644 --- a/core/racing/application/use-cases/GetLeagueMembershipsUseCase.ts +++ b/core/racing/application/use-cases/GetLeagueMembershipsUseCase.ts @@ -3,19 +3,19 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit import type { AsyncUseCase } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; -import type { GetLeagueMembershipsResultDTO } from '../dto/GetLeagueMembershipsResultDTO'; +import type { GetLeagueMembershipsOutputPort } from '../ports/output/GetLeagueMembershipsOutputPort'; export interface GetLeagueMembershipsUseCaseParams { leagueId: string; } -export class GetLeagueMembershipsUseCase implements AsyncUseCase { +export class GetLeagueMembershipsUseCase implements AsyncUseCase { constructor( private readonly leagueMembershipRepository: ILeagueMembershipRepository, private readonly driverRepository: IDriverRepository, ) {} - async execute(params: GetLeagueMembershipsUseCaseParams): Promise>> { + async execute(params: GetLeagueMembershipsUseCaseParams): Promise>> { const memberships = await this.leagueMembershipRepository.getLeagueMembers(params.leagueId); const drivers: { id: string; name: string }[] = []; @@ -27,7 +27,7 @@ export class GetLeagueMembershipsUseCase implements AsyncUseCase { +export class GetLeagueOwnerSummaryUseCase implements AsyncUseCase { constructor(private readonly driverRepository: IDriverRepository) {} - async execute(params: GetLeagueOwnerSummaryUseCaseParams): Promise>> { + async execute(params: GetLeagueOwnerSummaryUseCaseParams): Promise>> { const driver = await this.driverRepository.findById(params.ownerId); const summary = driver ? { driver: { id: driver.id, name: driver.name }, rating: 0, rank: 0 } : null; return Result.ok({ summary }); diff --git a/core/racing/application/use-cases/GetRaceDetailUseCase.ts b/core/racing/application/use-cases/GetRaceDetailUseCase.ts index 64f0f2756..b8e9f7bc7 100644 --- a/core/racing/application/use-cases/GetRaceDetailUseCase.ts +++ b/core/racing/application/use-cases/GetRaceDetailUseCase.ts @@ -4,8 +4,10 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; -import type { DriverRatingProvider } from '../ports/DriverRatingProvider'; -import type { IImageServicePort } from '../ports/IImageServicePort'; +import type { GetDriverRatingInputPort } from '../ports/input/GetDriverRatingInputPort'; +import type { GetDriverRatingOutputPort } from '../ports/output/GetDriverRatingOutputPort'; +import type { GetDriverAvatarInputPort } from '../ports/input/GetDriverAvatarInputPort'; +import type { GetDriverAvatarOutputPort } from '../ports/output/GetDriverAvatarOutputPort'; import type { RaceDetailViewModel, RaceDetailRaceViewModel, @@ -44,8 +46,8 @@ export class GetRaceDetailUseCase private readonly raceRegistrationRepository: IRaceRegistrationRepository, private readonly resultRepository: IResultRepository, private readonly leagueMembershipRepository: ILeagueMembershipRepository, - private readonly driverRatingProvider: DriverRatingProvider, - private readonly imageService: IImageServicePort, + private readonly getDriverRating: (input: GetDriverRatingInputPort) => Promise, + private readonly getDriverAvatar: (input: GetDriverAvatarInputPort) => Promise, ) {} async execute(params: GetRaceDetailQueryParams): Promise>> { @@ -62,22 +64,26 @@ export class GetRaceDetailUseCase this.leagueMembershipRepository.getMembership(race.leagueId, driverId), ]); - const ratings = this.driverRatingProvider.getRatings(registeredDriverIds); - const drivers = await Promise.all( registeredDriverIds.map(id => this.driverRepository.findById(id)), ); - const entryList: RaceDetailEntryViewModel[] = drivers - .filter((d): d is NonNullable => d !== null) - .map(driver => ({ - id: driver.id, - name: driver.name, - country: driver.country, - avatarUrl: this.imageService.getDriverAvatar(driver.id), - rating: ratings.get(driver.id) ?? null, - isCurrentUser: driver.id === driverId, - })); + const entryList: RaceDetailEntryViewModel[] = []; + for (const driver of drivers) { + if (driver) { + const ratingResult = await this.getDriverRating({ driverId: driver.id }); + const avatarResult = await this.getDriverAvatar({ driverId: driver.id }); + + entryList.push({ + id: driver.id, + name: driver.name, + country: driver.country, + avatarUrl: avatarResult.avatarUrl, + rating: ratingResult.rating, + isCurrentUser: driver.id === driverId, + }); + } + } const isUserRegistered = registeredDriverIds.includes(driverId); const isUpcoming = race.status === 'scheduled' && race.scheduledAt > new Date(); diff --git a/core/racing/application/use-cases/GetTeamMembersUseCase.ts b/core/racing/application/use-cases/GetTeamMembersUseCase.ts index 52b49d108..5815968f3 100644 --- a/core/racing/application/use-cases/GetTeamMembersUseCase.ts +++ b/core/racing/application/use-cases/GetTeamMembersUseCase.ts @@ -1,6 +1,7 @@ import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository'; import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; -import type { IImageServicePort } from '../ports/IImageServicePort'; +import type { GetDriverAvatarInputPort } from '../ports/input/GetDriverAvatarInputPort'; +import type { GetDriverAvatarOutputPort } from '../ports/output/GetDriverAvatarOutputPort'; import type { TeamMembersResultDTO } from '../presenters/ITeamMembersPresenter'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; @@ -15,7 +16,7 @@ export class GetTeamMembersUseCase implements AsyncUseCase<{ teamId: string }, T constructor( private readonly membershipRepository: ITeamMembershipRepository, private readonly driverRepository: IDriverRepository, - private readonly imageService: IImageServicePort, + private readonly getDriverAvatar: (input: GetDriverAvatarInputPort) => Promise, private readonly logger: Logger, ) {} @@ -37,7 +38,9 @@ export class GetTeamMembersUseCase implements AsyncUseCase<{ teamId: string }, T } else { this.logger.warn(`Driver with ID ${membership.driverId} not found while fetching team members for team ${input.teamId}.`); } - avatarUrls[membership.driverId] = this.imageService.getDriverAvatar(membership.driverId); + + const avatarResult = await this.getDriverAvatar({ driverId: membership.driverId }); + avatarUrls[membership.driverId] = avatarResult.avatarUrl; } const dto: TeamMembersResultDTO = { diff --git a/core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts b/core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts index 629eb6ceb..0048bec24 100644 --- a/core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts +++ b/core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts @@ -1,4 +1,4 @@ -import type { LeagueScoringPresetProvider } from '../ports/LeagueScoringPresetProvider'; +import type { LeagueScoringPresetOutputPort } from '../ports/output/LeagueScoringPresetOutputPort'; import type { LeagueScoringPresetsResultDTO } from '../presenters/ILeagueScoringPresetsPresenter'; import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase'; import { Result } from '@core/shared/application/Result'; @@ -6,18 +6,16 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC /** * Use Case for listing league scoring presets. - * Orchestrates domain logic and returns result. + * Returns preset data without business logic. */ export class ListLeagueScoringPresetsUseCase implements AsyncUseCase { - constructor(private readonly presetProvider: LeagueScoringPresetProvider) {} + constructor(private readonly presets: LeagueScoringPresetOutputPort[]) {} async execute(): Promise>> { - const presets = await this.presetProvider.listPresets(); - const dto: LeagueScoringPresetsResultDTO = { - presets, + presets: this.presets, }; return Result.ok(dto); diff --git a/core/racing/application/use-cases/RecalculateChampionshipStandingsUseCase.ts b/core/racing/application/use-cases/RecalculateChampionshipStandingsUseCase.ts index b80dbf68c..b33afcb4b 100644 --- a/core/racing/application/use-cases/RecalculateChampionshipStandingsUseCase.ts +++ b/core/racing/application/use-cases/RecalculateChampionshipStandingsUseCase.ts @@ -11,10 +11,8 @@ import type { ChampionshipStanding } from '@core/racing/domain/entities/champion import { EventScoringService } from '@core/racing/domain/services/EventScoringService'; import { ChampionshipAggregator } from '@core/racing/domain/services/ChampionshipAggregator'; -import type { - ChampionshipStandingsDTO, - ChampionshipStandingsRowDTO, -} from '../dto/ChampionshipStandingsDTO'; +import type { ChampionshipStandingsOutputPort } from '../ports/output/ChampionshipStandingsOutputPort'; +import type { ChampionshipStandingsRowOutputPort } from '../ports/output/ChampionshipStandingsRowOutputPort'; import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase'; import { Result } from '@core/shared/application/Result'; @@ -31,7 +29,7 @@ type RecalculateChampionshipStandingsErrorCode = | 'CHAMPIONSHIP_CONFIG_NOT_FOUND'; export class RecalculateChampionshipStandingsUseCase - implements AsyncUseCase + implements AsyncUseCase { constructor( private readonly seasonRepository: ISeasonRepository, @@ -44,7 +42,7 @@ export class RecalculateChampionshipStandingsUseCase private readonly championshipAggregator: ChampionshipAggregator, ) {} - async execute(params: RecalculateChampionshipStandingsParams): Promise>> { + async execute(params: RecalculateChampionshipStandingsParams): Promise>> { const { seasonId, championshipId } = params; const season = await this.seasonRepository.findById(seasonId); @@ -107,7 +105,7 @@ export class RecalculateChampionshipStandingsUseCase resultsDropped: s.resultsDropped.toNumber(), })); - const dto: ChampionshipStandingsDTO = { + const dto: ChampionshipStandingsOutputPort = { seasonId, championshipId: championship.id, championshipName: championship.name, diff --git a/core/racing/application/use-cases/UpdateDriverProfileUseCase.test.ts b/core/racing/application/use-cases/UpdateDriverProfileUseCase.test.ts index acc1a0e89..3ad6f7174 100644 --- a/core/racing/application/use-cases/UpdateDriverProfileUseCase.test.ts +++ b/core/racing/application/use-cases/UpdateDriverProfileUseCase.test.ts @@ -1,32 +1,26 @@ import { describe, it, expect, vi } from 'vitest'; import { UpdateDriverProfileUseCase } from './UpdateDriverProfileUseCase'; import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; -import { EntityMappers } from '../mappers/EntityMappers'; - -vi.mock('../mappers/EntityMappers', () => ({ - EntityMappers: { - toDriverDTO: vi.fn(), - }, -})); +import type { Driver } from '../../domain/entities/Driver'; describe('UpdateDriverProfileUseCase', () => { it('updates driver profile successfully', async () => { const mockDriver = { id: 'driver-1', update: vi.fn().mockReturnValue({}), - }; + } as unknown as Driver; - const mockUpdatedDriver = {}; - - const mockDTO = { id: 'driver-1', bio: 'New bio' }; + const mockUpdatedDriver = { + id: 'driver-1', + bio: 'New bio', + country: 'US', + } as Driver; const mockDriverRepository = { findById: vi.fn().mockResolvedValue(mockDriver), update: vi.fn().mockResolvedValue(mockUpdatedDriver), } as unknown as IDriverRepository; - (EntityMappers.toDriverDTO as any).mockReturnValue(mockDTO); - const useCase = new UpdateDriverProfileUseCase(mockDriverRepository); const input = { @@ -38,11 +32,10 @@ describe('UpdateDriverProfileUseCase', () => { const result = await useCase.execute(input); expect(result.isOk()).toBe(true); - expect(result.unwrap()).toEqual(mockDTO); + expect(result.unwrap()).toEqual(mockUpdatedDriver); expect(mockDriverRepository.findById).toHaveBeenCalledWith('driver-1'); expect(mockDriver.update).toHaveBeenCalledWith({ bio: 'New bio', country: 'US' }); expect(mockDriverRepository.update).toHaveBeenCalledWith({}); - expect(EntityMappers.toDriverDTO).toHaveBeenCalledWith(mockUpdatedDriver); }); it('returns error when driver not found', async () => { @@ -67,19 +60,18 @@ describe('UpdateDriverProfileUseCase', () => { const mockDriver = { id: 'driver-1', update: vi.fn().mockReturnValue({}), - }; + } as unknown as Driver; - const mockUpdatedDriver = {}; - - const mockDTO = { id: 'driver-1', country: 'US' }; + const mockUpdatedDriver = { + id: 'driver-1', + country: 'US', + } as Driver; const mockDriverRepository = { findById: vi.fn().mockResolvedValue(mockDriver), update: vi.fn().mockResolvedValue(mockUpdatedDriver), } as unknown as IDriverRepository; - (EntityMappers.toDriverDTO as any).mockReturnValue(mockDTO); - const useCase = new UpdateDriverProfileUseCase(mockDriverRepository); const input = { diff --git a/core/racing/application/use-cases/UpdateDriverProfileUseCase.ts b/core/racing/application/use-cases/UpdateDriverProfileUseCase.ts index adae24ce1..96dfe51b6 100644 --- a/core/racing/application/use-cases/UpdateDriverProfileUseCase.ts +++ b/core/racing/application/use-cases/UpdateDriverProfileUseCase.ts @@ -1,8 +1,7 @@ import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; -import type { DriverDTO } from '../dto/DriverDTO'; -import { EntityMappers } from '../mappers/EntityMappers'; +import type { Driver } from '../../domain/entities/Driver'; export interface UpdateDriverProfileInput { driverId: string; @@ -12,12 +11,13 @@ export interface UpdateDriverProfileInput { /** * Application use case responsible for updating basic driver profile details. - * Encapsulates domain entity mutation and mapping to a DriverDTO. + * Encapsulates domain entity mutation and returns the updated entity. + * Mapping to DTOs is handled by presenters in the presentation layer. */ export class UpdateDriverProfileUseCase { constructor(private readonly driverRepository: IDriverRepository) {} - async execute(input: UpdateDriverProfileInput): Promise>> { + async execute(input: UpdateDriverProfileInput): Promise>> { const { driverId, bio, country } = input; const existing = await this.driverRepository.findById(driverId); @@ -31,7 +31,6 @@ export class UpdateDriverProfileUseCase { }); const persisted = await this.driverRepository.update(updated); - const dto = EntityMappers.toDriverDTO(persisted); - return Result.ok(dto!); + return Result.ok(persisted); } } \ No newline at end of file diff --git a/core/racing/index.ts b/core/racing/index.ts index f91c68806..039aaf461 100644 --- a/core/racing/index.ts +++ b/core/racing/index.ts @@ -42,8 +42,6 @@ export * from './infrastructure/repositories/InMemorySeasonSponsorshipRepository export * from './infrastructure/repositories/InMemorySponsorshipRequestRepository'; export * from './infrastructure/repositories/InMemorySponsorshipPricingRepository'; -export * from './application/mappers/EntityMappers'; -export * from './application/dtos/DriverDTO'; export * from './application/dtos/LeagueDriverSeasonStatsDTO'; export * from './application/dtos/LeagueScoringConfigDTO';