Files
gridpilot.gg/adapters/bootstrap/racing/RacingTeamFactory.ts
2026-01-20 17:49:54 +01:00

302 lines
11 KiB
TypeScript

import { MediaReference } from '@core/domain/media/MediaReference';
import { Driver } from '@core/racing/domain/entities/Driver';
import { League } from '@core/racing/domain/entities/League';
import { Team } from '@core/racing/domain/entities/Team';
import type { TeamJoinRequest, TeamMembership } from '@core/racing/domain/types/TeamMembership';
import { faker } from '@faker-js/faker';
import { seedId } from './SeedIdHelper';
export interface TeamStats {
performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro';
specialization: 'endurance' | 'sprint' | 'mixed';
region: string;
languages: string[];
totalWins: number;
totalRaces: number;
rating: number;
category?: string;
}
export class RacingTeamFactory {
constructor(
private readonly baseDate: Date,
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
) {}
createTeams(drivers: Driver[], leagues: League[]): Team[] {
const teamCount = 50; // Increased from 15 to 50
return Array.from({ length: teamCount }, (_, idx) => {
const i = idx + 1;
const owner = faker.helpers.arrayElement(drivers);
const teamLeagues = faker.helpers.arrayElements(
leagues.map((l) => l.id.toString()),
{ min: 0, max: 3 },
);
// 30-50% of teams are recruiting
const isRecruiting = faker.datatype.boolean({ probability: 0.4 });
const teamId = seedId(`team-${i}`, this.persistence);
const racingNames = [
'Apex Performance', 'Velocity Racing', 'Zenith Motorsport', 'Quantum Racing',
'Ignition Racing', 'Precision Dynamics', 'Overdrive Motorsport', 'Apex Predators',
'Gridline Racing', 'Shift Point Motorsport', 'Redline Performance', 'Apex Legends',
'Circuit Breakers', 'Full Throttle Racing', 'Gearhead Motorsport', 'Piston Cup Racing',
'Turbo Titans', 'Nitro Knights', 'Velocity Vanguards', 'Mach One Racing',
'Apex Alliance', 'Elite Endurance', 'Sprint Specialists', 'Grand Prix Group',
'Podium Pursuit', 'Victory Vibe', 'Championship Chase', 'Racing Renegades',
'Track Titans', 'Asphalt Assassins', 'Speed Syndicate', 'Fast Lane Force',
'Apex Architects', 'Velocity Visionaries', 'Zenith Zephyrs', 'Quantum Quicksilver',
'Ignition Iron', 'Precision Pilots', 'Overdrive Outlaws', 'Apex Aces',
'Gridline Guardians', 'Shift Point Sentinels', 'Redline Rebels', 'Apex Avengers',
'Circuit Crusaders', 'Full Throttle Falcons', 'Gearhead Giants', 'Piston Cup Pros',
'Turbo Tigers', 'Nitro Ninjas'
];
const name = racingNames[(i - 1) % racingNames.length]!;
return Team.create({
id: teamId,
name: name,
tag: name.split(' ').map(w => w[0]).join('').toUpperCase().substring(0, 4),
description: faker.lorem.sentences(2),
ownerId: owner.id,
leagues: teamLeagues,
isRecruiting,
createdAt: faker.date.past({ years: 2, refDate: this.baseDate }),
logoRef: MediaReference.createGenerated(`team-${teamId}`),
});
});
}
createTeamMemberships(drivers: Driver[], teams: Team[]): TeamMembership[] {
const memberships: TeamMembership[] = [];
const usedDrivers = new Set<string>();
teams.forEach((team, teamIndex) => {
const availableDrivers = drivers.filter(d => !usedDrivers.has(d.id.toString()) && d.id.toString() !== team.ownerId.toString());
// Create varied team compositions
let memberCount: number;
let hasManager: boolean;
if (teamIndex % 5 === 0) {
// Solo teams (just owner)
memberCount = 0;
hasManager = false;
} else if (teamIndex % 5 === 1) {
// Small teams (2-3 members)
memberCount = faker.number.int({ min: 1, max: 2 });
hasManager = faker.datatype.boolean();
} else if (teamIndex % 5 === 2) {
// Medium teams (3-5 members)
memberCount = faker.number.int({ min: 2, max: 4 });
hasManager = true;
} else if (teamIndex % 5 === 3) {
// Large teams (5-7 members)
memberCount = faker.number.int({ min: 4, max: 6 });
hasManager = true;
} else {
// Mixed - sometimes with manager, sometimes without
memberCount = faker.number.int({ min: 1, max: 5 });
hasManager = faker.datatype.boolean();
}
const members = faker.helpers.arrayElements(availableDrivers, memberCount);
// Add owner
memberships.push({
teamId: team.id.toString(),
driverId: team.ownerId.toString(),
role: 'owner',
status: 'active',
joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
});
usedDrivers.add(team.ownerId.toString());
// Add manager if needed
if (hasManager && members.length > 0) {
const managerIndex = faker.number.int({ min: 0, max: members.length - 1 });
const manager = members[managerIndex]!;
memberships.push({
teamId: team.id.toString(),
driverId: manager.id.toString(),
role: 'manager',
status: 'active',
joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
});
usedDrivers.add(manager.id.toString());
}
// Add remaining members as drivers
members.forEach((driver) => {
if (!usedDrivers.has(driver.id.toString())) {
memberships.push({
teamId: team.id.toString(),
driverId: driver.id.toString(),
role: 'driver',
status: 'active',
joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
});
usedDrivers.add(driver.id.toString());
}
});
});
return memberships;
}
createTeamJoinRequests(
drivers: Driver[],
teams: Team[],
teamMemberships: TeamMembership[],
): TeamJoinRequest[] {
const membershipIds = new Set<string>(teamMemberships.map(m => `${m.teamId}:${m.driverId}`));
const requests: TeamJoinRequest[] = [];
const addRequest = (request: TeamJoinRequest): void => {
requests.push(request);
};
const team1 = teams[0];
if (team1) {
const candidateDriverIds = drivers
.map(d => d.id.toString())
.filter(driverId => !membershipIds.has(`${team1.id.toString()}:${driverId}`))
.slice(0, 8);
candidateDriverIds.forEach((driverId, idx) => {
addRequest({
id: seedId(`team-join-${team1.id.toString()}-${driverId}`, this.persistence),
teamId: team1.id.toString(),
driverId,
requestedAt: this.addDays(this.baseDate, -(5 + idx)),
...(idx % 3 === 0
? { message: 'Can I join as a substitute driver for endurance events?' }
: idx % 3 === 2
? { message: '' }
: {}),
});
});
// Conflict edge case: owner submits a join request to own team.
addRequest({
id: seedId(`team-join-${team1.id.toString()}-${team1.ownerId.toString()}-conflict`, this.persistence),
teamId: team1.id.toString(),
driverId: team1.ownerId.toString(),
requestedAt: this.addDays(this.baseDate, -1),
message: 'Testing edge case: owner submitted join request.',
});
}
const team3 = teams[2];
if (team3 && drivers[0]) {
const driverId = drivers[0].id.toString();
addRequest({
id: seedId('dup-team-join-req-1', this.persistence),
teamId: team3.id.toString(),
driverId,
requestedAt: this.addDays(this.baseDate, -10),
message: 'First request message (will be overwritten).',
});
addRequest({
id: seedId('dup-team-join-req-1', this.persistence),
teamId: team3.id.toString(),
driverId,
requestedAt: this.addDays(this.baseDate, -9),
message: 'Updated request message (duplicate id).',
});
}
return requests;
}
/**
* Generate team statistics and metadata for display
* This would be stored in a separate stats table/service in production
*/
generateTeamStats(teams: Team[]): Map<string, TeamStats> {
const statsMap = new Map<string, TeamStats>();
// Available regions (using country codes for flags)
const regions = ['DE', 'GB', 'US', 'FR', 'IT', 'ES', 'BR', 'JP', 'AU', 'NL', 'BE', 'AT', 'CH', 'SE', 'NO', 'FI', 'DK', 'PL', 'CZ', 'HU'];
// Available languages
const allLanguages = ['English', 'German', 'French', 'Spanish', 'Italian', 'Portuguese', 'Japanese', 'Korean', 'Russian', 'Chinese'];
teams.forEach((team, idx) => {
const i = idx + 1;
// Determine performance level based on index
let performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro';
let totalRaces: number;
let rating: number;
let totalWins: number;
if (i % 8 === 0) {
// Pro teams
performanceLevel = 'pro';
totalRaces = faker.number.int({ min: 80, max: 150 });
rating = faker.number.int({ min: 1700, max: 2000 });
totalWins = faker.number.int({ min: 15, max: 40 });
} else if (i % 5 === 0) {
// Advanced teams
performanceLevel = 'advanced';
totalRaces = faker.number.int({ min: 40, max: 100 });
rating = faker.number.int({ min: 1500, max: 1800 });
totalWins = faker.number.int({ min: 8, max: 20 });
} else if (i % 3 === 0) {
// Intermediate teams
performanceLevel = 'intermediate';
totalRaces = faker.number.int({ min: 20, max: 60 });
rating = faker.number.int({ min: 1200, max: 1600 });
totalWins = faker.number.int({ min: 3, max: 12 });
} else {
// Beginner teams
performanceLevel = 'beginner';
totalRaces = faker.number.int({ min: 5, max: 25 });
rating = faker.number.int({ min: 900, max: 1300 });
totalWins = faker.number.int({ min: 0, max: 5 });
}
// Determine specialization
let specialization: 'endurance' | 'sprint' | 'mixed';
if (i % 7 === 0) {
specialization = 'endurance';
} else if (i % 4 === 0) {
specialization = 'sprint';
} else {
specialization = 'mixed';
}
// Determine category - use all available categories
const categories = ['beginner', 'intermediate', 'advanced', 'pro', 'endurance', 'sprint'];
const category = faker.helpers.arrayElement(categories);
// Generate region and languages
const region = faker.helpers.arrayElement(regions);
const languageCount = faker.number.int({ min: 1, max: 3 });
const languages = faker.helpers.arrayElements(allLanguages, languageCount);
statsMap.set(team.id.toString(), {
performanceLevel,
specialization,
region,
languages,
totalWins,
totalRaces,
rating,
category,
});
});
return statsMap;
}
private addDays(date: Date, days: number): Date {
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
}
}