/** * Domain Entity: DriverLivery * * Represents a driver's custom livery for a specific car. * Includes user-placed decals and league-specific overrides. */ import { RacingDomainValidationError, RacingDomainInvariantError, RacingDomainError } from '../errors/RacingDomainError'; import type { IEntity } from '@gridpilot/shared/domain'; import type { LiveryDecal } from '../value-objects/LiveryDecal'; export interface DecalOverride { leagueId: string; seasonId: string; decalId: string; newX: number; newY: number; } export interface DriverLiveryProps { id: string; driverId: string; gameId: string; carId: string; uploadedImageUrl: string; userDecals: LiveryDecal[]; leagueOverrides: DecalOverride[]; createdAt: Date; updatedAt: Date | undefined; validatedAt: Date | undefined; } export class DriverLivery implements IEntity { readonly id: string; readonly driverId: string; readonly gameId: string; readonly carId: string; readonly uploadedImageUrl: string; 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: Omit & { createdAt?: Date; userDecals?: LiveryDecal[]; leagueOverrides?: DecalOverride[]; }): DriverLivery { this.validate(props); return new DriverLivery({ ...props, createdAt: props.createdAt ?? new Date(), userDecals: props.userDecals ?? [], leagueOverrides: props.leagueOverrides ?? [], }); } private static validate(props: Omit): 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 = { 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; } }