This commit is contained in:
2025-12-09 10:32:59 +01:00
parent 35f988f885
commit a780139692
26 changed files with 2224 additions and 344 deletions

View File

@@ -1,85 +1,70 @@
/**
* Infrastructure Adapter: InMemoryPenaltyRepository
*
* Simple in-memory implementation of IPenaltyRepository seeded with
* a handful of demo penalties and bonuses for leagues/drivers.
* In-Memory Implementation: InMemoryPenaltyRepository
*
* Provides an in-memory storage implementation for penalties.
*/
import type { Penalty } from '@gridpilot/racing/domain/entities/Penalty';
import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/IPenaltyRepository';
import type { Penalty } from '../../domain/entities/Penalty';
import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository';
export class InMemoryPenaltyRepository implements IPenaltyRepository {
private readonly penalties: Penalty[];
private penalties: Map<string, Penalty> = new Map();
constructor(seedPenalties?: Penalty[]) {
this.penalties = seedPenalties ? [...seedPenalties] : InMemoryPenaltyRepository.createDefaultSeed();
constructor(initialPenalties: Penalty[] = []) {
initialPenalties.forEach(penalty => {
this.penalties.set(penalty.id, penalty);
});
}
async findByLeagueId(leagueId: string): Promise<Penalty[]> {
return this.penalties.filter((p) => p.leagueId === leagueId);
async findById(id: string): Promise<Penalty | null> {
return this.penalties.get(id) || null;
}
async findByLeagueIdAndDriverId(leagueId: string, driverId: string): Promise<Penalty[]> {
return this.penalties.filter((p) => p.leagueId === leagueId && p.driverId === driverId);
async findByRaceId(raceId: string): Promise<Penalty[]> {
return Array.from(this.penalties.values()).filter(
penalty => penalty.raceId === raceId
);
}
async findAll(): Promise<Penalty[]> {
return [...this.penalties];
async findByDriverId(driverId: string): Promise<Penalty[]> {
return Array.from(this.penalties.values()).filter(
penalty => penalty.driverId === driverId
);
}
/**
* Default demo seed with a mix of deductions and bonuses
* across a couple of leagues and drivers.
*/
private static createDefaultSeed(): Penalty[] {
const now = new Date();
const daysAgo = (n: number) => new Date(now.getTime() - n * 24 * 60 * 60 * 1000);
async findByProtestId(protestId: string): Promise<Penalty[]> {
return Array.from(this.penalties.values()).filter(
penalty => penalty.protestId === protestId
);
}
return [
{
id: 'pen-league-1-driver-1-main',
leagueId: 'league-1',
driverId: 'driver-1',
type: 'points-deduction',
pointsDelta: -3,
reason: 'Incident points penalty',
appliedAt: daysAgo(7),
},
{
id: 'pen-league-1-driver-2-bonus',
leagueId: 'league-1',
driverId: 'driver-2',
type: 'points-bonus',
pointsDelta: 2,
reason: 'Fastest laps bonus',
appliedAt: daysAgo(5),
},
{
id: 'pen-league-1-driver-3-bonus',
leagueId: 'league-1',
driverId: 'driver-3',
type: 'points-bonus',
pointsDelta: 1,
reason: 'Pole position bonus',
appliedAt: daysAgo(3),
},
{
id: 'pen-league-2-driver-4-main',
leagueId: 'league-2',
driverId: 'driver-4',
type: 'points-deduction',
pointsDelta: -5,
reason: 'Post-race steward decision',
appliedAt: daysAgo(10),
},
{
id: 'pen-league-2-driver-5-bonus',
leagueId: 'league-2',
driverId: 'driver-5',
type: 'points-bonus',
pointsDelta: 3,
reason: 'Clean race awards',
appliedAt: daysAgo(2),
},
];
async findPending(): Promise<Penalty[]> {
return Array.from(this.penalties.values()).filter(
penalty => penalty.isPending()
);
}
async findIssuedBy(stewardId: string): Promise<Penalty[]> {
return Array.from(this.penalties.values()).filter(
penalty => penalty.issuedBy === stewardId
);
}
async create(penalty: Penalty): Promise<void> {
if (this.penalties.has(penalty.id)) {
throw new Error(`Penalty with ID ${penalty.id} already exists`);
}
this.penalties.set(penalty.id, penalty);
}
async update(penalty: Penalty): Promise<void> {
if (!this.penalties.has(penalty.id)) {
throw new Error(`Penalty with ID ${penalty.id} not found`);
}
this.penalties.set(penalty.id, penalty);
}
async exists(id: string): Promise<boolean> {
return this.penalties.has(id);
}
}

View File

@@ -0,0 +1,70 @@
/**
* In-Memory Implementation: InMemoryProtestRepository
*
* Provides an in-memory storage implementation for protests.
*/
import type { Protest } from '../../domain/entities/Protest';
import type { IProtestRepository } from '../../domain/repositories/IProtestRepository';
export class InMemoryProtestRepository implements IProtestRepository {
private protests: Map<string, Protest> = new Map();
constructor(initialProtests: Protest[] = []) {
initialProtests.forEach(protest => {
this.protests.set(protest.id, protest);
});
}
async findById(id: string): Promise<Protest | null> {
return this.protests.get(id) || null;
}
async findByRaceId(raceId: string): Promise<Protest[]> {
return Array.from(this.protests.values()).filter(
protest => protest.raceId === raceId
);
}
async findByProtestingDriverId(driverId: string): Promise<Protest[]> {
return Array.from(this.protests.values()).filter(
protest => protest.protestingDriverId === driverId
);
}
async findByAccusedDriverId(driverId: string): Promise<Protest[]> {
return Array.from(this.protests.values()).filter(
protest => protest.accusedDriverId === driverId
);
}
async findPending(): Promise<Protest[]> {
return Array.from(this.protests.values()).filter(
protest => protest.isPending()
);
}
async findUnderReviewBy(stewardId: string): Promise<Protest[]> {
return Array.from(this.protests.values()).filter(
protest => protest.reviewedBy === stewardId && protest.isUnderReview()
);
}
async create(protest: Protest): Promise<void> {
if (this.protests.has(protest.id)) {
throw new Error(`Protest with ID ${protest.id} already exists`);
}
this.protests.set(protest.id, protest);
}
async update(protest: Protest): Promise<void> {
if (!this.protests.has(protest.id)) {
throw new Error(`Protest with ID ${protest.id} not found`);
}
this.protests.set(protest.id, protest);
}
async exists(id: string): Promise<boolean> {
return this.protests.has(id);
}
}