refactor
This commit is contained in:
@@ -13,107 +13,136 @@
|
||||
*/
|
||||
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
|
||||
import type { IEntity } from '@core/shared/domain';
|
||||
|
||||
export type ProtestStatus = 'pending' | 'awaiting_defense' | 'under_review' | 'upheld' | 'dismissed' | 'withdrawn';
|
||||
|
||||
export interface ProtestIncident {
|
||||
/** Lap number where the incident occurred */
|
||||
lap: number;
|
||||
/** Time in the race (seconds from start, or timestamp) */
|
||||
timeInRace?: number;
|
||||
/** Brief description of the incident */
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface ProtestDefense {
|
||||
/** The accused driver's statement/defense */
|
||||
statement: string;
|
||||
/** URL to defense video clip (optional) */
|
||||
videoUrl?: string;
|
||||
/** Timestamp when defense was submitted */
|
||||
submittedAt: Date;
|
||||
}
|
||||
import { ProtestId } from './ProtestId';
|
||||
import { RaceId } from './RaceId';
|
||||
import { DriverId } from './DriverId';
|
||||
import { StewardId } from './StewardId';
|
||||
import { ProtestStatus } from './ProtestStatus';
|
||||
import { ProtestIncident } from './ProtestIncident';
|
||||
import { ProtestComment } from './ProtestComment';
|
||||
import { VideoUrl } from './VideoUrl';
|
||||
import { FiledAt } from './FiledAt';
|
||||
import { ReviewedAt } from './ReviewedAt';
|
||||
import { ProtestDefense } from './ProtestDefense';
|
||||
import { DefenseRequestedAt } from './DefenseRequestedAt';
|
||||
import { DecisionNotes } from './DecisionNotes';
|
||||
|
||||
export interface ProtestProps {
|
||||
id: string;
|
||||
raceId: string;
|
||||
id: ProtestId;
|
||||
raceId: RaceId;
|
||||
/** The driver filing the protest */
|
||||
protestingDriverId: string;
|
||||
protestingDriverId: DriverId;
|
||||
/** The driver being protested against */
|
||||
accusedDriverId: string;
|
||||
accusedDriverId: DriverId;
|
||||
/** Details of the incident */
|
||||
incident: ProtestIncident;
|
||||
/** Optional comment/statement from the protesting driver */
|
||||
comment?: string;
|
||||
comment?: ProtestComment;
|
||||
/** URL to proof video clip */
|
||||
proofVideoUrl?: string;
|
||||
proofVideoUrl?: VideoUrl;
|
||||
/** Current status of the protest */
|
||||
status: ProtestStatus;
|
||||
/** ID of the steward/admin who reviewed (if any) */
|
||||
reviewedBy?: string;
|
||||
reviewedBy?: StewardId;
|
||||
/** Decision notes from the steward */
|
||||
decisionNotes?: string;
|
||||
decisionNotes?: DecisionNotes;
|
||||
/** Timestamp when the protest was filed */
|
||||
filedAt: Date;
|
||||
filedAt: FiledAt;
|
||||
/** Timestamp when the protest was reviewed */
|
||||
reviewedAt?: Date;
|
||||
reviewedAt?: ReviewedAt;
|
||||
/** Defense from the accused driver (if requested and submitted) */
|
||||
defense?: ProtestDefense;
|
||||
/** Timestamp when defense was requested */
|
||||
defenseRequestedAt?: Date;
|
||||
defenseRequestedAt?: DefenseRequestedAt;
|
||||
/** ID of the steward who requested defense */
|
||||
defenseRequestedBy?: string;
|
||||
defenseRequestedBy?: StewardId;
|
||||
}
|
||||
|
||||
export class Protest implements IEntity<string> {
|
||||
private constructor(private readonly props: ProtestProps) {}
|
||||
|
||||
static create(props: ProtestProps): Protest {
|
||||
if (!props.id) throw new RacingDomainValidationError('Protest ID is required');
|
||||
if (!props.raceId) throw new RacingDomainValidationError('Race ID is required');
|
||||
if (!props.protestingDriverId) throw new RacingDomainValidationError('Protesting driver ID is required');
|
||||
if (!props.accusedDriverId) throw new RacingDomainValidationError('Accused driver ID is required');
|
||||
if (!props.incident) throw new RacingDomainValidationError('Incident details are required');
|
||||
if (props.incident.lap < 0) throw new RacingDomainValidationError('Lap number must be non-negative');
|
||||
if (!props.incident.description?.trim()) throw new RacingDomainValidationError('Incident description is required');
|
||||
|
||||
static create(props: {
|
||||
id: string;
|
||||
raceId: string;
|
||||
protestingDriverId: string;
|
||||
accusedDriverId: string;
|
||||
incident: { lap: number; description: string; timeInRace?: number };
|
||||
comment?: string;
|
||||
proofVideoUrl?: string;
|
||||
status?: string;
|
||||
reviewedBy?: string;
|
||||
decisionNotes?: string;
|
||||
filedAt?: Date;
|
||||
reviewedAt?: Date;
|
||||
defense?: { statement: string; videoUrl?: string; submittedAt: Date };
|
||||
defenseRequestedAt?: Date;
|
||||
defenseRequestedBy?: string;
|
||||
}): Protest {
|
||||
const id = ProtestId.create(props.id);
|
||||
const raceId = RaceId.create(props.raceId);
|
||||
const protestingDriverId = DriverId.create(props.protestingDriverId);
|
||||
const accusedDriverId = DriverId.create(props.accusedDriverId);
|
||||
const incident = ProtestIncident.create(props.incident.lap, props.incident.description, props.incident.timeInRace);
|
||||
const comment = props.comment ? ProtestComment.create(props.comment) : undefined;
|
||||
const proofVideoUrl = props.proofVideoUrl ? VideoUrl.create(props.proofVideoUrl) : undefined;
|
||||
const status = ProtestStatus.create(props.status || 'pending');
|
||||
const reviewedBy = props.reviewedBy ? StewardId.create(props.reviewedBy) : undefined;
|
||||
const decisionNotes = props.decisionNotes ? DecisionNotes.create(props.decisionNotes) : undefined;
|
||||
const filedAt = FiledAt.create(props.filedAt || new Date());
|
||||
const reviewedAt = props.reviewedAt ? ReviewedAt.create(props.reviewedAt) : undefined;
|
||||
const defense = props.defense ? ProtestDefense.create(props.defense.statement, props.defense.submittedAt, props.defense.videoUrl) : undefined;
|
||||
const defenseRequestedAt = props.defenseRequestedAt ? DefenseRequestedAt.create(props.defenseRequestedAt) : undefined;
|
||||
const defenseRequestedBy = props.defenseRequestedBy ? StewardId.create(props.defenseRequestedBy) : undefined;
|
||||
|
||||
return new Protest({
|
||||
...props,
|
||||
status: props.status || 'pending',
|
||||
filedAt: props.filedAt || new Date(),
|
||||
id,
|
||||
raceId,
|
||||
protestingDriverId,
|
||||
accusedDriverId,
|
||||
incident,
|
||||
comment,
|
||||
proofVideoUrl,
|
||||
status,
|
||||
reviewedBy,
|
||||
decisionNotes,
|
||||
filedAt,
|
||||
reviewedAt,
|
||||
defense,
|
||||
defenseRequestedAt,
|
||||
defenseRequestedBy,
|
||||
});
|
||||
}
|
||||
|
||||
get id(): string { return this.props.id; }
|
||||
get raceId(): string { return this.props.raceId; }
|
||||
get protestingDriverId(): string { return this.props.protestingDriverId; }
|
||||
get accusedDriverId(): string { return this.props.accusedDriverId; }
|
||||
get incident(): ProtestIncident { return { ...this.props.incident }; }
|
||||
get comment(): string | undefined { return this.props.comment; }
|
||||
get proofVideoUrl(): string | undefined { return this.props.proofVideoUrl; }
|
||||
get id(): string { return this.props.id.toString(); }
|
||||
get raceId(): string { return this.props.raceId.toString(); }
|
||||
get protestingDriverId(): string { return this.props.protestingDriverId.toString(); }
|
||||
get accusedDriverId(): string { return this.props.accusedDriverId.toString(); }
|
||||
get incident(): ProtestIncident { return this.props.incident; }
|
||||
get comment(): string | undefined { return this.props.comment?.toString(); }
|
||||
get proofVideoUrl(): string | undefined { return this.props.proofVideoUrl?.toString(); }
|
||||
get status(): ProtestStatus { return this.props.status; }
|
||||
get reviewedBy(): string | undefined { return this.props.reviewedBy; }
|
||||
get decisionNotes(): string | undefined { return this.props.decisionNotes; }
|
||||
get filedAt(): Date { return this.props.filedAt; }
|
||||
get reviewedAt(): Date | undefined { return this.props.reviewedAt; }
|
||||
get defense(): ProtestDefense | undefined { return this.props.defense ? { ...this.props.defense } : undefined; }
|
||||
get defenseRequestedAt(): Date | undefined { return this.props.defenseRequestedAt; }
|
||||
get defenseRequestedBy(): string | undefined { return this.props.defenseRequestedBy; }
|
||||
get reviewedBy(): string | undefined { return this.props.reviewedBy?.toString(); }
|
||||
get decisionNotes(): string | undefined { return this.props.decisionNotes?.toString(); }
|
||||
get filedAt(): Date { return this.props.filedAt.toDate(); }
|
||||
get reviewedAt(): Date | undefined { return this.props.reviewedAt?.toDate(); }
|
||||
get defense(): ProtestDefense | undefined { return this.props.defense; }
|
||||
get defenseRequestedAt(): Date | undefined { return this.props.defenseRequestedAt?.toDate(); }
|
||||
get defenseRequestedBy(): string | undefined { return this.props.defenseRequestedBy?.toString(); }
|
||||
|
||||
isPending(): boolean {
|
||||
return this.props.status === 'pending';
|
||||
return this.props.status.toString() === 'pending';
|
||||
}
|
||||
|
||||
isAwaitingDefense(): boolean {
|
||||
return this.props.status === 'awaiting_defense';
|
||||
return this.props.status.toString() === 'awaiting_defense';
|
||||
}
|
||||
|
||||
isUnderReview(): boolean {
|
||||
return this.props.status === 'under_review';
|
||||
return this.props.status.toString() === 'under_review';
|
||||
}
|
||||
|
||||
isResolved(): boolean {
|
||||
return ['upheld', 'dismissed', 'withdrawn'].includes(this.props.status);
|
||||
return ['upheld', 'dismissed', 'withdrawn'].includes(this.props.status.toString());
|
||||
}
|
||||
|
||||
hasDefense(): boolean {
|
||||
@@ -137,9 +166,9 @@ export class Protest implements IEntity<string> {
|
||||
}
|
||||
return new Protest({
|
||||
...this.props,
|
||||
status: 'awaiting_defense',
|
||||
defenseRequestedAt: new Date(),
|
||||
defenseRequestedBy: stewardId,
|
||||
status: ProtestStatus.create('awaiting_defense'),
|
||||
defenseRequestedAt: DefenseRequestedAt.create(new Date()),
|
||||
defenseRequestedBy: StewardId.create(stewardId),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,18 +182,12 @@ export class Protest implements IEntity<string> {
|
||||
if (!statement?.trim()) {
|
||||
throw new RacingDomainValidationError('Defense statement is required');
|
||||
}
|
||||
const defenseBase: ProtestDefense = {
|
||||
statement: statement.trim(),
|
||||
submittedAt: new Date(),
|
||||
};
|
||||
|
||||
const nextDefense: ProtestDefense =
|
||||
videoUrl !== undefined ? { ...defenseBase, videoUrl } : defenseBase;
|
||||
const defense = ProtestDefense.create(statement.trim(), new Date(), videoUrl);
|
||||
|
||||
return new Protest({
|
||||
...this.props,
|
||||
status: 'under_review',
|
||||
defense: nextDefense,
|
||||
status: ProtestStatus.create('under_review'),
|
||||
defense,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,8 +200,8 @@ export class Protest implements IEntity<string> {
|
||||
}
|
||||
return new Protest({
|
||||
...this.props,
|
||||
status: 'under_review',
|
||||
reviewedBy: stewardId,
|
||||
status: ProtestStatus.create('under_review'),
|
||||
reviewedBy: StewardId.create(stewardId),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,10 +214,10 @@ export class Protest implements IEntity<string> {
|
||||
}
|
||||
return new Protest({
|
||||
...this.props,
|
||||
status: 'upheld',
|
||||
reviewedBy: stewardId,
|
||||
decisionNotes,
|
||||
reviewedAt: new Date(),
|
||||
status: ProtestStatus.create('upheld'),
|
||||
reviewedBy: StewardId.create(stewardId),
|
||||
decisionNotes: DecisionNotes.create(decisionNotes),
|
||||
reviewedAt: ReviewedAt.create(new Date()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,10 +230,10 @@ export class Protest implements IEntity<string> {
|
||||
}
|
||||
return new Protest({
|
||||
...this.props,
|
||||
status: 'dismissed',
|
||||
reviewedBy: stewardId,
|
||||
decisionNotes,
|
||||
reviewedAt: new Date(),
|
||||
status: ProtestStatus.create('dismissed'),
|
||||
reviewedBy: StewardId.create(stewardId),
|
||||
decisionNotes: DecisionNotes.create(decisionNotes),
|
||||
reviewedAt: ReviewedAt.create(new Date()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -223,8 +246,8 @@ export class Protest implements IEntity<string> {
|
||||
}
|
||||
return new Protest({
|
||||
...this.props,
|
||||
status: 'withdrawn',
|
||||
reviewedAt: new Date(),
|
||||
status: ProtestStatus.create('withdrawn'),
|
||||
reviewedAt: ReviewedAt.create(new Date()),
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user