wip
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
* Allows easy swapping to persistent repositories later.
|
||||
*/
|
||||
|
||||
import { Penalty } from '@gridpilot/racing/domain/entities/Penalty';
|
||||
import { Protest } from '@gridpilot/racing/domain/entities/Protest';
|
||||
import { Driver } from '@gridpilot/racing/domain/entities/Driver';
|
||||
import { League } from '@gridpilot/racing/domain/entities/League';
|
||||
import { Race } from '@gridpilot/racing/domain/entities/Race';
|
||||
@@ -21,6 +23,7 @@ import type { IRaceRepository } from '@gridpilot/racing/domain/repositories/IRac
|
||||
import type { IResultRepository } from '@gridpilot/racing/domain/repositories/IResultRepository';
|
||||
import type { IStandingRepository } from '@gridpilot/racing/domain/repositories/IStandingRepository';
|
||||
import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/IPenaltyRepository';
|
||||
import type { IProtestRepository } from '@gridpilot/racing/domain/repositories/IProtestRepository';
|
||||
import type { IGameRepository } from '@gridpilot/racing/domain/repositories/IGameRepository';
|
||||
import type { ISeasonRepository } from '@gridpilot/racing/domain/repositories/ISeasonRepository';
|
||||
import type { ILeagueScoringConfigRepository } from '@gridpilot/racing/domain/repositories/ILeagueScoringConfigRepository';
|
||||
@@ -43,6 +46,7 @@ import { InMemoryRaceRepository } from '@gridpilot/racing/infrastructure/reposit
|
||||
import { InMemoryResultRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryResultRepository';
|
||||
import { InMemoryStandingRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryStandingRepository';
|
||||
import { InMemoryPenaltyRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryPenaltyRepository';
|
||||
import { InMemoryProtestRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryProtestRepository';
|
||||
import { InMemoryTrackRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTrackRepository';
|
||||
import { InMemoryCarRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryCarRepository';
|
||||
import {
|
||||
@@ -83,6 +87,11 @@ import {
|
||||
GetLeagueFullConfigQuery,
|
||||
GetRaceWithSOFQuery,
|
||||
GetLeagueStatsQuery,
|
||||
FileProtestUseCase,
|
||||
ReviewProtestUseCase,
|
||||
ApplyPenaltyUseCase,
|
||||
GetRaceProtestsQuery,
|
||||
GetRacePenaltiesQuery,
|
||||
} from '@gridpilot/racing/application';
|
||||
import type { DriverRatingProvider } from '@gridpilot/racing/application';
|
||||
import {
|
||||
@@ -170,6 +179,7 @@ class DIContainer {
|
||||
private _resultRepository: IResultRepository;
|
||||
private _standingRepository: IStandingRepository;
|
||||
private _penaltyRepository: IPenaltyRepository;
|
||||
private _protestRepository: IProtestRepository;
|
||||
private _teamRepository: ITeamRepository;
|
||||
private _teamMembershipRepository: ITeamMembershipRepository;
|
||||
private _raceRegistrationRepository: IRaceRegistrationRepository;
|
||||
@@ -204,6 +214,12 @@ class DIContainer {
|
||||
private _getLeagueStatsQuery: GetLeagueStatsQuery;
|
||||
private _driverRatingProvider: DriverRatingProvider;
|
||||
|
||||
private _fileProtestUseCase: FileProtestUseCase;
|
||||
private _reviewProtestUseCase: ReviewProtestUseCase;
|
||||
private _applyPenaltyUseCase: ApplyPenaltyUseCase;
|
||||
private _getRaceProtestsQuery: GetRaceProtestsQuery;
|
||||
private _getRacePenaltiesQuery: GetRacePenaltiesQuery;
|
||||
|
||||
private _createTeamUseCase: CreateTeamUseCase;
|
||||
private _joinTeamUseCase: JoinTeamUseCase;
|
||||
private _leaveTeamUseCase: LeaveTeamUseCase;
|
||||
@@ -267,9 +283,123 @@ class DIContainer {
|
||||
}
|
||||
|
||||
this._raceRegistrationRepository = new InMemoryRaceRegistrationRepository(seedRaceRegistrations);
|
||||
|
||||
// Penalties (seeded in-memory adapter)
|
||||
this._penaltyRepository = new InMemoryPenaltyRepository();
|
||||
|
||||
// Seed sample penalties and protests for completed races across different leagues
|
||||
// Group completed races by league, then take 1-2 from each league to ensure coverage
|
||||
const completedRaces = seedData.races.filter(r => r.status === 'completed');
|
||||
const racesByLeague = new Map<string, typeof completedRaces>();
|
||||
for (const race of completedRaces) {
|
||||
const existing = racesByLeague.get(race.leagueId) || [];
|
||||
existing.push(race);
|
||||
racesByLeague.set(race.leagueId, existing);
|
||||
}
|
||||
|
||||
// Get up to 2 races per league for protest seeding
|
||||
const racesForProtests: Array<{ race: typeof completedRaces[0]; leagueIndex: number }> = [];
|
||||
let leagueIndex = 0;
|
||||
for (const [, leagueRaces] of racesByLeague) {
|
||||
// Sort by scheduled date, take earliest 2
|
||||
const sorted = [...leagueRaces].sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
|
||||
for (const race of sorted.slice(0, 2)) {
|
||||
racesForProtests.push({ race, leagueIndex });
|
||||
}
|
||||
leagueIndex++;
|
||||
}
|
||||
|
||||
const seededPenalties: Penalty[] = [];
|
||||
const seededProtests: Protest[] = [];
|
||||
|
||||
racesForProtests.forEach(({ race, leagueIndex: leagueIdx }, raceIndex) => {
|
||||
// Get results for this race to find drivers involved
|
||||
const raceResults = seedData.results.filter(r => r.raceId === race.id);
|
||||
if (raceResults.length < 4) return;
|
||||
|
||||
// Create 1-2 protests per race
|
||||
const protestCount = Math.min(2, raceResults.length - 2);
|
||||
for (let i = 0; i < protestCount; i++) {
|
||||
const protestingResult = raceResults[i + 2]; // Driver who finished 3rd or 4th
|
||||
const accusedResult = raceResults[i]; // Driver who finished 1st or 2nd
|
||||
|
||||
if (!protestingResult || !accusedResult) continue;
|
||||
|
||||
const protestStatuses: Array<'pending' | 'under_review' | 'upheld' | 'dismissed'> = ['pending', 'under_review', 'upheld', 'dismissed'];
|
||||
const status = protestStatuses[(raceIndex + i) % protestStatuses.length];
|
||||
|
||||
const protest = Protest.create({
|
||||
id: `protest-${race.id}-${i}`,
|
||||
raceId: race.id,
|
||||
protestingDriverId: protestingResult.driverId,
|
||||
accusedDriverId: accusedResult.driverId,
|
||||
incident: {
|
||||
lap: 5 + i * 3,
|
||||
description: i === 0
|
||||
? 'Unsafe rejoining to the track after going off, causing contact'
|
||||
: 'Aggressive defending, pushing competitor off track',
|
||||
},
|
||||
comment: i === 0
|
||||
? 'Driver rejoined directly into my racing line, causing contact and damaging my front wing.'
|
||||
: 'Driver moved under braking multiple times, forcing me off the circuit.',
|
||||
status,
|
||||
filedAt: new Date(Date.now() - (raceIndex + 1) * 24 * 60 * 60 * 1000),
|
||||
reviewedBy: status !== 'pending' ? primaryDriverId : undefined,
|
||||
decisionNotes: status === 'upheld'
|
||||
? 'After reviewing the evidence, the accused driver is found at fault. Penalty applied.'
|
||||
: status === 'dismissed'
|
||||
? 'No clear fault found. Racing incident.'
|
||||
: undefined,
|
||||
reviewedAt: status !== 'pending' ? new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000) : undefined,
|
||||
});
|
||||
|
||||
seededProtests.push(protest);
|
||||
|
||||
// If protest was upheld, create a penalty
|
||||
if (status === 'upheld') {
|
||||
const penaltyTypes: Array<'time_penalty' | 'points_deduction' | 'warning'> = ['time_penalty', 'points_deduction', 'warning'];
|
||||
const penaltyType = penaltyTypes[i % penaltyTypes.length];
|
||||
|
||||
const penalty = Penalty.create({
|
||||
id: `penalty-${race.id}-${i}`,
|
||||
raceId: race.id,
|
||||
driverId: accusedResult.driverId,
|
||||
type: penaltyType,
|
||||
value: penaltyType === 'time_penalty' ? 5 : penaltyType === 'points_deduction' ? 3 : undefined,
|
||||
reason: protest.incident.description,
|
||||
protestId: protest.id,
|
||||
issuedBy: primaryDriverId,
|
||||
status: 'applied',
|
||||
issuedAt: new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000),
|
||||
appliedAt: new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000),
|
||||
});
|
||||
|
||||
seededPenalties.push(penalty);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a direct penalty (not from protest) for some races
|
||||
if (raceIndex % 2 === 0 && raceResults.length > 5) {
|
||||
const penalizedResult = raceResults[4];
|
||||
if (penalizedResult) {
|
||||
const penalty = Penalty.create({
|
||||
id: `penalty-direct-${race.id}`,
|
||||
raceId: race.id,
|
||||
driverId: penalizedResult.driverId,
|
||||
type: 'time_penalty',
|
||||
value: 10,
|
||||
reason: 'Track limits violation - gained lasting advantage',
|
||||
issuedBy: primaryDriverId,
|
||||
status: 'applied',
|
||||
issuedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000),
|
||||
appliedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000),
|
||||
});
|
||||
|
||||
seededPenalties.push(penalty);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Penalties and protests with seeded data
|
||||
this._penaltyRepository = new InMemoryPenaltyRepository(seededPenalties);
|
||||
this._protestRepository = new InMemoryProtestRepository(seededProtests);
|
||||
|
||||
// Scoring preset provider and seeded game/season/scoring config repositories
|
||||
this._leagueScoringPresetProvider = new InMemoryLeagueScoringPresetProvider();
|
||||
@@ -396,22 +526,34 @@ class DIContainer {
|
||||
});
|
||||
}
|
||||
|
||||
// Seed a few pending join requests for demo leagues
|
||||
// Seed a few pending join requests for demo leagues (expanded to more leagues)
|
||||
const seededJoinRequests: JoinRequest[] = [];
|
||||
const demoLeagues = seedData.leagues.slice(0, 2);
|
||||
const extraDrivers = seedData.drivers.slice(3, 8);
|
||||
const demoLeagues = seedData.leagues.slice(0, 6); // Expanded from 2 to 6 leagues
|
||||
const extraDrivers = seedData.drivers.slice(5, 12); // More drivers for requests
|
||||
|
||||
demoLeagues.forEach((league) => {
|
||||
extraDrivers.forEach((driver, index) => {
|
||||
demoLeagues.forEach((league, leagueIndex) => {
|
||||
// Skip leagues where these drivers are already members
|
||||
const memberDriverIds = seededMemberships
|
||||
.filter(m => m.leagueId === league.id)
|
||||
.map(m => m.driverId);
|
||||
|
||||
const availableDrivers = extraDrivers.filter(d => !memberDriverIds.includes(d.id));
|
||||
const driversForThisLeague = availableDrivers.slice(0, 3 + (leagueIndex % 3)); // 3-5 requests per league
|
||||
|
||||
driversForThisLeague.forEach((driver, index) => {
|
||||
const messages = [
|
||||
'Would love to race in this series!',
|
||||
'Looking to join for the upcoming season.',
|
||||
'Heard great things about this league. Can I join?',
|
||||
'Experienced driver looking for competitive racing.',
|
||||
'My friend recommended this league. Hope to race with you!',
|
||||
];
|
||||
seededJoinRequests.push({
|
||||
id: `join-${league.id}-${driver.id}`,
|
||||
leagueId: league.id,
|
||||
driverId: driver.id,
|
||||
requestedAt: new Date(Date.now() - (index + 1) * 24 * 60 * 60 * 1000),
|
||||
message:
|
||||
index % 2 === 0
|
||||
? 'Would love to race in this series!'
|
||||
: 'Looking to join for the upcoming season.',
|
||||
requestedAt: new Date(Date.now() - (index + 1 + leagueIndex) * 24 * 60 * 60 * 1000),
|
||||
message: messages[(index + leagueIndex) % messages.length],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -467,6 +609,7 @@ class DIContainer {
|
||||
this._standingRepository,
|
||||
this._resultRepository,
|
||||
this._penaltyRepository,
|
||||
this._raceRepository,
|
||||
{
|
||||
getRating: (driverId: string) => {
|
||||
const stats = driverStats[driverId];
|
||||
@@ -595,6 +738,32 @@ class DIContainer {
|
||||
this._teamMembershipRepository,
|
||||
);
|
||||
|
||||
// Stewarding use cases and queries
|
||||
this._fileProtestUseCase = new FileProtestUseCase(
|
||||
this._protestRepository,
|
||||
this._raceRepository,
|
||||
this._leagueMembershipRepository,
|
||||
);
|
||||
this._reviewProtestUseCase = new ReviewProtestUseCase(
|
||||
this._protestRepository,
|
||||
this._raceRepository,
|
||||
this._leagueMembershipRepository,
|
||||
);
|
||||
this._applyPenaltyUseCase = new ApplyPenaltyUseCase(
|
||||
this._penaltyRepository,
|
||||
this._protestRepository,
|
||||
this._raceRepository,
|
||||
this._leagueMembershipRepository,
|
||||
);
|
||||
this._getRaceProtestsQuery = new GetRaceProtestsQuery(
|
||||
this._protestRepository,
|
||||
this._driverRepository,
|
||||
);
|
||||
this._getRacePenaltiesQuery = new GetRacePenaltiesQuery(
|
||||
this._penaltyRepository,
|
||||
this._driverRepository,
|
||||
);
|
||||
|
||||
// Social and feed adapters backed by static seed
|
||||
this._feedRepository = new InMemoryFeedRepository(seedData);
|
||||
this._socialRepository = new InMemorySocialGraphRepository(seedData);
|
||||
@@ -825,6 +994,10 @@ class DIContainer {
|
||||
return this._penaltyRepository;
|
||||
}
|
||||
|
||||
get protestRepository(): IProtestRepository {
|
||||
return this._protestRepository;
|
||||
}
|
||||
|
||||
get raceRegistrationRepository(): IRaceRegistrationRepository {
|
||||
return this._raceRegistrationRepository;
|
||||
}
|
||||
@@ -989,6 +1162,26 @@ class DIContainer {
|
||||
get carRepository(): ICarRepository {
|
||||
return this._carRepository;
|
||||
}
|
||||
|
||||
get fileProtestUseCase(): FileProtestUseCase {
|
||||
return this._fileProtestUseCase;
|
||||
}
|
||||
|
||||
get reviewProtestUseCase(): ReviewProtestUseCase {
|
||||
return this._reviewProtestUseCase;
|
||||
}
|
||||
|
||||
get applyPenaltyUseCase(): ApplyPenaltyUseCase {
|
||||
return this._applyPenaltyUseCase;
|
||||
}
|
||||
|
||||
get getRaceProtestsQuery(): GetRaceProtestsQuery {
|
||||
return this._getRaceProtestsQuery;
|
||||
}
|
||||
|
||||
get getRacePenaltiesQuery(): GetRacePenaltiesQuery {
|
||||
return this._getRacePenaltiesQuery;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1018,6 +1211,10 @@ export function getPenaltyRepository(): IPenaltyRepository {
|
||||
return DIContainer.getInstance().penaltyRepository;
|
||||
}
|
||||
|
||||
export function getProtestRepository(): IProtestRepository {
|
||||
return DIContainer.getInstance().protestRepository;
|
||||
}
|
||||
|
||||
export function getRaceRegistrationRepository(): IRaceRegistrationRepository {
|
||||
return DIContainer.getInstance().raceRegistrationRepository;
|
||||
}
|
||||
@@ -1167,6 +1364,26 @@ export function getCarRepository(): ICarRepository {
|
||||
return DIContainer.getInstance().carRepository;
|
||||
}
|
||||
|
||||
export function getFileProtestUseCase(): FileProtestUseCase {
|
||||
return DIContainer.getInstance().fileProtestUseCase;
|
||||
}
|
||||
|
||||
export function getReviewProtestUseCase(): ReviewProtestUseCase {
|
||||
return DIContainer.getInstance().reviewProtestUseCase;
|
||||
}
|
||||
|
||||
export function getApplyPenaltyUseCase(): ApplyPenaltyUseCase {
|
||||
return DIContainer.getInstance().applyPenaltyUseCase;
|
||||
}
|
||||
|
||||
export function getGetRaceProtestsQuery(): GetRaceProtestsQuery {
|
||||
return DIContainer.getInstance().getRaceProtestsQuery;
|
||||
}
|
||||
|
||||
export function getGetRacePenaltiesQuery(): GetRacePenaltiesQuery {
|
||||
return DIContainer.getInstance().getRacePenaltiesQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset function for testing
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user