wip
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Infrastructure Adapter: InMemoryLeagueMembershipRepository
|
||||
*
|
||||
* In-memory implementation of ILeagueMembershipRepository.
|
||||
* Stores memberships and join requests in maps keyed by league.
|
||||
*/
|
||||
|
||||
import type {
|
||||
LeagueMembership,
|
||||
JoinRequest,
|
||||
} from '@gridpilot/racing/domain/entities/LeagueMembership';
|
||||
import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository';
|
||||
|
||||
export class InMemoryLeagueMembershipRepository implements ILeagueMembershipRepository {
|
||||
private membershipsByLeague: Map<string, LeagueMembership[]>;
|
||||
private joinRequestsByLeague: Map<string, JoinRequest[]>;
|
||||
|
||||
constructor(seedMemberships?: LeagueMembership[], seedJoinRequests?: JoinRequest[]) {
|
||||
this.membershipsByLeague = new Map();
|
||||
this.joinRequestsByLeague = new Map();
|
||||
|
||||
if (seedMemberships) {
|
||||
seedMemberships.forEach((membership) => {
|
||||
const list = this.membershipsByLeague.get(membership.leagueId) ?? [];
|
||||
list.push(membership);
|
||||
this.membershipsByLeague.set(membership.leagueId, list);
|
||||
});
|
||||
}
|
||||
|
||||
if (seedJoinRequests) {
|
||||
seedJoinRequests.forEach((request) => {
|
||||
const list = this.joinRequestsByLeague.get(request.leagueId) ?? [];
|
||||
list.push(request);
|
||||
this.joinRequestsByLeague.set(request.leagueId, list);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getMembership(leagueId: string, driverId: string): Promise<LeagueMembership | null> {
|
||||
const list = this.membershipsByLeague.get(leagueId);
|
||||
if (!list) return null;
|
||||
return list.find((m) => m.driverId === driverId) ?? null;
|
||||
}
|
||||
|
||||
async getLeagueMembers(leagueId: string): Promise<LeagueMembership[]> {
|
||||
return [...(this.membershipsByLeague.get(leagueId) ?? [])];
|
||||
}
|
||||
|
||||
async getJoinRequests(leagueId: string): Promise<JoinRequest[]> {
|
||||
return [...(this.joinRequestsByLeague.get(leagueId) ?? [])];
|
||||
}
|
||||
|
||||
async saveMembership(membership: LeagueMembership): Promise<LeagueMembership> {
|
||||
const list = this.membershipsByLeague.get(membership.leagueId) ?? [];
|
||||
const existingIndex = list.findIndex(
|
||||
(m) => m.leagueId === membership.leagueId && m.driverId === membership.driverId,
|
||||
);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
list[existingIndex] = membership;
|
||||
} else {
|
||||
list.push(membership);
|
||||
}
|
||||
|
||||
this.membershipsByLeague.set(membership.leagueId, list);
|
||||
return membership;
|
||||
}
|
||||
|
||||
async removeMembership(leagueId: string, driverId: string): Promise<void> {
|
||||
const list = this.membershipsByLeague.get(leagueId);
|
||||
if (!list) return;
|
||||
|
||||
const next = list.filter((m) => m.driverId !== driverId);
|
||||
this.membershipsByLeague.set(leagueId, next);
|
||||
}
|
||||
|
||||
async saveJoinRequest(request: JoinRequest): Promise<JoinRequest> {
|
||||
const list = this.joinRequestsByLeague.get(request.leagueId) ?? [];
|
||||
const existingIndex = list.findIndex((r) => r.id === request.id);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
list[existingIndex] = request;
|
||||
} else {
|
||||
list.push(request);
|
||||
}
|
||||
|
||||
this.joinRequestsByLeague.set(request.leagueId, list);
|
||||
return request;
|
||||
}
|
||||
|
||||
async removeJoinRequest(requestId: string): Promise<void> {
|
||||
for (const [leagueId, requests] of this.joinRequestsByLeague.entries()) {
|
||||
const next = requests.filter((r) => r.id !== requestId);
|
||||
if (next.length !== requests.length) {
|
||||
this.joinRequestsByLeague.set(leagueId, next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Infrastructure Adapter: InMemoryPenaltyRepository
|
||||
*
|
||||
* Simple in-memory implementation of IPenaltyRepository seeded with
|
||||
* a handful of demo penalties and bonuses for leagues/drivers.
|
||||
*/
|
||||
import type { Penalty } from '@gridpilot/racing/domain/entities/Penalty';
|
||||
import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/IPenaltyRepository';
|
||||
|
||||
export class InMemoryPenaltyRepository implements IPenaltyRepository {
|
||||
private readonly penalties: Penalty[];
|
||||
|
||||
constructor(seedPenalties?: Penalty[]) {
|
||||
this.penalties = seedPenalties ? [...seedPenalties] : InMemoryPenaltyRepository.createDefaultSeed();
|
||||
}
|
||||
|
||||
async findByLeagueId(leagueId: string): Promise<Penalty[]> {
|
||||
return this.penalties.filter((p) => p.leagueId === leagueId);
|
||||
}
|
||||
|
||||
async findByLeagueIdAndDriverId(leagueId: string, driverId: string): Promise<Penalty[]> {
|
||||
return this.penalties.filter((p) => p.leagueId === leagueId && p.driverId === driverId);
|
||||
}
|
||||
|
||||
async findAll(): Promise<Penalty[]> {
|
||||
return [...this.penalties];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Infrastructure Adapter: InMemoryRaceRegistrationRepository
|
||||
*
|
||||
* In-memory implementation of IRaceRegistrationRepository.
|
||||
* Stores race registrations in Maps keyed by raceId and driverId.
|
||||
*/
|
||||
|
||||
import type { RaceRegistration } from '@gridpilot/racing/domain/entities/RaceRegistration';
|
||||
import type { IRaceRegistrationRepository } from '@gridpilot/racing/domain/repositories/IRaceRegistrationRepository';
|
||||
|
||||
export class InMemoryRaceRegistrationRepository implements IRaceRegistrationRepository {
|
||||
private registrationsByRace: Map<string, Set<string>>;
|
||||
private registrationsByDriver: Map<string, Set<string>>;
|
||||
|
||||
constructor(seedRegistrations?: RaceRegistration[]) {
|
||||
this.registrationsByRace = new Map();
|
||||
this.registrationsByDriver = new Map();
|
||||
|
||||
if (seedRegistrations) {
|
||||
seedRegistrations.forEach((registration) => {
|
||||
this.addToIndexes(registration.raceId, registration.driverId, registration.registeredAt);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private addToIndexes(raceId: string, driverId: string, _registeredAt: Date): void {
|
||||
let raceSet = this.registrationsByRace.get(raceId);
|
||||
if (!raceSet) {
|
||||
raceSet = new Set();
|
||||
this.registrationsByRace.set(raceId, raceSet);
|
||||
}
|
||||
raceSet.add(driverId);
|
||||
|
||||
let driverSet = this.registrationsByDriver.get(driverId);
|
||||
if (!driverSet) {
|
||||
driverSet = new Set();
|
||||
this.registrationsByDriver.set(driverId, driverSet);
|
||||
}
|
||||
driverSet.add(raceId);
|
||||
}
|
||||
|
||||
private removeFromIndexes(raceId: string, driverId: string): void {
|
||||
const raceSet = this.registrationsByRace.get(raceId);
|
||||
if (raceSet) {
|
||||
raceSet.delete(driverId);
|
||||
if (raceSet.size === 0) {
|
||||
this.registrationsByRace.delete(raceId);
|
||||
}
|
||||
}
|
||||
|
||||
const driverSet = this.registrationsByDriver.get(driverId);
|
||||
if (driverSet) {
|
||||
driverSet.delete(raceId);
|
||||
if (driverSet.size === 0) {
|
||||
this.registrationsByDriver.delete(driverId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async isRegistered(raceId: string, driverId: string): Promise<boolean> {
|
||||
const raceSet = this.registrationsByRace.get(raceId);
|
||||
if (!raceSet) return false;
|
||||
return raceSet.has(driverId);
|
||||
}
|
||||
|
||||
async getRegisteredDrivers(raceId: string): Promise<string[]> {
|
||||
const raceSet = this.registrationsByRace.get(raceId);
|
||||
if (!raceSet) return [];
|
||||
return Array.from(raceSet.values());
|
||||
}
|
||||
|
||||
async getRegistrationCount(raceId: string): Promise<number> {
|
||||
const raceSet = this.registrationsByRace.get(raceId);
|
||||
return raceSet ? raceSet.size : 0;
|
||||
}
|
||||
|
||||
async register(registration: RaceRegistration): Promise<void> {
|
||||
const alreadyRegistered = await this.isRegistered(registration.raceId, registration.driverId);
|
||||
if (alreadyRegistered) {
|
||||
throw new Error('Already registered for this race');
|
||||
}
|
||||
this.addToIndexes(registration.raceId, registration.driverId, registration.registeredAt);
|
||||
}
|
||||
|
||||
async withdraw(raceId: string, driverId: string): Promise<void> {
|
||||
const alreadyRegistered = await this.isRegistered(raceId, driverId);
|
||||
if (!alreadyRegistered) {
|
||||
throw new Error('Not registered for this race');
|
||||
}
|
||||
this.removeFromIndexes(raceId, driverId);
|
||||
}
|
||||
|
||||
async getDriverRegistrations(driverId: string): Promise<string[]> {
|
||||
const driverSet = this.registrationsByDriver.get(driverId);
|
||||
if (!driverSet) return [];
|
||||
return Array.from(driverSet.values());
|
||||
}
|
||||
|
||||
async clearRaceRegistrations(raceId: string): Promise<void> {
|
||||
const raceSet = this.registrationsByRace.get(raceId);
|
||||
if (!raceSet) return;
|
||||
|
||||
for (const driverId of raceSet.values()) {
|
||||
const driverSet = this.registrationsByDriver.get(driverId);
|
||||
if (driverSet) {
|
||||
driverSet.delete(raceId);
|
||||
if (driverSet.size === 0) {
|
||||
this.registrationsByDriver.delete(driverId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.registrationsByRace.delete(raceId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
import { Game } from '@gridpilot/racing/domain/entities/Game';
|
||||
import { Season } from '@gridpilot/racing/domain/entities/Season';
|
||||
import type { LeagueScoringConfig } from '@gridpilot/racing/domain/entities/LeagueScoringConfig';
|
||||
import { PointsTable } from '@gridpilot/racing/domain/value-objects/PointsTable';
|
||||
import type { ChampionshipConfig } from '@gridpilot/racing/domain/value-objects/ChampionshipConfig';
|
||||
import type { SessionType } from '@gridpilot/racing/domain/value-objects/SessionType';
|
||||
import type { BonusRule } from '@gridpilot/racing/domain/value-objects/BonusRule';
|
||||
import type { DropScorePolicy } from '@gridpilot/racing/domain/value-objects/DropScorePolicy';
|
||||
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';
|
||||
import type { IChampionshipStandingRepository } from '@gridpilot/racing/domain/repositories/IChampionshipStandingRepository';
|
||||
import { ChampionshipStanding } from '@gridpilot/racing/domain/entities/ChampionshipStanding';
|
||||
import type { ChampionshipType } from '@gridpilot/racing/domain/value-objects/ChampionshipType';
|
||||
import type { ParticipantRef } from '@gridpilot/racing/domain/value-objects/ParticipantRef';
|
||||
|
||||
export class InMemoryGameRepository implements IGameRepository {
|
||||
private games: Game[];
|
||||
|
||||
constructor(seedData?: Game[]) {
|
||||
this.games = seedData ? [...seedData] : [];
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Game | null> {
|
||||
return this.games.find((g) => g.id === id) ?? null;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Game[]> {
|
||||
return [...this.games];
|
||||
}
|
||||
|
||||
seed(game: Game): void {
|
||||
this.games.push(game);
|
||||
}
|
||||
}
|
||||
|
||||
export class InMemorySeasonRepository implements ISeasonRepository {
|
||||
private seasons: Season[];
|
||||
|
||||
constructor(seedData?: Season[]) {
|
||||
this.seasons = seedData ? [...seedData] : [];
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Season | null> {
|
||||
return this.seasons.find((s) => s.id === id) ?? null;
|
||||
}
|
||||
|
||||
async findByLeagueId(leagueId: string): Promise<Season[]> {
|
||||
return this.seasons.filter((s) => s.leagueId === leagueId);
|
||||
}
|
||||
|
||||
seed(season: Season): void {
|
||||
this.seasons.push(season);
|
||||
}
|
||||
}
|
||||
|
||||
export class InMemoryLeagueScoringConfigRepository
|
||||
implements ILeagueScoringConfigRepository
|
||||
{
|
||||
private configs: LeagueScoringConfig[];
|
||||
|
||||
constructor(seedData?: LeagueScoringConfig[]) {
|
||||
this.configs = seedData ? [...seedData] : [];
|
||||
}
|
||||
|
||||
async findBySeasonId(seasonId: string): Promise<LeagueScoringConfig | null> {
|
||||
return this.configs.find((c) => c.seasonId === seasonId) ?? null;
|
||||
}
|
||||
|
||||
seed(config: LeagueScoringConfig): void {
|
||||
this.configs.push(config);
|
||||
}
|
||||
}
|
||||
|
||||
export class InMemoryChampionshipStandingRepository
|
||||
implements IChampionshipStandingRepository
|
||||
{
|
||||
private standings: ChampionshipStanding[] = [];
|
||||
|
||||
async findBySeasonAndChampionship(
|
||||
seasonId: string,
|
||||
championshipId: string,
|
||||
): Promise<ChampionshipStanding[]> {
|
||||
return this.standings.filter(
|
||||
(s) => s.seasonId === seasonId && s.championshipId === championshipId,
|
||||
);
|
||||
}
|
||||
|
||||
async saveAll(standings: ChampionshipStanding[]): Promise<void> {
|
||||
this.standings = standings;
|
||||
}
|
||||
|
||||
seed(standing: ChampionshipStanding): void {
|
||||
this.standings.push(standing);
|
||||
}
|
||||
|
||||
getAll(): ChampionshipStanding[] {
|
||||
return [...this.standings];
|
||||
}
|
||||
}
|
||||
|
||||
export function createF1DemoScoringSetup(params: {
|
||||
leagueId: string;
|
||||
seasonId?: string;
|
||||
}): {
|
||||
gameRepo: InMemoryGameRepository;
|
||||
seasonRepo: InMemorySeasonRepository;
|
||||
scoringConfigRepo: InMemoryLeagueScoringConfigRepository;
|
||||
championshipStandingRepo: InMemoryChampionshipStandingRepository;
|
||||
seasonId: string;
|
||||
championshipId: string;
|
||||
} {
|
||||
const { leagueId } = params;
|
||||
const seasonId = params.seasonId ?? 'season-f1-demo';
|
||||
const championshipId = 'driver-champ';
|
||||
|
||||
const game = Game.create({ id: 'iracing', name: 'iRacing' });
|
||||
|
||||
const season = Season.create({
|
||||
id: seasonId,
|
||||
leagueId,
|
||||
gameId: game.id,
|
||||
name: 'F1-Style Demo Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
|
||||
const mainPoints = new PointsTable({
|
||||
1: 25,
|
||||
2: 18,
|
||||
3: 15,
|
||||
4: 12,
|
||||
5: 10,
|
||||
6: 8,
|
||||
7: 6,
|
||||
8: 4,
|
||||
9: 2,
|
||||
10: 1,
|
||||
});
|
||||
|
||||
const sprintPoints = new PointsTable({
|
||||
1: 8,
|
||||
2: 7,
|
||||
3: 6,
|
||||
4: 5,
|
||||
5: 4,
|
||||
6: 3,
|
||||
7: 2,
|
||||
8: 1,
|
||||
});
|
||||
|
||||
const fastestLapBonus: BonusRule = {
|
||||
id: 'fastest-lap-main',
|
||||
type: 'fastestLap',
|
||||
points: 1,
|
||||
requiresFinishInTopN: 10,
|
||||
};
|
||||
|
||||
const sessionTypes: SessionType[] = ['sprint', 'main'];
|
||||
|
||||
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
|
||||
sprint: sprintPoints,
|
||||
main: mainPoints,
|
||||
practice: new PointsTable({}),
|
||||
qualifying: new PointsTable({}),
|
||||
q1: new PointsTable({}),
|
||||
q2: new PointsTable({}),
|
||||
q3: new PointsTable({}),
|
||||
timeTrial: new PointsTable({}),
|
||||
};
|
||||
|
||||
const bonusRulesBySessionType: Record<SessionType, BonusRule[]> = {
|
||||
sprint: [],
|
||||
main: [fastestLapBonus],
|
||||
practice: [],
|
||||
qualifying: [],
|
||||
q1: [],
|
||||
q2: [],
|
||||
q3: [],
|
||||
timeTrial: [],
|
||||
};
|
||||
|
||||
const dropScorePolicy: DropScorePolicy = {
|
||||
strategy: 'bestNResults',
|
||||
count: 6,
|
||||
};
|
||||
|
||||
const championship: ChampionshipConfig = {
|
||||
id: championshipId,
|
||||
name: 'Driver Championship',
|
||||
type: 'driver' as ChampionshipType,
|
||||
sessionTypes,
|
||||
pointsTableBySessionType,
|
||||
bonusRulesBySessionType,
|
||||
dropScorePolicy,
|
||||
};
|
||||
|
||||
const leagueScoringConfig: LeagueScoringConfig = {
|
||||
id: 'lsc-f1-demo',
|
||||
seasonId: season.id,
|
||||
championships: [championship],
|
||||
};
|
||||
|
||||
const gameRepo = new InMemoryGameRepository([game]);
|
||||
const seasonRepo = new InMemorySeasonRepository([season]);
|
||||
const scoringConfigRepo = new InMemoryLeagueScoringConfigRepository([
|
||||
leagueScoringConfig,
|
||||
]);
|
||||
const championshipStandingRepo = new InMemoryChampionshipStandingRepository();
|
||||
|
||||
return {
|
||||
gameRepo,
|
||||
seasonRepo,
|
||||
scoringConfigRepo,
|
||||
championshipStandingRepo,
|
||||
seasonId: season.id,
|
||||
championshipId,
|
||||
};
|
||||
}
|
||||
|
||||
export function createParticipantRef(driverId: string): ParticipantRef {
|
||||
return {
|
||||
type: 'driver' as ChampionshipType,
|
||||
id: driverId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Infrastructure Adapter: InMemoryTeamMembershipRepository
|
||||
*
|
||||
* In-memory implementation of ITeamMembershipRepository.
|
||||
* Stores memberships and join requests in Map structures.
|
||||
*/
|
||||
|
||||
import type {
|
||||
TeamMembership,
|
||||
TeamJoinRequest,
|
||||
} from '@gridpilot/racing/domain/entities/Team';
|
||||
import type { ITeamMembershipRepository } from '@gridpilot/racing/domain/repositories/ITeamMembershipRepository';
|
||||
|
||||
export class InMemoryTeamMembershipRepository implements ITeamMembershipRepository {
|
||||
private membershipsByTeam: Map<string, TeamMembership[]>;
|
||||
private joinRequestsByTeam: Map<string, TeamJoinRequest[]>;
|
||||
|
||||
constructor(seedMemberships?: TeamMembership[], seedJoinRequests?: TeamJoinRequest[]) {
|
||||
this.membershipsByTeam = new Map();
|
||||
this.joinRequestsByTeam = new Map();
|
||||
|
||||
if (seedMemberships) {
|
||||
seedMemberships.forEach((membership) => {
|
||||
const list = this.membershipsByTeam.get(membership.teamId) ?? [];
|
||||
list.push(membership);
|
||||
this.membershipsByTeam.set(membership.teamId, list);
|
||||
});
|
||||
}
|
||||
|
||||
if (seedJoinRequests) {
|
||||
seedJoinRequests.forEach((request) => {
|
||||
const list = this.joinRequestsByTeam.get(request.teamId) ?? [];
|
||||
list.push(request);
|
||||
this.joinRequestsByTeam.set(request.teamId, list);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getMembershipList(teamId: string): TeamMembership[] {
|
||||
let list = this.membershipsByTeam.get(teamId);
|
||||
if (!list) {
|
||||
list = [];
|
||||
this.membershipsByTeam.set(teamId, list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private getJoinRequestList(teamId: string): TeamJoinRequest[] {
|
||||
let list = this.joinRequestsByTeam.get(teamId);
|
||||
if (!list) {
|
||||
list = [];
|
||||
this.joinRequestsByTeam.set(teamId, list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
async getMembership(teamId: string, driverId: string): Promise<TeamMembership | null> {
|
||||
const list = this.membershipsByTeam.get(teamId);
|
||||
if (!list) return null;
|
||||
return list.find((m) => m.driverId === driverId) ?? null;
|
||||
}
|
||||
|
||||
async getActiveMembershipForDriver(driverId: string): Promise<TeamMembership | null> {
|
||||
for (const list of this.membershipsByTeam.values()) {
|
||||
const membership = list.find(
|
||||
(m) => m.driverId === driverId && m.status === 'active',
|
||||
);
|
||||
if (membership) {
|
||||
return membership;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getTeamMembers(teamId: string): Promise<TeamMembership[]> {
|
||||
return [...(this.membershipsByTeam.get(teamId) ?? [])];
|
||||
}
|
||||
|
||||
async saveMembership(membership: TeamMembership): Promise<TeamMembership> {
|
||||
const list = this.getMembershipList(membership.teamId);
|
||||
const existingIndex = list.findIndex(
|
||||
(m) => m.teamId === membership.teamId && m.driverId === membership.driverId,
|
||||
);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
list[existingIndex] = membership;
|
||||
} else {
|
||||
list.push(membership);
|
||||
}
|
||||
|
||||
this.membershipsByTeam.set(membership.teamId, list);
|
||||
return membership;
|
||||
}
|
||||
|
||||
async removeMembership(teamId: string, driverId: string): Promise<void> {
|
||||
const list = this.membershipsByTeam.get(teamId);
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
const index = list.findIndex((m) => m.driverId === driverId);
|
||||
if (index >= 0) {
|
||||
list.splice(index, 1);
|
||||
this.membershipsByTeam.set(teamId, list);
|
||||
}
|
||||
}
|
||||
|
||||
async getJoinRequests(teamId: string): Promise<TeamJoinRequest[]> {
|
||||
return [...(this.joinRequestsByTeam.get(teamId) ?? [])];
|
||||
}
|
||||
|
||||
async saveJoinRequest(request: TeamJoinRequest): Promise<TeamJoinRequest> {
|
||||
const list = this.getJoinRequestList(request.teamId);
|
||||
const existingIndex = list.findIndex((r) => r.id === request.id);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
list[existingIndex] = request;
|
||||
} else {
|
||||
list.push(request);
|
||||
}
|
||||
|
||||
this.joinRequestsByTeam.set(request.teamId, list);
|
||||
return request;
|
||||
}
|
||||
|
||||
async removeJoinRequest(requestId: string): Promise<void> {
|
||||
for (const [teamId, list] of this.joinRequestsByTeam.entries()) {
|
||||
const index = list.findIndex((r) => r.id === requestId);
|
||||
if (index >= 0) {
|
||||
list.splice(index, 1);
|
||||
this.joinRequestsByTeam.set(teamId, list);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Infrastructure Adapter: InMemoryTeamRepository
|
||||
*
|
||||
* In-memory implementation of ITeamRepository.
|
||||
* Stores data in a Map structure.
|
||||
*/
|
||||
|
||||
import type { Team } from '@gridpilot/racing/domain/entities/Team';
|
||||
import type { ITeamRepository } from '@gridpilot/racing/domain/repositories/ITeamRepository';
|
||||
|
||||
export class InMemoryTeamRepository implements ITeamRepository {
|
||||
private teams: Map<string, Team>;
|
||||
|
||||
constructor(seedData?: Team[]) {
|
||||
this.teams = new Map();
|
||||
|
||||
if (seedData) {
|
||||
seedData.forEach((team) => {
|
||||
this.teams.set(team.id, team);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Team | null> {
|
||||
return this.teams.get(id) ?? null;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Team[]> {
|
||||
return Array.from(this.teams.values());
|
||||
}
|
||||
|
||||
async findByLeagueId(leagueId: string): Promise<Team[]> {
|
||||
return Array.from(this.teams.values()).filter((team) =>
|
||||
team.leagues.includes(leagueId),
|
||||
);
|
||||
}
|
||||
|
||||
async create(team: Team): Promise<Team> {
|
||||
if (await this.exists(team.id)) {
|
||||
throw new Error(`Team with ID ${team.id} already exists`);
|
||||
}
|
||||
|
||||
this.teams.set(team.id, team);
|
||||
return team;
|
||||
}
|
||||
|
||||
async update(team: Team): Promise<Team> {
|
||||
if (!(await this.exists(team.id))) {
|
||||
throw new Error(`Team with ID ${team.id} not found`);
|
||||
}
|
||||
|
||||
this.teams.set(team.id, team);
|
||||
return team;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
if (!(await this.exists(id))) {
|
||||
throw new Error(`Team with ID ${id} not found`);
|
||||
}
|
||||
|
||||
this.teams.delete(id);
|
||||
}
|
||||
|
||||
async exists(id: string): Promise<boolean> {
|
||||
return this.teams.has(id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user