alpha wip

This commit is contained in:
2025-12-03 00:46:08 +01:00
parent 3b55fd1a63
commit 97e29d3d80
51 changed files with 6321 additions and 237 deletions

View File

@@ -0,0 +1,174 @@
/**
* 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';
export type DriverDTO = {
id: string;
iracingId: string;
name: string;
country: string;
bio?: string;
joinedAt: string;
};
export type LeagueDTO = {
id: string;
name: string;
description: string;
ownerId: string;
settings: {
pointsSystem: 'f1-2024' | 'indycar' | 'custom';
sessionDuration?: number;
qualifyingFormat?: 'single-lap' | 'open';
customPoints?: Record<number, number>;
};
createdAt: string;
};
export type RaceDTO = {
id: string;
leagueId: string;
scheduledAt: string;
track: string;
car: string;
sessionType: 'practice' | 'qualifying' | 'race';
status: 'scheduled' | 'completed' | 'cancelled';
};
export type ResultDTO = {
id: string;
raceId: string;
driverId: string;
position: number;
fastestLap: number;
incidents: number;
startPosition: number;
};
export type StandingDTO = {
leagueId: string;
driverId: string;
points: number;
wins: number;
position: number;
racesCompleted: number;
};
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;
return {
id: league.id,
name: league.name,
description: league.description,
ownerId: league.ownerId,
settings: league.settings,
createdAt: league.createdAt.toISOString(),
};
}
static toLeagueDTOs(leagues: League[]): LeagueDTO[] {
return leagues.map(league => ({
id: league.id,
name: league.name,
description: league.description,
ownerId: league.ownerId,
settings: league.settings,
createdAt: league.createdAt.toISOString(),
}));
}
static toRaceDTO(race: Race | null): RaceDTO | null {
if (!race) return null;
return {
id: race.id,
leagueId: race.leagueId,
scheduledAt: race.scheduledAt.toISOString(),
track: race.track,
car: race.car,
sessionType: race.sessionType,
status: race.status,
};
}
static toRaceDTOs(races: Race[]): RaceDTO[] {
return races.map(race => ({
id: race.id,
leagueId: race.leagueId,
scheduledAt: race.scheduledAt.toISOString(),
track: race.track,
car: race.car,
sessionType: race.sessionType,
status: race.status,
}));
}
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,
}));
}
}

View File

@@ -0,0 +1,50 @@
/**
* Application Port: IDriverRepository
*
* Repository interface for Driver entity CRUD operations.
* Defines async methods using domain entities as types.
*/
import { Driver } from '../../domain/entities/Driver';
export interface IDriverRepository {
/**
* Find a driver by ID
*/
findById(id: string): Promise<Driver | null>;
/**
* Find a driver by iRacing ID
*/
findByIRacingId(iracingId: string): Promise<Driver | null>;
/**
* Find all drivers
*/
findAll(): Promise<Driver[]>;
/**
* Create a new driver
*/
create(driver: Driver): Promise<Driver>;
/**
* Update an existing driver
*/
update(driver: Driver): Promise<Driver>;
/**
* Delete a driver by ID
*/
delete(id: string): Promise<void>;
/**
* Check if a driver exists by ID
*/
exists(id: string): Promise<boolean>;
/**
* Check if an iRacing ID is already registered
*/
existsByIRacingId(iracingId: string): Promise<boolean>;
}

View File

@@ -0,0 +1,50 @@
/**
* Application Port: ILeagueRepository
*
* Repository interface for League entity CRUD operations.
* Defines async methods using domain entities as types.
*/
import { League } from '../../domain/entities/League';
export interface ILeagueRepository {
/**
* Find a league by ID
*/
findById(id: string): Promise<League | null>;
/**
* Find all leagues
*/
findAll(): Promise<League[]>;
/**
* Find leagues by owner ID
*/
findByOwnerId(ownerId: string): Promise<League[]>;
/**
* Create a new league
*/
create(league: League): Promise<League>;
/**
* Update an existing league
*/
update(league: League): Promise<League>;
/**
* Delete a league by ID
*/
delete(id: string): Promise<void>;
/**
* Check if a league exists by ID
*/
exists(id: string): Promise<boolean>;
/**
* Search leagues by name
*/
searchByName(query: string): Promise<League[]>;
}

View File

@@ -0,0 +1,65 @@
/**
* Application Port: IRaceRepository
*
* Repository interface for Race entity CRUD operations.
* Defines async methods using domain entities as types.
*/
import { Race, RaceStatus } from '../../domain/entities/Race';
export interface IRaceRepository {
/**
* Find a race by ID
*/
findById(id: string): Promise<Race | null>;
/**
* Find all races
*/
findAll(): Promise<Race[]>;
/**
* Find races by league ID
*/
findByLeagueId(leagueId: string): Promise<Race[]>;
/**
* Find upcoming races for a league
*/
findUpcomingByLeagueId(leagueId: string): Promise<Race[]>;
/**
* Find completed races for a league
*/
findCompletedByLeagueId(leagueId: string): Promise<Race[]>;
/**
* Find races by status
*/
findByStatus(status: RaceStatus): Promise<Race[]>;
/**
* Find races scheduled within a date range
*/
findByDateRange(startDate: Date, endDate: Date): Promise<Race[]>;
/**
* Create a new race
*/
create(race: Race): Promise<Race>;
/**
* Update an existing race
*/
update(race: Race): Promise<Race>;
/**
* Delete a race by ID
*/
delete(id: string): Promise<void>;
/**
* Check if a race exists by ID
*/
exists(id: string): Promise<boolean>;
}

View File

@@ -0,0 +1,70 @@
/**
* Application Port: IResultRepository
*
* Repository interface for Result entity CRUD operations.
* Defines async methods using domain entities as types.
*/
import { Result } from '../../domain/entities/Result';
export interface IResultRepository {
/**
* Find a result by ID
*/
findById(id: string): Promise<Result | null>;
/**
* Find all results
*/
findAll(): Promise<Result[]>;
/**
* Find results by race ID
*/
findByRaceId(raceId: string): Promise<Result[]>;
/**
* Find results by driver ID
*/
findByDriverId(driverId: string): Promise<Result[]>;
/**
* Find results by driver ID for a specific league
*/
findByDriverIdAndLeagueId(driverId: string, leagueId: string): Promise<Result[]>;
/**
* Create a new result
*/
create(result: Result): Promise<Result>;
/**
* Create multiple results
*/
createMany(results: Result[]): Promise<Result[]>;
/**
* Update an existing result
*/
update(result: Result): Promise<Result>;
/**
* Delete a result by ID
*/
delete(id: string): Promise<void>;
/**
* Delete all results for a race
*/
deleteByRaceId(raceId: string): Promise<void>;
/**
* Check if a result exists by ID
*/
exists(id: string): Promise<boolean>;
/**
* Check if results exist for a race
*/
existsByRaceId(raceId: string): Promise<boolean>;
}

View File

@@ -0,0 +1,55 @@
/**
* Application Port: IStandingRepository
*
* Repository interface for Standing entity operations.
* Includes methods for calculating and retrieving standings.
*/
import { Standing } from '../../domain/entities/Standing';
export interface IStandingRepository {
/**
* Find standings by league ID (sorted by position)
*/
findByLeagueId(leagueId: string): Promise<Standing[]>;
/**
* Find standing for a specific driver in a league
*/
findByDriverIdAndLeagueId(driverId: string, leagueId: string): Promise<Standing | null>;
/**
* Find all standings
*/
findAll(): Promise<Standing[]>;
/**
* Create or update a standing
*/
save(standing: Standing): Promise<Standing>;
/**
* Create or update multiple standings
*/
saveMany(standings: Standing[]): Promise<Standing[]>;
/**
* Delete a standing
*/
delete(leagueId: string, driverId: string): Promise<void>;
/**
* Delete all standings for a league
*/
deleteByLeagueId(leagueId: string): Promise<void>;
/**
* Check if a standing exists
*/
exists(leagueId: string, driverId: string): Promise<boolean>;
/**
* Recalculate standings for a league based on race results
*/
recalculate(leagueId: string): Promise<Standing[]>;
}