Files
gridpilot.gg/packages/racing/domain/entities/LiveryTemplate.ts
2025-12-12 14:23:40 +01:00

144 lines
4.0 KiB
TypeScript

/**
* 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<string> {
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<LiveryTemplateProps, 'createdAt' | 'adminDecals'> & {
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<LiveryTemplateProps, 'createdAt' | 'adminDecals'>): 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');
}
}