Files
gridpilot.gg/core/racing/domain/entities/DriverLivery.ts
2026-01-16 16:46:57 +01:00

270 lines
7.8 KiB
TypeScript

/**
* Domain Entity: DriverLivery
*
* Represents a driver's custom livery for a specific car.
* Includes user-placed decals and league-specific overrides.
*/
import { Entity } from '@core/shared/domain/Entity';
import { RacingDomainInvariantError, RacingDomainValidationError } from '../errors/RacingDomainError';
import { CarId } from '../value-objects/CarId';
import { DecalOverride } from '../value-objects/DecalOverride';
import { DriverId } from '../value-objects/driver/DriverId';
import { ImageUrl } from '../value-objects/ImageUrl';
import { LiveryDecal } from '../value-objects/LiveryDecal';
import { GameId } from './GameId';
export interface DriverLiveryProps {
id: string;
driverId: DriverId;
gameId: GameId;
carId: CarId;
uploadedImageUrl: ImageUrl;
userDecals: LiveryDecal[];
leagueOverrides: DecalOverride[];
createdAt: Date;
updatedAt: Date | undefined;
validatedAt: Date | undefined;
}
export class DriverLivery extends Entity<string> {
readonly driverId: DriverId;
readonly gameId: GameId;
readonly carId: CarId;
readonly uploadedImageUrl: ImageUrl;
readonly userDecals: LiveryDecal[];
readonly leagueOverrides: DecalOverride[];
readonly createdAt: Date;
readonly updatedAt: Date | undefined;
readonly validatedAt: Date | undefined;
private constructor(props: DriverLiveryProps) {
super(props.id);
this.driverId = props.driverId;
this.gameId = props.gameId;
this.carId = props.carId;
this.uploadedImageUrl = props.uploadedImageUrl;
this.userDecals = props.userDecals;
this.leagueOverrides = props.leagueOverrides;
this.createdAt = props.createdAt ?? new Date();
this.updatedAt = props.updatedAt;
this.validatedAt = props.validatedAt;
}
static create(props: {
id: string;
driverId: string;
gameId: string;
carId: string;
uploadedImageUrl: string;
createdAt?: Date;
userDecals?: LiveryDecal[];
leagueOverrides?: DecalOverride[];
}): DriverLivery {
this.validate(props);
return new DriverLivery({
id: props.id,
driverId: DriverId.create(props.driverId),
gameId: GameId.create(props.gameId),
carId: CarId.create(props.carId),
uploadedImageUrl: ImageUrl.create(props.uploadedImageUrl),
userDecals: props.userDecals ?? [],
leagueOverrides: props.leagueOverrides ?? [],
createdAt: props.createdAt ?? new Date(),
updatedAt: undefined,
validatedAt: undefined,
});
}
private static validate(props: {
id: string;
driverId: string;
gameId: string;
carId: string;
uploadedImageUrl: string;
}): void {
if (!props.id || props.id.trim().length === 0) {
throw new RacingDomainValidationError('DriverLivery ID is required');
}
if (!props.driverId || props.driverId.trim().length === 0) {
throw new RacingDomainValidationError('DriverLivery driverId is required');
}
if (!props.gameId || props.gameId.trim().length === 0) {
throw new RacingDomainValidationError('DriverLivery gameId is required');
}
if (!props.carId || props.carId.trim().length === 0) {
throw new RacingDomainValidationError('DriverLivery carId is required');
}
if (!props.uploadedImageUrl || props.uploadedImageUrl.trim().length === 0) {
throw new RacingDomainValidationError('DriverLivery uploadedImageUrl is required');
}
}
/**
* Add a user decal
*/
addDecal(decal: LiveryDecal): DriverLivery {
if (decal.type !== 'user') {
throw new RacingDomainInvariantError('Only user decals can be added to driver livery');
}
return new DriverLivery({
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: [...this.userDecals, decal],
leagueOverrides: this.leagueOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
/**
* Remove a user decal
*/
removeDecal(decalId: string): DriverLivery {
const updatedDecals = this.userDecals.filter(d => d.id !== decalId);
if (updatedDecals.length === this.userDecals.length) {
throw new RacingDomainValidationError('Decal not found in livery');
}
return new DriverLivery({
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: updatedDecals,
leagueOverrides: this.leagueOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
/**
* Update a user decal
*/
updateDecal(decalId: string, updatedDecal: LiveryDecal): DriverLivery {
const index = this.userDecals.findIndex(d => d.id === decalId);
if (index === -1) {
throw new RacingDomainValidationError('Decal not found in livery');
}
const updatedDecals = [...this.userDecals];
updatedDecals[index] = updatedDecal;
return new DriverLivery({
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: updatedDecals,
leagueOverrides: this.leagueOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
/**
* Add or update a league-specific decal override
*/
setLeagueOverride(leagueId: string, seasonId: string, decalId: string, newX: number, newY: number): DriverLivery {
const existingIndex = this.leagueOverrides.findIndex(
o => o.leagueId === leagueId && o.seasonId === seasonId && o.decalId === decalId
);
const override = DecalOverride.create({ leagueId, seasonId, decalId, newX, newY });
let updatedOverrides: DecalOverride[];
if (existingIndex >= 0) {
updatedOverrides = [...this.leagueOverrides];
updatedOverrides[existingIndex] = override;
} else {
updatedOverrides = [...this.leagueOverrides, override];
}
return new DriverLivery({
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: this.userDecals,
leagueOverrides: updatedOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
/**
* Remove a league-specific override
*/
removeLeagueOverride(leagueId: string, seasonId: string, decalId: string): DriverLivery {
const updatedOverrides = this.leagueOverrides.filter(
o => !(o.leagueId === leagueId && o.seasonId === seasonId && o.decalId === decalId)
);
return new DriverLivery({
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: this.userDecals,
leagueOverrides: updatedOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
/**
* Get overrides for a specific league/season
*/
getOverridesFor(leagueId: string, seasonId: string): DecalOverride[] {
return this.leagueOverrides.filter(
o => o.leagueId === leagueId && o.seasonId === seasonId
);
}
/**
* Mark livery as validated (no logos/text detected)
*/
markAsValidated(): DriverLivery {
return new DriverLivery({
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: this.userDecals,
leagueOverrides: this.leagueOverrides,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
validatedAt: new Date(),
});
}
/**
* Check if livery is validated
*/
isValidated(): boolean {
return this.validatedAt !== undefined;
}
}