refactor dtos to ports
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<AcceptSponsorshipRequestResultDTO | null> {
|
||||
async acceptSponsorshipRequest(@Param('requestId') requestId: string, @Body() input: AcceptSponsorshipRequestInputDTO): Promise<AcceptSponsorshipRequestResultPort | null> {
|
||||
return this.sponsorService.acceptSponsorshipRequest(requestId, input.respondedBy);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<AcceptSponsorshipRequestResultDTO | null> {
|
||||
async acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<AcceptSponsorshipRequestResultPort | null> {
|
||||
this.logger.debug('[SponsorService] Accepting sponsorship request.', { requestId, respondedBy });
|
||||
|
||||
const result = await this.acceptSponsorshipRequestUseCase.execute({ requestId, respondedBy });
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface ApproveLeagueJoinRequestResultDTO {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { Team } from '../../domain/entities/Team';
|
||||
|
||||
export interface CreateTeamResultDTO {
|
||||
team: Team;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { Team } from '../../domain/entities/Team';
|
||||
|
||||
export type GetAllTeamsQueryResultDTO = Team[];
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest';
|
||||
|
||||
export interface GetEntitySponsorshipPricingDTO {
|
||||
entityType: SponsorableEntityType;
|
||||
entityId: string;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { LeagueMembership } from '../../domain/entities/LeagueMembership';
|
||||
|
||||
export interface GetLeagueMembershipsResultDTO {
|
||||
memberships: LeagueMembership[];
|
||||
drivers: { id: string; name: string }[];
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface IsDriverRegisteredForRaceQueryParamsDTO {
|
||||
raceId: string;
|
||||
driverId: string;
|
||||
}
|
||||
|
||||
export interface GetRaceRegistrationsQueryParamsDTO {
|
||||
raceId: string;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// Mappers for converting between domain entities and DTOs
|
||||
// Example: driverToDTO, leagueToDTO, etc.
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface DriverRatingPort {
|
||||
getRating(driverId: string): { rating: number | null; ratingChange: number | 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<string, number>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<CompositionResult>;
|
||||
|
||||
/**
|
||||
* 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<Buffer>;
|
||||
|
||||
/**
|
||||
* Validate livery image (check for logos/text)
|
||||
*/
|
||||
validateLiveryImage(imageUrl: string): Promise<{
|
||||
isValid: boolean;
|
||||
violations?: string[];
|
||||
}>;
|
||||
}
|
||||
@@ -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<string, unknown>
|
||||
): Promise<UploadResult>;
|
||||
|
||||
/**
|
||||
* Download a livery image
|
||||
*/
|
||||
download(imageUrl: string): Promise<Buffer>;
|
||||
|
||||
/**
|
||||
* Delete a livery image
|
||||
*/
|
||||
delete(imageUrl: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Generate a signed URL for temporary access
|
||||
*/
|
||||
generateSignedUrl(imageUrl: string, expiresInSeconds: number): Promise<string>;
|
||||
}
|
||||
@@ -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<string, unknown>
|
||||
): Promise<PaymentResult>;
|
||||
|
||||
/**
|
||||
* Refund a payment
|
||||
*/
|
||||
refund(
|
||||
originalTransactionId: string,
|
||||
amount: Money,
|
||||
reason: string
|
||||
): Promise<RefundResult>;
|
||||
|
||||
/**
|
||||
* Verify payment status
|
||||
*/
|
||||
verifyPayment(transactionId: string): Promise<PaymentResult>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetDriverAvatarInputPort {
|
||||
driverId: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetDriverRatingInputPort {
|
||||
driverId: string;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { SponsorableEntityType } from '../../../domain/entities/SponsorshipRequest';
|
||||
|
||||
export interface GetEntitySponsorshipPricingInputPort {
|
||||
entityType: SponsorableEntityType;
|
||||
entityId: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetLeagueCoverInputPort {
|
||||
leagueId: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetLeagueLogoInputPort {
|
||||
leagueId: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetLeagueScoringPresetByIdInputPort {
|
||||
presetId: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetTeamLogoInputPort {
|
||||
teamId: string;
|
||||
}
|
||||
@@ -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';
|
||||
export type LeagueVisibilityInputPort = 'ranked' | 'unranked' | 'public' | 'private';
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface ListLeagueScoringPresetsInputPort {
|
||||
// Empty interface for query with no parameters
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface ProcessPaymentInputPort {
|
||||
amount: number; // in cents
|
||||
payerId: string;
|
||||
description: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface IsDriverRegisteredForRaceInputPort {
|
||||
raceId: string;
|
||||
driverId: string;
|
||||
}
|
||||
|
||||
export interface GetRaceRegistrationsInputPort {
|
||||
raceId: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { Money } from '../../domain/value-objects/Money';
|
||||
|
||||
export interface RefundPaymentInputPort {
|
||||
originalTransactionId: string;
|
||||
amount: Money;
|
||||
reason: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface VerifyPaymentInputPort {
|
||||
transactionId: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface AcceptSponsorshipRequestResultDTO {
|
||||
export interface AcceptSponsorshipOutputPort {
|
||||
requestId: string;
|
||||
sponsorshipId: string;
|
||||
status: 'accepted';
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface ApplyForSponsorshipResultDTO {
|
||||
export interface ApplyForSponsorshipResultPort {
|
||||
requestId: string;
|
||||
status: 'pending';
|
||||
createdAt: Date;
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface ApproveLeagueJoinRequestResultPort {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { ChampionshipStandingsRowOutputPort } from './ChampionshipStandingsRowOutputPort';
|
||||
|
||||
export interface ChampionshipStandingsOutputPort {
|
||||
seasonId: string;
|
||||
championshipId: string;
|
||||
championshipName: string;
|
||||
rows: ChampionshipStandingsRowOutputPort[];
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface CreateLeagueWithSeasonAndScoringResultDTO {
|
||||
export interface CreateLeagueWithSeasonAndScoringOutputPort {
|
||||
leagueId: string;
|
||||
seasonId: string;
|
||||
scoringPresetId?: string;
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface CreateSponsorResultDTO {
|
||||
export interface CreateSponsorOutputPort {
|
||||
sponsor: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { Team } from '../../../domain/entities/Team';
|
||||
|
||||
export interface CreateTeamOutputPort {
|
||||
team: Team;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
export type DriverDTO = {
|
||||
export interface DriverOutputPort {
|
||||
id: string;
|
||||
iracingId: string;
|
||||
name: string;
|
||||
country: string;
|
||||
bio?: string;
|
||||
joinedAt: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { Team } from '../../../domain/entities/Team';
|
||||
|
||||
export type GetAllTeamsOutputPort = Team[];
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetDriverAvatarOutputPort {
|
||||
avatarUrl: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface GetDriverRatingOutputPort {
|
||||
rating: number | null;
|
||||
ratingChange: number | null;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface GetLeagueAdminResultDTO {
|
||||
export interface GetLeagueAdminOutputPort {
|
||||
league: {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface GetLeagueAdminPermissionsResultDTO {
|
||||
export interface GetLeagueAdminPermissionsOutputPort {
|
||||
canRemoveMember: boolean;
|
||||
canUpdateRoles: boolean;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetLeagueCoverOutputPort {
|
||||
coverUrl: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface GetLeagueJoinRequestsResultDTO {
|
||||
export interface GetLeagueJoinRequestsOutputPort {
|
||||
joinRequests: Array<{
|
||||
id: string;
|
||||
leagueId: string;
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetLeagueLogoOutputPort {
|
||||
logoUrl: string;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { LeagueMembership } from '../../../domain/entities/LeagueMembership';
|
||||
|
||||
export interface GetLeagueMembershipsOutputPort {
|
||||
memberships: LeagueMembership[];
|
||||
drivers: { id: string; name: string }[];
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export interface GetLeagueOwnerSummaryResultDTO {
|
||||
export interface GetLeagueOwnerSummaryOutputPort {
|
||||
summary: { driver: { id: string; name: string }; rating: number; rank: number } | null;
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface GetLeagueScheduleResultDTO {
|
||||
export interface GetLeagueScheduleOutputPort {
|
||||
races: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GetTeamLogoOutputPort {
|
||||
logoUrl: string;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface ProcessPaymentOutputPort {
|
||||
success: boolean;
|
||||
transactionId?: string;
|
||||
error?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface ProtestOutputPort {
|
||||
id: string;
|
||||
raceId: string;
|
||||
protestingDriverId: string;
|
||||
accusedDriverId: string;
|
||||
submittedAt: Date;
|
||||
description: string;
|
||||
status: string;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface RefundPaymentOutputPort {
|
||||
success: boolean;
|
||||
refundId?: string;
|
||||
error?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
@@ -1,8 +1,8 @@
|
||||
export type StandingDTO = {
|
||||
export interface StandingOutputPort {
|
||||
leagueId: string;
|
||||
driverId: string;
|
||||
points: number;
|
||||
wins: number;
|
||||
position: number;
|
||||
racesCompleted: number;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface VerifyPaymentOutputPort {
|
||||
success: boolean;
|
||||
transactionId?: string;
|
||||
error?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -5,9 +5,9 @@ export interface ApproveLeagueJoinRequestViewModel {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ApproveLeagueJoinRequestResultDTO {
|
||||
export interface ApproveLeagueJoinRequestResultPort {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface IApproveLeagueJoinRequestPresenter extends Presenter<ApproveLeagueJoinRequestResultDTO, ApproveLeagueJoinRequestViewModel> {}
|
||||
export interface IApproveLeagueJoinRequestPresenter extends Presenter<ApproveLeagueJoinRequestResultPort, ApproveLeagueJoinRequestViewModel> {}
|
||||
@@ -13,8 +13,8 @@ export interface CreateSponsorViewModel {
|
||||
sponsor: SponsorDto;
|
||||
}
|
||||
|
||||
export interface CreateSponsorResultDTO {
|
||||
export interface CreateSponsorOutputPort {
|
||||
sponsor: SponsorDto;
|
||||
}
|
||||
|
||||
export interface ICreateSponsorPresenter extends Presenter<CreateSponsorResultDTO, CreateSponsorViewModel> {}
|
||||
export interface ICreateSponsorPresenter extends Presenter<CreateSponsorOutputPort, CreateSponsorViewModel> {}
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<AcceptSponsorshipRequestDTO, AcceptSponsorshipRequestResultDTO, string> {
|
||||
implements AsyncUseCase<AcceptSponsorshipRequestDTO, AcceptSponsorshipOutputPort, string> {
|
||||
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<ProcessPaymentOutputPort>,
|
||||
private readonly walletRepository: IWalletRepository,
|
||||
private readonly leagueWalletRepository: ILeagueWalletRepository,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: AcceptSponsorshipRequestDTO): Promise<Result<AcceptSponsorshipRequestResultDTO, ApplicationErrorCode<string>>> {
|
||||
async execute(dto: AcceptSponsorshipRequestDTO): Promise<Result<AcceptSponsorshipOutputPort, ApplicationErrorCode<string>>> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ApplyForSponsorshipDTO, ApplyForSponsorshipResultDTO, string>
|
||||
implements AsyncUseCase<ApplyForSponsorshipPort, ApplyForSponsorshipResultPort, string>
|
||||
{
|
||||
constructor(
|
||||
private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository,
|
||||
@@ -27,7 +27,7 @@ export class ApplyForSponsorshipUseCase
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: ApplyForSponsorshipDTO): Promise<Result<ApplyForSponsorshipResultDTO, ApplicationErrorCode<string>>> {
|
||||
async execute(dto: ApplyForSponsorshipPort): Promise<Result<ApplyForSponsorshipResultPort, ApplicationErrorCode<string>>> {
|
||||
this.logger.debug('Attempting to apply for sponsorship', { dto });
|
||||
|
||||
// Validate sponsor exists
|
||||
|
||||
@@ -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<ApplyPenaltyCommand, { penaltyId: string }, string> {
|
||||
implements AsyncUseCase<ApplyPenaltyCommandPort, { penaltyId: string }, string> {
|
||||
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<Result<{ penaltyId: string }, ApplicationErrorCode<string>>> {
|
||||
async execute(command: ApplyPenaltyCommandPort): Promise<Result<{ penaltyId: string }, ApplicationErrorCode<string>>> {
|
||||
this.logger.debug('ApplyPenaltyUseCase: Executing with command', command);
|
||||
|
||||
// Validate race exists
|
||||
|
||||
@@ -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<ApproveLeagueJoinRequestUseCaseParams, ApproveLeagueJoinRequestResultDTO, string> {
|
||||
export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase<ApproveLeagueJoinRequestUseCaseParams, ApproveLeagueJoinRequestResultPort, string> {
|
||||
constructor(private readonly leagueMembershipRepository: ILeagueMembershipRepository) {}
|
||||
|
||||
async execute(params: ApproveLeagueJoinRequestUseCaseParams): Promise<Result<ApproveLeagueJoinRequestResultDTO, ApplicationErrorCode<string>>> {
|
||||
async execute(params: ApproveLeagueJoinRequestUseCaseParams): Promise<Result<ApproveLeagueJoinRequestResultPort, ApplicationErrorCode<string>>> {
|
||||
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<ApproveLeag
|
||||
status: 'active',
|
||||
joinedAt: JoinedAt.create(new Date()),
|
||||
});
|
||||
const dto: ApproveLeagueJoinRequestResultDTO = { success: true, message: 'Join request approved.' };
|
||||
const dto: ApproveLeagueJoinRequestResultPort = { success: true, message: 'Join request approved.' };
|
||||
return Result.ok(dto);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { LeagueVisibilityInput } from '../dto/LeagueVisibilityInput';
|
||||
import type { CreateLeagueWithSeasonAndScoringResultDTO } from '../dto/CreateLeagueWithSeasonAndScoringResultDTO';
|
||||
import type { CreateLeagueWithSeasonAndScoringOutputPort } from '../ports/output/CreateLeagueWithSeasonAndScoringOutputPort';
|
||||
|
||||
export interface CreateLeagueWithSeasonAndScoringCommand {
|
||||
name: string;
|
||||
@@ -40,7 +40,7 @@ export interface CreateLeagueWithSeasonAndScoringCommand {
|
||||
}
|
||||
|
||||
export class CreateLeagueWithSeasonAndScoringUseCase
|
||||
implements AsyncUseCase<CreateLeagueWithSeasonAndScoringCommand, CreateLeagueWithSeasonAndScoringResultDTO, 'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR'> {
|
||||
implements AsyncUseCase<CreateLeagueWithSeasonAndScoringCommand, CreateLeagueWithSeasonAndScoringOutputPort, 'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR'> {
|
||||
constructor(
|
||||
private readonly leagueRepository: ILeagueRepository,
|
||||
private readonly seasonRepository: ISeasonRepository,
|
||||
@@ -51,7 +51,7 @@ export class CreateLeagueWithSeasonAndScoringUseCase
|
||||
|
||||
async execute(
|
||||
command: CreateLeagueWithSeasonAndScoringCommand,
|
||||
): Promise<Result<CreateLeagueWithSeasonAndScoringResultDTO, ApplicationErrorCode<'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
): Promise<Result<CreateLeagueWithSeasonAndScoringOutputPort, ApplicationErrorCode<'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
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,
|
||||
|
||||
@@ -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<CreateSponsorCommand, CreateSponsorResultDTO, 'VALIDATION_ERROR' | 'REPOSITORY_ERROR'>
|
||||
implements AsyncUseCase<CreateSponsorCommand, CreateSponsorOutputPort, 'VALIDATION_ERROR' | 'REPOSITORY_ERROR'>
|
||||
{
|
||||
constructor(
|
||||
private readonly sponsorRepository: ISponsorRepository,
|
||||
@@ -29,7 +29,7 @@ export class CreateSponsorUseCase
|
||||
|
||||
async execute(
|
||||
command: CreateSponsorCommand,
|
||||
): Promise<Result<CreateSponsorResultDTO, ApplicationErrorCode<'VALIDATION_ERROR' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
): Promise<Result<CreateSponsorOutputPort, ApplicationErrorCode<'VALIDATION_ERROR' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
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,
|
||||
|
||||
@@ -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<CreateTeamCommandDTO, CreateTeamResultDTO, 'ALREADY_IN_TEAM' | 'REPOSITORY_ERROR'>
|
||||
implements AsyncUseCase<CreateTeamCommandDTO, CreateTeamOutputPort, 'ALREADY_IN_TEAM' | 'REPOSITORY_ERROR'>
|
||||
{
|
||||
constructor(
|
||||
private readonly teamRepository: ITeamRepository,
|
||||
@@ -40,7 +37,7 @@ export class CreateTeamUseCase
|
||||
|
||||
async execute(
|
||||
command: CreateTeamCommandDTO,
|
||||
): Promise<Result<CreateTeamResultDTO, ApplicationErrorCode<'ALREADY_IN_TEAM' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
): Promise<Result<CreateTeamOutputPort, ApplicationErrorCode<'ALREADY_IN_TEAM' | 'REPOSITORY_ERROR', { message: string }>>> {
|
||||
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) {
|
||||
|
||||
@@ -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<GetEntitySponsorshipPricingDTO, GetEntitySponsorshipPricingResultDTO | null, 'REPOSITORY_ERROR'>
|
||||
implements AsyncUseCase<GetEntitySponsorshipPricingInputPort, GetEntitySponsorshipPricingOutputPort | null, 'REPOSITORY_ERROR'>
|
||||
{
|
||||
constructor(
|
||||
private readonly sponsorshipPricingRepo: ISponsorshipPricingRepository,
|
||||
@@ -24,7 +24,7 @@ export class GetEntitySponsorshipPricingUseCase
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: GetEntitySponsorshipPricingDTO): Promise<Result<GetEntitySponsorshipPricingResultDTO | null, ApplicationErrorCode<'REPOSITORY_ERROR', { message: string }>>> {
|
||||
async execute(dto: GetEntitySponsorshipPricingInputPort): Promise<Result<GetEntitySponsorshipPricingOutputPort | null, ApplicationErrorCode<'REPOSITORY_ERROR', { message: string }>>> {
|
||||
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,
|
||||
|
||||
@@ -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<Result<GetLeagueAdminPermissionsResultDTO, never>> {
|
||||
async execute(params: { leagueId: string; performerDriverId: string }): Promise<Result<GetLeagueAdminPermissionsOutputPort, never>> {
|
||||
const league = await this.leagueRepository.findById(params.leagueId);
|
||||
if (!league) {
|
||||
return Result.ok({ canRemoveMember: false, canUpdateRoles: false });
|
||||
|
||||
@@ -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<Result<GetLeagueAdminResultDTO, ApplicationErrorCode<'LEAGUE_NOT_FOUND', { message: string }>>> {
|
||||
async execute(params: { leagueId: string }): Promise<Result<GetLeagueAdminOutputPort, ApplicationErrorCode<'LEAGUE_NOT_FOUND', { message: string }>>> {
|
||||
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,
|
||||
|
||||
@@ -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<GetLeagueJoinRequestsUseCaseParams, GetLeagueJoinRequestsResultDTO, 'NO_ERROR'> {
|
||||
export class GetLeagueJoinRequestsUseCase implements AsyncUseCase<GetLeagueJoinRequestsUseCaseParams, GetLeagueJoinRequestsOutputPort, 'NO_ERROR'> {
|
||||
constructor(
|
||||
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
||||
private readonly driverRepository: IDriverRepository,
|
||||
) {}
|
||||
|
||||
async execute(params: GetLeagueJoinRequestsUseCaseParams): Promise<Result<GetLeagueJoinRequestsResultDTO, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
async execute(params: GetLeagueJoinRequestsUseCaseParams): Promise<Result<GetLeagueJoinRequestsOutputPort, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
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)));
|
||||
|
||||
@@ -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<GetLeagueMembershipsUseCaseParams, GetLeagueMembershipsResultDTO, 'NO_ERROR'> {
|
||||
export class GetLeagueMembershipsUseCase implements AsyncUseCase<GetLeagueMembershipsUseCaseParams, GetLeagueMembershipsOutputPort, 'NO_ERROR'> {
|
||||
constructor(
|
||||
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
|
||||
private readonly driverRepository: IDriverRepository,
|
||||
) {}
|
||||
|
||||
async execute(params: GetLeagueMembershipsUseCaseParams): Promise<Result<GetLeagueMembershipsResultDTO, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
async execute(params: GetLeagueMembershipsUseCaseParams): Promise<Result<GetLeagueMembershipsOutputPort, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
const memberships = await this.leagueMembershipRepository.getLeagueMembers(params.leagueId);
|
||||
const drivers: { id: string; name: string }[] = [];
|
||||
|
||||
@@ -27,7 +27,7 @@ export class GetLeagueMembershipsUseCase implements AsyncUseCase<GetLeagueMember
|
||||
}
|
||||
}
|
||||
|
||||
const dto: GetLeagueMembershipsResultDTO = {
|
||||
const dto: GetLeagueMembershipsOutputPort = {
|
||||
memberships,
|
||||
drivers,
|
||||
};
|
||||
|
||||
@@ -2,16 +2,16 @@ 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 { GetLeagueOwnerSummaryResultDTO } from '../dto/GetLeagueOwnerSummaryResultDTO';
|
||||
import type { GetLeagueOwnerSummaryOutputPort } from '../ports/output/GetLeagueOwnerSummaryOutputPort';
|
||||
|
||||
export interface GetLeagueOwnerSummaryUseCaseParams {
|
||||
ownerId: string;
|
||||
}
|
||||
|
||||
export class GetLeagueOwnerSummaryUseCase implements AsyncUseCase<GetLeagueOwnerSummaryUseCaseParams, GetLeagueOwnerSummaryResultDTO, 'NO_ERROR'> {
|
||||
export class GetLeagueOwnerSummaryUseCase implements AsyncUseCase<GetLeagueOwnerSummaryUseCaseParams, GetLeagueOwnerSummaryOutputPort, 'NO_ERROR'> {
|
||||
constructor(private readonly driverRepository: IDriverRepository) {}
|
||||
|
||||
async execute(params: GetLeagueOwnerSummaryUseCaseParams): Promise<Result<GetLeagueOwnerSummaryResultDTO, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
async execute(params: GetLeagueOwnerSummaryUseCaseParams): Promise<Result<GetLeagueOwnerSummaryOutputPort, ApplicationErrorCode<'NO_ERROR'>>> {
|
||||
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 });
|
||||
|
||||
@@ -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<GetDriverRatingOutputPort>,
|
||||
private readonly getDriverAvatar: (input: GetDriverAvatarInputPort) => Promise<GetDriverAvatarOutputPort>,
|
||||
) {}
|
||||
|
||||
async execute(params: GetRaceDetailQueryParams): Promise<Result<RaceDetailViewModel, ApplicationErrorCode<GetRaceDetailErrorCode>>> {
|
||||
@@ -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<typeof d> => 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();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user