Files
gridpilot.gg/packages/racing/domain/entities/Sponsor.ts
2025-12-12 01:11:36 +01:00

122 lines
3.4 KiB
TypeScript

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