rename to core
This commit is contained in:
127
core/racing/domain/entities/Track.ts
Normal file
127
core/racing/domain/entities/Track.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Domain Entity: Track
|
||||
*
|
||||
* Represents a racing track/circuit in the GridPilot platform.
|
||||
* Immutable entity with factory methods and domain validation.
|
||||
*/
|
||||
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
import type { IEntity } from '@gridpilot/shared/domain';
|
||||
|
||||
export type TrackCategory = 'oval' | 'road' | 'street' | 'dirt';
|
||||
export type TrackDifficulty = 'beginner' | 'intermediate' | 'advanced' | 'expert';
|
||||
|
||||
export class Track implements IEntity<string> {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly shortName: string;
|
||||
readonly country: string;
|
||||
readonly category: TrackCategory;
|
||||
readonly difficulty: TrackDifficulty;
|
||||
readonly lengthKm: number;
|
||||
readonly turns: number;
|
||||
readonly imageUrl: string | undefined;
|
||||
readonly gameId: string;
|
||||
|
||||
private constructor(props: {
|
||||
id: string;
|
||||
name: string;
|
||||
shortName: string;
|
||||
country: string;
|
||||
category: TrackCategory;
|
||||
difficulty: TrackDifficulty;
|
||||
lengthKm: number;
|
||||
turns: number;
|
||||
imageUrl?: string | undefined;
|
||||
gameId: string;
|
||||
}) {
|
||||
this.id = props.id;
|
||||
this.name = props.name;
|
||||
this.shortName = props.shortName;
|
||||
this.country = props.country;
|
||||
this.category = props.category;
|
||||
this.difficulty = props.difficulty;
|
||||
this.lengthKm = props.lengthKm;
|
||||
this.turns = props.turns;
|
||||
this.imageUrl = props.imageUrl;
|
||||
this.gameId = props.gameId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new Track entity
|
||||
*/
|
||||
static create(props: {
|
||||
id: string;
|
||||
name: string;
|
||||
shortName?: string;
|
||||
country: string;
|
||||
category?: TrackCategory;
|
||||
difficulty?: TrackDifficulty;
|
||||
lengthKm: number;
|
||||
turns: number;
|
||||
imageUrl?: string;
|
||||
gameId: string;
|
||||
}): Track {
|
||||
this.validate(props);
|
||||
|
||||
const base = {
|
||||
id: props.id,
|
||||
name: props.name,
|
||||
shortName: props.shortName ?? props.name.slice(0, 3).toUpperCase(),
|
||||
country: props.country,
|
||||
category: props.category ?? 'road',
|
||||
difficulty: props.difficulty ?? 'intermediate',
|
||||
lengthKm: props.lengthKm,
|
||||
turns: props.turns,
|
||||
gameId: props.gameId,
|
||||
};
|
||||
|
||||
const withImage =
|
||||
props.imageUrl !== undefined ? { ...base, imageUrl: props.imageUrl } : base;
|
||||
|
||||
return new Track(withImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain validation logic
|
||||
*/
|
||||
private static validate(props: {
|
||||
id: string;
|
||||
name: string;
|
||||
country: string;
|
||||
lengthKm: number;
|
||||
turns: number;
|
||||
gameId: string;
|
||||
}): void {
|
||||
if (!props.id || props.id.trim().length === 0) {
|
||||
throw new RacingDomainValidationError('Track ID is required');
|
||||
}
|
||||
|
||||
if (!props.name || props.name.trim().length === 0) {
|
||||
throw new RacingDomainValidationError('Track name is required');
|
||||
}
|
||||
|
||||
if (!props.country || props.country.trim().length === 0) {
|
||||
throw new RacingDomainValidationError('Track country is required');
|
||||
}
|
||||
|
||||
if (props.lengthKm <= 0) {
|
||||
throw new RacingDomainValidationError('Track length must be positive');
|
||||
}
|
||||
|
||||
if (props.turns < 0) {
|
||||
throw new RacingDomainValidationError('Track turns cannot be negative');
|
||||
}
|
||||
|
||||
if (!props.gameId || props.gameId.trim().length === 0) {
|
||||
throw new RacingDomainValidationError('Game ID is required');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted length string
|
||||
*/
|
||||
getFormattedLength(): string {
|
||||
return `${this.lengthKm.toFixed(2)} km`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user