270 lines
7.8 KiB
TypeScript
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 type { IEntity } from '@core/shared/domain';
|
|
|
|
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
|
|
import { LiveryDecal } from '../value-objects/LiveryDecal';
|
|
import { DecalOverride } from '../value-objects/DecalOverride';
|
|
import { DriverId } from '../value-objects/driver/DriverId';
|
|
import { GameId } from './GameId';
|
|
import { CarId } from '../value-objects/CarId';
|
|
import { ImageUrl } from '../value-objects/ImageUrl';
|
|
|
|
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 implements IEntity<string> {
|
|
readonly id: 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) {
|
|
this.id = 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;
|
|
}
|
|
} |