import { MediaReference } from '@core/domain/media/MediaReference'; import { Driver } from '@core/racing/domain/entities/Driver'; import { League, LeagueSettings } from '@core/racing/domain/entities/League'; import { faker } from '@faker-js/faker'; import { seedId } from './SeedIdHelper'; export class RacingLeagueFactory { constructor( private readonly baseDate: Date, private readonly drivers: Driver[], private readonly persistence: 'postgres' | 'inmemory' = 'inmemory', ) {} create(): League[] { const leagueCount = 120; // Expanded to 100+ leagues // Create diverse league configurations covering ALL enum combinations // Points systems: f1-2024, indycar, custom (3) // Qualifying formats: single-lap, open (2) // Visibility: ranked, unranked (2) // Decision modes: admin_only, steward_decides, steward_vote, member_vote, steward_veto, member_veto (6) // Total combinations: 3 * 2 * 2 * 6 = 72, but we'll sample 30 covering extremes // Category types for leagues const leagueConfigs = [ // 1-5: Ranked, F1-2024, various stewarding { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'single-lap' as const, visibility: 'ranked' as const, maxDrivers: 40, sessionDuration: 60, stewarding: { decisionMode: 'admin_only' as const, requireDefense: false } }, { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'open' as const, visibility: 'ranked' as const, maxDrivers: 32, sessionDuration: 45, stewarding: { decisionMode: 'steward_vote' as const, requiredVotes: 3, requireDefense: true, defenseTimeLimit: 24 } }, { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'single-lap' as const, visibility: 'ranked' as const, maxDrivers: 50, sessionDuration: 90, stewarding: { decisionMode: 'member_vote' as const, requiredVotes: 5, requireDefense: false } }, { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'open' as const, visibility: 'ranked' as const, maxDrivers: 24, sessionDuration: 30, stewarding: { decisionMode: 'steward_veto' as const, requiredVotes: 2, requireDefense: true } }, { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'single-lap' as const, visibility: 'ranked' as const, maxDrivers: 60, sessionDuration: 120, stewarding: { decisionMode: 'member_veto' as const, requiredVotes: 4, requireDefense: false } }, // 6-10: Ranked, IndyCar, various stewarding { pointsSystem: 'indycar' as const, qualifyingFormat: 'open' as const, visibility: 'ranked' as const, maxDrivers: 28, sessionDuration: 75, stewarding: { decisionMode: 'steward_decides' as const, requireDefense: true, defenseTimeLimit: 48 } }, { pointsSystem: 'indycar' as const, qualifyingFormat: 'single-lap' as const, visibility: 'ranked' as const, maxDrivers: 36, sessionDuration: 60, stewarding: { decisionMode: 'admin_only' as const, requireDefense: false } }, { pointsSystem: 'indycar' as const, qualifyingFormat: 'open' as const, visibility: 'ranked' as const, maxDrivers: 44, sessionDuration: 100, stewarding: { decisionMode: 'steward_vote' as const, requiredVotes: 4, requireDefense: true, voteTimeLimit: 72 } }, { pointsSystem: 'indycar' as const, qualifyingFormat: 'single-lap' as const, visibility: 'ranked' as const, maxDrivers: 20, sessionDuration: 45, stewarding: { decisionMode: 'member_vote' as const, requiredVotes: 6, requireDefense: false } }, { pointsSystem: 'indycar' as const, qualifyingFormat: 'open' as const, visibility: 'ranked' as const, maxDrivers: 48, sessionDuration: 110, stewarding: { decisionMode: 'steward_veto' as const, requiredVotes: 3, requireDefense: true } }, // 11-15: Ranked, Custom, various stewarding { pointsSystem: 'custom' as const, qualifyingFormat: 'single-lap' as const, visibility: 'ranked' as const, maxDrivers: 32, sessionDuration: 50, stewarding: { decisionMode: 'member_veto' as const, requiredVotes: 5, requireDefense: false } }, { pointsSystem: 'custom' as const, qualifyingFormat: 'open' as const, visibility: 'ranked' as const, maxDrivers: 40, sessionDuration: 85, stewarding: { decisionMode: 'admin_only' as const, requireDefense: false } }, { pointsSystem: 'custom' as const, qualifyingFormat: 'single-lap' as const, visibility: 'ranked' as const, maxDrivers: 52, sessionDuration: 95, stewarding: { decisionMode: 'steward_decides' as const, requireDefense: true, defenseTimeLimit: 36 } }, { pointsSystem: 'custom' as const, qualifyingFormat: 'open' as const, visibility: 'ranked' as const, maxDrivers: 26, sessionDuration: 65, stewarding: { decisionMode: 'steward_vote' as const, requiredVotes: 2, requireDefense: false } }, { pointsSystem: 'custom' as const, qualifyingFormat: 'single-lap' as const, visibility: 'ranked' as const, maxDrivers: 56, sessionDuration: 105, stewarding: { decisionMode: 'member_vote' as const, requiredVotes: 7, requireDefense: true, protestDeadlineHours: 24 } }, // 16-20: Unranked, F1-2024, various stewarding (smaller, no participant requirements) { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'open' as const, visibility: 'unranked' as const, maxDrivers: 8, sessionDuration: 30, stewarding: { decisionMode: 'admin_only' as const, requireDefense: false } }, { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'single-lap' as const, visibility: 'unranked' as const, maxDrivers: 12, sessionDuration: 45, stewarding: { decisionMode: 'steward_decides' as const, requireDefense: true } }, { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'open' as const, visibility: 'unranked' as const, maxDrivers: 16, sessionDuration: 60, stewarding: { decisionMode: 'steward_vote' as const, requiredVotes: 2, requireDefense: false } }, { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'single-lap' as const, visibility: 'unranked' as const, maxDrivers: 10, sessionDuration: 35, stewarding: { decisionMode: 'member_vote' as const, requiredVotes: 3, requireDefense: true } }, { pointsSystem: 'f1-2024' as const, qualifyingFormat: 'open' as const, visibility: 'unranked' as const, maxDrivers: 20, sessionDuration: 55, stewarding: { decisionMode: 'steward_veto' as const, requiredVotes: 2, requireDefense: false } }, // 21-25: Unranked, IndyCar, various stewarding { pointsSystem: 'indycar' as const, qualifyingFormat: 'single-lap' as const, visibility: 'unranked' as const, maxDrivers: 14, sessionDuration: 40, stewarding: { decisionMode: 'member_veto' as const, requiredVotes: 3, requireDefense: true } }, { pointsSystem: 'indycar' as const, qualifyingFormat: 'open' as const, visibility: 'unranked' as const, maxDrivers: 18, sessionDuration: 50, stewarding: { decisionMode: 'admin_only' as const, requireDefense: false } }, { pointsSystem: 'indycar' as const, qualifyingFormat: 'single-lap' as const, visibility: 'unranked' as const, maxDrivers: 22, sessionDuration: 65, stewarding: { decisionMode: 'steward_decides' as const, requireDefense: true, defenseTimeLimit: 12 } }, { pointsSystem: 'indycar' as const, qualifyingFormat: 'open' as const, visibility: 'unranked' as const, maxDrivers: 6, sessionDuration: 25, stewarding: { decisionMode: 'steward_vote' as const, requiredVotes: 2, requireDefense: false } }, { pointsSystem: 'indycar' as const, qualifyingFormat: 'single-lap' as const, visibility: 'unranked' as const, maxDrivers: 24, sessionDuration: 70, stewarding: { decisionMode: 'member_vote' as const, requiredVotes: 4, requireDefense: true } }, // 26-30: Unranked, Custom, various stewarding { pointsSystem: 'custom' as const, qualifyingFormat: 'open' as const, visibility: 'unranked' as const, maxDrivers: 12, sessionDuration: 35, stewarding: { decisionMode: 'steward_veto' as const, requiredVotes: 2, requireDefense: false } }, { pointsSystem: 'custom' as const, qualifyingFormat: 'single-lap' as const, visibility: 'unranked' as const, maxDrivers: 16, sessionDuration: 45, stewarding: { decisionMode: 'member_veto' as const, requiredVotes: 3, requireDefense: true } }, { pointsSystem: 'custom' as const, qualifyingFormat: 'open' as const, visibility: 'unranked' as const, maxDrivers: 20, sessionDuration: 55, stewarding: { decisionMode: 'admin_only' as const, requireDefense: false } }, { pointsSystem: 'custom' as const, qualifyingFormat: 'single-lap' as const, visibility: 'unranked' as const, maxDrivers: 8, sessionDuration: 30, stewarding: { decisionMode: 'steward_decides' as const, requireDefense: true, defenseTimeLimit: 18 } }, { pointsSystem: 'custom' as const, qualifyingFormat: 'open' as const, visibility: 'unranked' as const, maxDrivers: 24, sessionDuration: 60, stewarding: { decisionMode: 'steward_vote' as const, requiredVotes: 3, requireDefense: false } }, ]; return Array.from({ length: leagueCount }, (_, idx) => { const i = idx + 1; const owner = faker.helpers.arrayElement(this.drivers); // Cycle through the 30 configs for variety const config = leagueConfigs[idx % 30]!; const createdAt = // Ensure some "New" leagues (created within 7 days) so `/leagues` featured categories are populated. idx % 6 === 0 ? faker.date.recent({ days: 6, refDate: this.baseDate }) : faker.date.past({ years: 2, refDate: this.baseDate }); // Calculate participant count based on visibility and league let participantCount: number; if (config.visibility === 'ranked') { // Ranked leagues need >= 10 participants // Some are full (max), some have minimum, some have none if (idx % 5 === 0) { participantCount = config.maxDrivers; // Full league } else if (idx % 3 === 0) { participantCount = 12; // Just meets minimum } else if (idx % 7 === 0) { participantCount = 0; // Empty but valid } else { participantCount = faker.number.int({ min: 10, max: config.maxDrivers - 5 }); } } else { // Unranked can have any count, but must have at least 2 if not empty if (idx % 4 === 0) { participantCount = config.maxDrivers; // Full } else if (idx % 5 === 0) { participantCount = 0; // Empty } else { participantCount = faker.number.int({ min: 2, max: Math.min(config.maxDrivers, 15) }); } } // Determine category based on scoring configuration let category: string | undefined; if (config.pointsSystem === 'f1-2024') { if (config.qualifyingFormat === 'single-lap') { category = 'driver'; } else { category = 'team'; } } else if (config.pointsSystem === 'indycar') { category = 'nations'; } else if (config.pointsSystem === 'custom') { category = 'trophy'; } // Override some leagues to have endurance or sprint categories if (idx % 8 === 0) { category = 'endurance'; } else if (idx % 7 === 0) { category = 'sprint'; } // Build the league data object const leagueData = { id: seedId(`league-${i}`, this.persistence), name: faker.company.name() + ' Racing League', description: faker.lorem.sentences(2), ownerId: owner.id.toString(), settings: { pointsSystem: config.pointsSystem, maxDrivers: config.maxDrivers, sessionDuration: config.sessionDuration, qualifyingFormat: config.qualifyingFormat, visibility: config.visibility, stewarding: { ...config.stewarding, voteTimeLimit: 72, protestDeadlineHours: 48, stewardingClosesHours: 168, notifyAccusedOnProtest: true, notifyOnVoteRequired: true, }, }, category, createdAt, participantCount, }; // Add social links with varying completeness const socialLinks: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string } = {}; const socialLinkTypes = ['discord', 'youtube', 'website'] as const; // Ensure some leagues have no social links, some have partial, some have all const numSocialLinks = idx % 4; // 0, 1, 2, or 3 const selectedTypes = faker.helpers.arrayElements(socialLinkTypes, numSocialLinks); selectedTypes.forEach(type => { if (type === 'discord') socialLinks.discordUrl = faker.internet.url(); if (type === 'youtube') socialLinks.youtubeUrl = faker.internet.url(); if (type === 'website') socialLinks.websiteUrl = faker.internet.url(); }); // Create the final league data with optional fields const finalLeagueData: { id: string; name: string; description: string; ownerId: string; settings?: Partial; category?: string | undefined; createdAt?: Date; socialLinks?: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string; }; participantCount?: number; logoRef?: MediaReference; } = { id: leagueData.id, name: leagueData.name, description: leagueData.description, ownerId: leagueData.ownerId, settings: leagueData.settings, category: leagueData.category, createdAt: leagueData.createdAt, participantCount: leagueData.participantCount, logoRef: MediaReference.generated('league', leagueData.id), }; if (Object.keys(socialLinks).length > 0) { finalLeagueData.socialLinks = socialLinks; } return League.create(finalLeagueData); }); } }