/** * Domain Entity: LiveryTemplate * * Represents an admin-defined livery template for a specific car. * Contains base image and sponsor decal placements. */ import { RacingDomainValidationError, RacingDomainInvariantError, RacingDomainError } from '../errors/RacingDomainError'; import type { IEntity } from '@gridpilot/shared/domain'; import type { LiveryDecal } from '../value-objects/LiveryDecal'; export interface LiveryTemplateProps { id: string; leagueId: string; seasonId: string; carId: string; baseImageUrl: string; adminDecals: LiveryDecal[]; createdAt: Date; updatedAt: Date | undefined; } export class LiveryTemplate implements IEntity { readonly id: string; readonly leagueId: string; readonly seasonId: string; readonly carId: string; readonly baseImageUrl: string; readonly adminDecals: LiveryDecal[]; readonly createdAt: Date; readonly updatedAt: Date | undefined; private constructor(props: LiveryTemplateProps) { this.id = props.id; this.leagueId = props.leagueId; this.seasonId = props.seasonId; this.carId = props.carId; this.baseImageUrl = props.baseImageUrl; this.adminDecals = props.adminDecals; this.createdAt = props.createdAt ?? new Date(); this.updatedAt = props.updatedAt; } static create(props: Omit & { createdAt?: Date; adminDecals?: LiveryDecal[]; }): LiveryTemplate { this.validate(props); return new LiveryTemplate({ ...props, createdAt: props.createdAt ?? new Date(), adminDecals: props.adminDecals ?? [], }); } private static validate(props: Omit): void { if (!props.id || props.id.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate ID is required'); } if (!props.leagueId || props.leagueId.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate leagueId is required'); } if (!props.seasonId || props.seasonId.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate seasonId is required'); } if (!props.carId || props.carId.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate carId is required'); } if (!props.baseImageUrl || props.baseImageUrl.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate baseImageUrl is required'); } } /** * Add a decal to the template */ addDecal(decal: LiveryDecal): LiveryTemplate { if (decal.type !== 'sponsor') { throw new RacingDomainInvariantError('Only sponsor decals can be added to admin template'); } return new LiveryTemplate({ ...this, adminDecals: [...this.adminDecals, decal], updatedAt: new Date(), }); } /** * Remove a decal from the template */ removeDecal(decalId: string): LiveryTemplate { const updatedDecals = this.adminDecals.filter(d => d.id !== decalId); if (updatedDecals.length === this.adminDecals.length) { throw new RacingDomainValidationError('Decal not found in template'); } return new LiveryTemplate({ ...this, adminDecals: updatedDecals, updatedAt: new Date(), }); } /** * Update a decal position */ updateDecal(decalId: string, updatedDecal: LiveryDecal): LiveryTemplate { const index = this.adminDecals.findIndex(d => d.id === decalId); if (index === -1) { throw new RacingDomainValidationError('Decal not found in template'); } const updatedDecals = [...this.adminDecals]; updatedDecals[index] = updatedDecal; return new LiveryTemplate({ ...this, adminDecals: updatedDecals, updatedAt: new Date(), }); } /** * Get all sponsor decals */ getSponsorDecals(): LiveryDecal[] { return this.adminDecals.filter(d => d.type === 'sponsor'); } /** * Check if template has sponsor decals */ hasSponsorDecals(): boolean { return this.adminDecals.some(d => d.type === 'sponsor'); } }