302 lines
11 KiB
TypeScript
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);
|
|
}
|
|
} |