/** * Domain Entity: Sponsor * * Represents a sponsor that can sponsor leagues/seasons. * Aggregate root for sponsor information. */ import { RacingDomainValidationError } from '../errors/RacingDomainError'; import type { IEntity } from '@gridpilot/shared/domain'; export interface SponsorProps { id: string; name: string; contactEmail: string; logoUrl?: string; websiteUrl?: string; createdAt: Date; } export class Sponsor implements IEntity { readonly id: string; readonly name: string; readonly contactEmail: string; readonly logoUrl: string | undefined; readonly websiteUrl: string | undefined; readonly createdAt: Date; private constructor(props: SponsorProps) { this.id = props.id; this.name = props.name; this.contactEmail = props.contactEmail; this.logoUrl = props.logoUrl; this.websiteUrl = props.websiteUrl; this.createdAt = props.createdAt; } static create(props: Omit & { createdAt?: Date }): Sponsor { this.validate(props); const { createdAt, ...rest } = props; const base = { id: rest.id, name: rest.name, contactEmail: rest.contactEmail, createdAt: createdAt ?? new Date(), }; const withLogo = rest.logoUrl !== undefined ? { ...base, logoUrl: rest.logoUrl } : base; const withWebsite = rest.websiteUrl !== undefined ? { ...withLogo, websiteUrl: rest.websiteUrl } : withLogo; return new Sponsor(withWebsite); } private static validate(props: Omit): void { if (!props.id || props.id.trim().length === 0) { throw new RacingDomainValidationError('Sponsor ID is required'); } if (!props.name || props.name.trim().length === 0) { throw new RacingDomainValidationError('Sponsor name is required'); } if (props.name.length > 100) { throw new RacingDomainValidationError('Sponsor name must be 100 characters or less'); } if (!props.contactEmail || props.contactEmail.trim().length === 0) { throw new RacingDomainValidationError('Sponsor contact email is required'); } // Basic email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(props.contactEmail)) { throw new RacingDomainValidationError('Invalid sponsor contact email format'); } if (props.websiteUrl && props.websiteUrl.trim().length > 0) { try { new URL(props.websiteUrl); } catch { throw new RacingDomainValidationError('Invalid sponsor website URL'); } } } /** * Update sponsor information */ update(props: Partial<{ name: string; contactEmail: string; logoUrl?: string; websiteUrl?: string; }>): Sponsor { const updatedBase = { id: this.id, name: props.name ?? this.name, contactEmail: props.contactEmail ?? this.contactEmail, createdAt: this.createdAt, }; const withLogo = props.logoUrl !== undefined ? { ...updatedBase, logoUrl: props.logoUrl } : this.logoUrl !== undefined ? { ...updatedBase, logoUrl: this.logoUrl } : updatedBase; const updated = props.websiteUrl !== undefined ? { ...withLogo, websiteUrl: props.websiteUrl } : this.websiteUrl !== undefined ? { ...withLogo, websiteUrl: this.websiteUrl } : withLogo; Sponsor.validate(updated); return new Sponsor(updated); } }