fix seeds
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Driver } from '@core/racing/domain/entities/Driver';
|
import { Driver } from '@core/racing/domain/entities/Driver';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
export class RacingDriverFactory {
|
export class RacingDriverFactory {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -7,7 +8,7 @@ export class RacingDriverFactory {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(): Driver[] {
|
create(): Driver[] {
|
||||||
const countries = ['DE', 'NL', 'FR', 'GB', 'US', 'CA', 'SE', 'NO', 'IT', 'ES'] as const;
|
const countries = ['DE', 'NL', 'FR', 'GB', 'US', 'CA', 'SE', 'NO', 'IT', 'ES', 'AU', 'BR', 'JP', 'KR', 'RU', 'PL', 'CZ', 'HU', 'AT', 'CH'] as const;
|
||||||
|
|
||||||
return Array.from({ length: this.driverCount }, (_, idx) => {
|
return Array.from({ length: this.driverCount }, (_, idx) => {
|
||||||
const i = idx + 1;
|
const i = idx + 1;
|
||||||
@@ -15,15 +16,11 @@ export class RacingDriverFactory {
|
|||||||
return Driver.create({
|
return Driver.create({
|
||||||
id: `driver-${i}`,
|
id: `driver-${i}`,
|
||||||
iracingId: String(100000 + i),
|
iracingId: String(100000 + i),
|
||||||
name: `Driver ${i}`,
|
name: faker.person.fullName(),
|
||||||
country: countries[idx % countries.length]!,
|
country: faker.helpers.arrayElement(countries),
|
||||||
bio: `Demo driver #${i} seeded for in-memory mode.`,
|
bio: faker.lorem.sentences(2),
|
||||||
joinedAt: this.addDays(this.baseDate, -90 + i),
|
joinedAt: faker.date.past({ years: 2, refDate: this.baseDate }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private addDays(date: Date, days: number): Date {
|
|
||||||
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,100 +1,58 @@
|
|||||||
import { League } from '@core/racing/domain/entities/League';
|
import { League } from '@core/racing/domain/entities/League';
|
||||||
|
import { Driver } from '@core/racing/domain/entities/Driver';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
export class RacingLeagueFactory {
|
export class RacingLeagueFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly drivers: Driver[],
|
||||||
|
) {}
|
||||||
|
|
||||||
create(): League[] {
|
create(): League[] {
|
||||||
const createdAtBase = this.baseDate;
|
const leagueCount = 20;
|
||||||
|
const pointsSystems = ['f1-2024', 'indycar', 'custom'] as const;
|
||||||
|
const qualifyingFormats = ['open', 'single-lap'] as const;
|
||||||
|
|
||||||
return [
|
return Array.from({ length: leagueCount }, (_, idx) => {
|
||||||
League.create({
|
const i = idx + 1;
|
||||||
id: 'league-1',
|
const owner = faker.helpers.arrayElement(this.drivers);
|
||||||
name: 'GridPilot Sprint Series',
|
const socialLinks: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string } = {};
|
||||||
description: 'Weekly sprint races with stable grids.',
|
if (faker.datatype.boolean()) socialLinks.discordUrl = faker.internet.url();
|
||||||
ownerId: 'driver-1',
|
if (faker.datatype.boolean()) socialLinks.youtubeUrl = faker.internet.url();
|
||||||
settings: {
|
if (faker.datatype.boolean()) socialLinks.websiteUrl = faker.internet.url();
|
||||||
pointsSystem: 'f1-2024',
|
|
||||||
maxDrivers: 24,
|
|
||||||
sessionDuration: 60,
|
|
||||||
qualifyingFormat: 'open',
|
|
||||||
},
|
|
||||||
createdAt: this.addDays(createdAtBase, -200),
|
|
||||||
socialLinks: {
|
|
||||||
discordUrl: 'https://discord.gg/gridpilot-demo',
|
|
||||||
youtubeUrl: 'https://youtube.com/@gridpilot-demo',
|
|
||||||
websiteUrl: 'https://gridpilot-demo.example.com',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
League.create({
|
|
||||||
id: 'league-2',
|
|
||||||
name: 'GridPilot Endurance Cup',
|
|
||||||
description: 'Longer races with strategy and consistency.',
|
|
||||||
ownerId: 'driver-2',
|
|
||||||
settings: {
|
|
||||||
pointsSystem: 'indycar',
|
|
||||||
maxDrivers: 32,
|
|
||||||
sessionDuration: 120,
|
|
||||||
qualifyingFormat: 'open',
|
|
||||||
},
|
|
||||||
createdAt: this.addDays(createdAtBase, -180),
|
|
||||||
socialLinks: { discordUrl: 'https://discord.gg/gridpilot-endurance' },
|
|
||||||
}),
|
|
||||||
League.create({
|
|
||||||
id: 'league-3',
|
|
||||||
name: 'GridPilot Club Ladder',
|
|
||||||
description: 'Casual ladder with fast onboarding.',
|
|
||||||
ownerId: 'driver-3',
|
|
||||||
settings: {
|
|
||||||
pointsSystem: 'f1-2024',
|
|
||||||
maxDrivers: 48,
|
|
||||||
sessionDuration: 45,
|
|
||||||
qualifyingFormat: 'single-lap',
|
|
||||||
},
|
|
||||||
createdAt: this.addDays(createdAtBase, -160),
|
|
||||||
}),
|
|
||||||
League.create({
|
|
||||||
id: 'league-4',
|
|
||||||
name: 'Nordic Night Series',
|
|
||||||
description: 'Evening races with tight fields.',
|
|
||||||
ownerId: 'driver-4',
|
|
||||||
settings: {
|
|
||||||
pointsSystem: 'f1-2024',
|
|
||||||
maxDrivers: 32,
|
|
||||||
sessionDuration: 60,
|
|
||||||
qualifyingFormat: 'open',
|
|
||||||
},
|
|
||||||
createdAt: this.addDays(createdAtBase, -150),
|
|
||||||
}),
|
|
||||||
League.create({
|
|
||||||
id: 'league-5',
|
|
||||||
name: 'Demo League (Admin)',
|
|
||||||
description: 'Primary demo league owned by driver-1.',
|
|
||||||
ownerId: 'driver-1',
|
|
||||||
settings: {
|
|
||||||
pointsSystem: 'f1-2024',
|
|
||||||
maxDrivers: 24,
|
|
||||||
sessionDuration: 60,
|
|
||||||
qualifyingFormat: 'open',
|
|
||||||
},
|
|
||||||
createdAt: this.addDays(createdAtBase, -140),
|
|
||||||
}),
|
|
||||||
League.create({
|
|
||||||
id: 'league-6',
|
|
||||||
name: 'Sim Racing Alliance',
|
|
||||||
description: 'Mixed-format season with community events.',
|
|
||||||
ownerId: 'driver-5',
|
|
||||||
settings: {
|
|
||||||
pointsSystem: 'indycar',
|
|
||||||
maxDrivers: 40,
|
|
||||||
sessionDuration: 90,
|
|
||||||
qualifyingFormat: 'open',
|
|
||||||
},
|
|
||||||
createdAt: this.addDays(createdAtBase, -130),
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private addDays(date: Date, days: number): Date {
|
const leagueData: {
|
||||||
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
ownerId: string;
|
||||||
|
settings: {
|
||||||
|
pointsSystem: 'f1-2024' | 'indycar' | 'custom';
|
||||||
|
maxDrivers: number;
|
||||||
|
sessionDuration: number;
|
||||||
|
qualifyingFormat: 'open' | 'single-lap';
|
||||||
|
};
|
||||||
|
createdAt: Date;
|
||||||
|
socialLinks?: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string };
|
||||||
|
} = {
|
||||||
|
id: `league-${i}`,
|
||||||
|
name: faker.company.name() + ' Racing League',
|
||||||
|
description: faker.lorem.sentences(2),
|
||||||
|
ownerId: owner.id.toString(),
|
||||||
|
settings: {
|
||||||
|
pointsSystem: faker.helpers.arrayElement(pointsSystems),
|
||||||
|
maxDrivers: faker.number.int({ min: 20, max: 50 }),
|
||||||
|
sessionDuration: faker.number.int({ min: 30, max: 180 }),
|
||||||
|
qualifyingFormat: faker.helpers.arrayElement(qualifyingFormats),
|
||||||
|
},
|
||||||
|
createdAt: faker.date.past({ years: 2, refDate: this.baseDate }),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(socialLinks).length > 0) {
|
||||||
|
leagueData.socialLinks = socialLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
return League.create(leagueData);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,31 @@
|
|||||||
import { League } from '@core/racing/domain/entities/League';
|
import { League } from '@core/racing/domain/entities/League';
|
||||||
import { Race } from '@core/racing/domain/entities/Race';
|
import { Race } from '@core/racing/domain/entities/Race';
|
||||||
|
import { Track } from '@core/racing/domain/entities/Track';
|
||||||
|
|
||||||
export class RacingRaceFactory {
|
export class RacingRaceFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(private readonly baseDate: Date) {}
|
||||||
|
|
||||||
create(leagues: League[]): Race[] {
|
create(leagues: League[], tracks: Track[]): Race[] {
|
||||||
const tracks = [
|
|
||||||
'Monza GP',
|
|
||||||
'Spa-Francorchamps',
|
|
||||||
'Suzuka',
|
|
||||||
'Mount Panorama',
|
|
||||||
'Silverstone GP',
|
|
||||||
'Interlagos',
|
|
||||||
'Imola',
|
|
||||||
'Laguna Seca',
|
|
||||||
];
|
|
||||||
const cars = ['GT3 – Porsche 911', 'GT3 – BMW M4', 'LMP3 Prototype', 'GT4 – Alpine', 'Touring – Civic'];
|
const cars = ['GT3 – Porsche 911', 'GT3 – BMW M4', 'LMP3 Prototype', 'GT4 – Alpine', 'Touring – Civic'];
|
||||||
|
|
||||||
const leagueIds = leagues.map((l) => l.id.toString());
|
const leagueIds = leagues.map((l) => l.id.toString());
|
||||||
|
const trackIds = tracks.map((t) => t.id);
|
||||||
const demoLeagueId = 'league-5';
|
const demoLeagueId = 'league-5';
|
||||||
|
|
||||||
const races: Race[] = [];
|
const races: Race[] = [];
|
||||||
|
|
||||||
for (let i = 1; i <= 25; i++) {
|
for (let i = 1; i <= 50; i++) {
|
||||||
const leagueId = leagueIds[(i - 1) % leagueIds.length] ?? demoLeagueId;
|
const leagueId = leagueIds[(i - 1) % leagueIds.length] ?? demoLeagueId;
|
||||||
|
const trackId = trackIds[(i - 1) % trackIds.length]!;
|
||||||
|
const track = tracks.find(t => t.id === trackId)!;
|
||||||
const scheduledAt = this.addDays(this.baseDate, i <= 10 ? -35 + i : 1 + (i - 10) * 2);
|
const scheduledAt = this.addDays(this.baseDate, i <= 10 ? -35 + i : 1 + (i - 10) * 2);
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
id: `race-${i}`,
|
id: `race-${i}`,
|
||||||
leagueId,
|
leagueId,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
track: tracks[(i - 1) % tracks.length]!,
|
track: track.name.toString(),
|
||||||
|
trackId: track.id,
|
||||||
car: cars[(i - 1) % cars.length]!,
|
car: cars[(i - 1) % cars.length]!,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration'
|
|||||||
import { Result as RaceResult } from '@core/racing/domain/entities/result/Result';
|
import { Result as RaceResult } from '@core/racing/domain/entities/result/Result';
|
||||||
import { Standing } from '@core/racing/domain/entities/Standing';
|
import { Standing } from '@core/racing/domain/entities/Standing';
|
||||||
import { Team } from '@core/racing/domain/entities/Team';
|
import { Team } from '@core/racing/domain/entities/Team';
|
||||||
|
import { Track } from '@core/racing/domain/entities/Track';
|
||||||
import type { TeamMembership } from '@core/racing/domain/types/TeamMembership';
|
import type { TeamMembership } from '@core/racing/domain/types/TeamMembership';
|
||||||
import type { FeedItem } from '@core/social/domain/types/FeedItem';
|
import type { FeedItem } from '@core/social/domain/types/FeedItem';
|
||||||
import { RacingDriverFactory } from './RacingDriverFactory';
|
import { RacingDriverFactory } from './RacingDriverFactory';
|
||||||
@@ -17,6 +18,7 @@ import { RacingRaceFactory } from './RacingRaceFactory';
|
|||||||
import { RacingResultFactory } from './RacingResultFactory';
|
import { RacingResultFactory } from './RacingResultFactory';
|
||||||
import { RacingStandingFactory } from './RacingStandingFactory';
|
import { RacingStandingFactory } from './RacingStandingFactory';
|
||||||
import { RacingTeamFactory } from './RacingTeamFactory';
|
import { RacingTeamFactory } from './RacingTeamFactory';
|
||||||
|
import { RacingTrackFactory } from './RacingTrackFactory';
|
||||||
|
|
||||||
export type Friendship = {
|
export type Friendship = {
|
||||||
driverId: string;
|
driverId: string;
|
||||||
@@ -33,6 +35,7 @@ export type RacingSeed = {
|
|||||||
raceRegistrations: RaceRegistration[];
|
raceRegistrations: RaceRegistration[];
|
||||||
teams: Team[];
|
teams: Team[];
|
||||||
teamMemberships: TeamMembership[];
|
teamMemberships: TeamMembership[];
|
||||||
|
tracks: Track[];
|
||||||
friendships: Friendship[];
|
friendships: Friendship[];
|
||||||
feedEvents: FeedItem[];
|
feedEvents: FeedItem[];
|
||||||
};
|
};
|
||||||
@@ -45,7 +48,7 @@ export type RacingSeedOptions = {
|
|||||||
export const racingSeedDefaults: Readonly<
|
export const racingSeedDefaults: Readonly<
|
||||||
Required<RacingSeedOptions>
|
Required<RacingSeedOptions>
|
||||||
> = {
|
> = {
|
||||||
driverCount: 32,
|
driverCount: 100,
|
||||||
baseDate: new Date('2025-01-15T12:00:00.000Z'),
|
baseDate: new Date('2025-01-15T12:00:00.000Z'),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,23 +63,25 @@ class RacingSeedFactory {
|
|||||||
|
|
||||||
create(): RacingSeed {
|
create(): RacingSeed {
|
||||||
const driverFactory = new RacingDriverFactory(this.driverCount, this.baseDate);
|
const driverFactory = new RacingDriverFactory(this.driverCount, this.baseDate);
|
||||||
const leagueFactory = new RacingLeagueFactory(this.baseDate);
|
const trackFactory = new RacingTrackFactory();
|
||||||
const raceFactory = new RacingRaceFactory(this.baseDate);
|
const raceFactory = new RacingRaceFactory(this.baseDate);
|
||||||
const resultFactory = new RacingResultFactory();
|
const resultFactory = new RacingResultFactory();
|
||||||
const standingFactory = new RacingStandingFactory();
|
const standingFactory = new RacingStandingFactory();
|
||||||
const membershipFactory = new RacingMembershipFactory(this.baseDate);
|
const membershipFactory = new RacingMembershipFactory(this.baseDate);
|
||||||
const teamFactory = new RacingTeamFactory(this.baseDate);
|
|
||||||
const friendshipFactory = new RacingFriendshipFactory();
|
const friendshipFactory = new RacingFriendshipFactory();
|
||||||
const feedFactory = new RacingFeedFactory(this.baseDate);
|
const feedFactory = new RacingFeedFactory(this.baseDate);
|
||||||
|
|
||||||
const drivers = driverFactory.create();
|
const drivers = driverFactory.create();
|
||||||
|
const tracks = trackFactory.create();
|
||||||
|
const leagueFactory = new RacingLeagueFactory(this.baseDate, drivers);
|
||||||
const leagues = leagueFactory.create();
|
const leagues = leagueFactory.create();
|
||||||
const races = raceFactory.create(leagues);
|
const teamFactory = new RacingTeamFactory(this.baseDate, drivers, leagues);
|
||||||
|
const teams = teamFactory.createTeams();
|
||||||
|
const races = raceFactory.create(leagues, tracks);
|
||||||
const results = resultFactory.create(drivers, races);
|
const results = resultFactory.create(drivers, races);
|
||||||
const standings = standingFactory.create(leagues, races, results);
|
const standings = standingFactory.create(leagues, races, results);
|
||||||
const leagueMemberships = membershipFactory.createLeagueMemberships(drivers, leagues);
|
const leagueMemberships = membershipFactory.createLeagueMemberships(drivers, leagues);
|
||||||
const raceRegistrations = membershipFactory.createRaceRegistrations(races);
|
const raceRegistrations = membershipFactory.createRaceRegistrations(races);
|
||||||
const teams = teamFactory.createTeams();
|
|
||||||
const teamMemberships = teamFactory.createTeamMemberships(drivers, teams);
|
const teamMemberships = teamFactory.createTeamMemberships(drivers, teams);
|
||||||
const friendships = friendshipFactory.create(drivers);
|
const friendships = friendshipFactory.create(drivers);
|
||||||
const feedEvents = feedFactory.create(drivers, friendships, races, leagues);
|
const feedEvents = feedFactory.create(drivers, friendships, races, leagues);
|
||||||
@@ -91,6 +96,7 @@ class RacingSeedFactory {
|
|||||||
raceRegistrations,
|
raceRegistrations,
|
||||||
teams,
|
teams,
|
||||||
teamMemberships,
|
teamMemberships,
|
||||||
|
tracks,
|
||||||
friendships,
|
friendships,
|
||||||
feedEvents,
|
feedEvents,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,92 +1,72 @@
|
|||||||
import { Driver } from '@core/racing/domain/entities/Driver';
|
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 { Team } from '@core/racing/domain/entities/Team';
|
||||||
import type { TeamMembership } from '@core/racing/domain/types/TeamMembership';
|
import type { TeamMembership } from '@core/racing/domain/types/TeamMembership';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
export class RacingTeamFactory {
|
export class RacingTeamFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly drivers: Driver[],
|
||||||
|
private readonly leagues: League[],
|
||||||
|
) {}
|
||||||
|
|
||||||
createTeams(): Team[] {
|
createTeams(): Team[] {
|
||||||
return [
|
const teamCount = 15;
|
||||||
Team.create({
|
|
||||||
id: 'team-1',
|
return Array.from({ length: teamCount }, (_, idx) => {
|
||||||
name: 'Apex Racing',
|
const i = idx + 1;
|
||||||
tag: 'APEX',
|
const owner = faker.helpers.arrayElement(this.drivers);
|
||||||
description: 'Demo team focused on clean racing.',
|
const teamLeagues = faker.helpers.arrayElements(
|
||||||
ownerId: 'driver-1',
|
this.leagues.map(l => l.id.toString()),
|
||||||
leagues: ['league-5'],
|
{ min: 0, max: 3 }
|
||||||
createdAt: this.addDays(this.baseDate, -100),
|
);
|
||||||
}),
|
|
||||||
Team.create({
|
return Team.create({
|
||||||
id: 'team-2',
|
id: `team-${i}`,
|
||||||
name: 'Night Owls',
|
name: faker.company.name() + ' Racing',
|
||||||
tag: 'NITE',
|
tag: faker.string.alpha({ length: 4, casing: 'upper' }),
|
||||||
description: 'Late-night grinders and endurance lovers.',
|
description: faker.lorem.sentences(2),
|
||||||
ownerId: 'driver-2',
|
ownerId: owner.id,
|
||||||
leagues: ['league-4'],
|
leagues: teamLeagues,
|
||||||
createdAt: this.addDays(this.baseDate, -90),
|
createdAt: faker.date.past({ years: 2, refDate: this.baseDate }),
|
||||||
}),
|
});
|
||||||
Team.create({
|
});
|
||||||
id: 'team-3',
|
|
||||||
name: 'Club Legends',
|
|
||||||
tag: 'CLUB',
|
|
||||||
description: 'A casual team for ladder climbing.',
|
|
||||||
ownerId: 'driver-3',
|
|
||||||
leagues: ['league-3'],
|
|
||||||
createdAt: this.addDays(this.baseDate, -80),
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createTeamMemberships(drivers: Driver[], teams: Team[]): TeamMembership[] {
|
createTeamMemberships(drivers: Driver[], teams: Team[]): TeamMembership[] {
|
||||||
const memberships: TeamMembership[] = [];
|
const memberships: TeamMembership[] = [];
|
||||||
|
const usedDrivers = new Set<string>();
|
||||||
|
|
||||||
const team1 = teams.find((t) => t.id === 'team-1');
|
teams.forEach((team) => {
|
||||||
const team2 = teams.find((t) => t.id === 'team-2');
|
const availableDrivers = drivers.filter(d => !usedDrivers.has(d.id.toString()) && d.id.toString() !== team.ownerId.toString());
|
||||||
const team3 = teams.find((t) => t.id === 'team-3');
|
const memberCount = faker.number.int({ min: 1, max: 8 });
|
||||||
|
const members = faker.helpers.arrayElements(availableDrivers, memberCount);
|
||||||
|
|
||||||
if (team1) {
|
// Add owner
|
||||||
const members = drivers.slice(0, 6);
|
memberships.push({
|
||||||
members.forEach((d, idx) => {
|
teamId: team.id.toString(),
|
||||||
memberships.push({
|
driverId: team.ownerId.toString(),
|
||||||
teamId: team1.id,
|
role: 'owner',
|
||||||
driverId: d.id,
|
status: 'active',
|
||||||
role: d.id === team1.ownerId.toString() ? 'owner' : idx === 1 ? 'manager' : 'driver',
|
joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
|
||||||
status: 'active',
|
|
||||||
joinedAt: this.addDays(this.baseDate, -50),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
usedDrivers.add(team.ownerId.toString());
|
||||||
|
|
||||||
if (team2) {
|
// Add members
|
||||||
const members = drivers.slice(6, 12);
|
members.forEach((driver) => {
|
||||||
members.forEach((d) => {
|
|
||||||
memberships.push({
|
memberships.push({
|
||||||
teamId: team2.id,
|
teamId: team.id.toString(),
|
||||||
driverId: d.id,
|
driverId: driver.id.toString(),
|
||||||
role: d.id === team2.ownerId.toString() ? 'owner' : 'driver',
|
role: faker.helpers.arrayElement(['driver', 'manager']),
|
||||||
status: 'active',
|
status: 'active',
|
||||||
joinedAt: this.addDays(this.baseDate, -45),
|
joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
|
||||||
});
|
});
|
||||||
|
usedDrivers.add(driver.id.toString());
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
if (team3) {
|
|
||||||
const members = drivers.slice(12, 18);
|
|
||||||
members.forEach((d) => {
|
|
||||||
memberships.push({
|
|
||||||
teamId: team3.id,
|
|
||||||
driverId: d.id,
|
|
||||||
role: d.id === team3.ownerId.toString() ? 'owner' : 'driver',
|
|
||||||
status: 'active',
|
|
||||||
joinedAt: this.addDays(this.baseDate, -40),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return memberships;
|
return memberships;
|
||||||
}
|
}
|
||||||
|
|
||||||
private addDays(date: Date, days: number): Date {
|
|
||||||
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
92
adapters/bootstrap/racing/RacingTrackFactory.ts
Normal file
92
adapters/bootstrap/racing/RacingTrackFactory.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { Track } from '@core/racing/domain/entities/Track';
|
||||||
|
|
||||||
|
export class RacingTrackFactory {
|
||||||
|
create(): Track[] {
|
||||||
|
return [
|
||||||
|
Track.create({
|
||||||
|
id: 'track-spa',
|
||||||
|
name: 'Spa-Francorchamps',
|
||||||
|
shortName: 'SPA',
|
||||||
|
country: 'Belgium',
|
||||||
|
category: 'road',
|
||||||
|
difficulty: 'advanced',
|
||||||
|
lengthKm: 7.004,
|
||||||
|
turns: 19,
|
||||||
|
imageUrl: '/images/tracks/spa.jpg',
|
||||||
|
gameId: 'iracing',
|
||||||
|
}),
|
||||||
|
Track.create({
|
||||||
|
id: 'track-monza',
|
||||||
|
name: 'Autodromo Nazionale Monza',
|
||||||
|
shortName: 'MON',
|
||||||
|
country: 'Italy',
|
||||||
|
category: 'road',
|
||||||
|
difficulty: 'intermediate',
|
||||||
|
lengthKm: 5.793,
|
||||||
|
turns: 11,
|
||||||
|
imageUrl: '/images/tracks/monza.jpg',
|
||||||
|
gameId: 'iracing',
|
||||||
|
}),
|
||||||
|
Track.create({
|
||||||
|
id: 'track-nurburgring',
|
||||||
|
name: 'Nürburgring Grand Prix',
|
||||||
|
shortName: 'NUR',
|
||||||
|
country: 'Germany',
|
||||||
|
category: 'road',
|
||||||
|
difficulty: 'advanced',
|
||||||
|
lengthKm: 5.148,
|
||||||
|
turns: 15,
|
||||||
|
imageUrl: '/images/tracks/nurburgring.jpg',
|
||||||
|
gameId: 'iracing',
|
||||||
|
}),
|
||||||
|
Track.create({
|
||||||
|
id: 'track-silverstone',
|
||||||
|
name: 'Silverstone Circuit',
|
||||||
|
shortName: 'SIL',
|
||||||
|
country: 'United Kingdom',
|
||||||
|
category: 'road',
|
||||||
|
difficulty: 'intermediate',
|
||||||
|
lengthKm: 5.891,
|
||||||
|
turns: 18,
|
||||||
|
imageUrl: '/images/tracks/silverstone.jpg',
|
||||||
|
gameId: 'iracing',
|
||||||
|
}),
|
||||||
|
Track.create({
|
||||||
|
id: 'track-suzuka',
|
||||||
|
name: 'Suzuka International Racing Course',
|
||||||
|
shortName: 'SUZ',
|
||||||
|
country: 'Japan',
|
||||||
|
category: 'road',
|
||||||
|
difficulty: 'expert',
|
||||||
|
lengthKm: 5.807,
|
||||||
|
turns: 18,
|
||||||
|
imageUrl: '/images/tracks/suzuka.jpg',
|
||||||
|
gameId: 'iracing',
|
||||||
|
}),
|
||||||
|
Track.create({
|
||||||
|
id: 'track-daytona',
|
||||||
|
name: 'Daytona International Speedway',
|
||||||
|
shortName: 'DAY',
|
||||||
|
country: 'United States',
|
||||||
|
category: 'oval',
|
||||||
|
difficulty: 'intermediate',
|
||||||
|
lengthKm: 4.023,
|
||||||
|
turns: 4,
|
||||||
|
imageUrl: '/images/tracks/daytona.jpg',
|
||||||
|
gameId: 'iracing',
|
||||||
|
}),
|
||||||
|
Track.create({
|
||||||
|
id: 'track-laguna',
|
||||||
|
name: 'WeatherTech Raceway Laguna Seca',
|
||||||
|
shortName: 'LAG',
|
||||||
|
country: 'United States',
|
||||||
|
category: 'road',
|
||||||
|
difficulty: 'advanced',
|
||||||
|
lengthKm: 3.602,
|
||||||
|
turns: 11,
|
||||||
|
imageUrl: '/images/tracks/laguna.jpg',
|
||||||
|
gameId: 'iracing',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ describe('CompleteDriverOnboardingUseCase', () => {
|
|||||||
useCase = new CompleteDriverOnboardingUseCase(
|
useCase = new CompleteDriverOnboardingUseCase(
|
||||||
driverRepository as unknown as IDriverRepository,
|
driverRepository as unknown as IDriverRepository,
|
||||||
logger,
|
logger,
|
||||||
|
output,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ describe('CompleteDriverOnboardingUseCase', () => {
|
|||||||
const result = await useCase.execute(command);
|
const result = await useCase.execute(command);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toEqual({ driver: createdDriver });
|
expect(output.present).toHaveBeenCalledWith({ driver: createdDriver });
|
||||||
expect(driverRepository.findById).toHaveBeenCalledWith('user-1');
|
expect(driverRepository.findById).toHaveBeenCalledWith('user-1');
|
||||||
expect(driverRepository.create).toHaveBeenCalledWith(
|
expect(driverRepository.create).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -143,7 +144,7 @@ describe('CompleteDriverOnboardingUseCase', () => {
|
|||||||
const result = await useCase.execute(command);
|
const result = await useCase.execute(command);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toEqual({ driver: createdDriver });
|
expect(output.present).toHaveBeenCalledWith({ driver: createdDriver });
|
||||||
expect(driverRepository.create).toHaveBeenCalledWith(
|
expect(driverRepository.create).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: 'user-1',
|
id: 'user-1',
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit
|
|||||||
import type { IRankingService } from '../../domain/services/IRankingService';
|
import type { IRankingService } from '../../domain/services/IRankingService';
|
||||||
import type { IDriverStatsService } from '../../domain/services/IDriverStatsService';
|
import type { IDriverStatsService } from '../../domain/services/IDriverStatsService';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
|
import type { GetDriversLeaderboardResult } from './GetDriversLeaderboardUseCase';
|
||||||
|
|
||||||
describe('GetDriversLeaderboardUseCase', () => {
|
describe('GetDriversLeaderboardUseCase', () => {
|
||||||
const mockDriverFindAll = vi.fn();
|
const mockDriverFindAll = vi.fn();
|
||||||
@@ -39,6 +41,10 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockOutput: UseCaseOutputPort<GetDriversLeaderboardResult> = {
|
||||||
|
present: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
it('should return drivers leaderboard data', async () => {
|
it('should return drivers leaderboard data', async () => {
|
||||||
const useCase = new GetDriversLeaderboardUseCase(
|
const useCase = new GetDriversLeaderboardUseCase(
|
||||||
mockDriverRepo,
|
mockDriverRepo,
|
||||||
@@ -46,6 +52,7 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
mockDriverStatsService,
|
mockDriverStatsService,
|
||||||
mockGetDriverAvatar,
|
mockGetDriverAvatar,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
|
mockOutput,
|
||||||
);
|
);
|
||||||
|
|
||||||
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
||||||
@@ -75,9 +82,8 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
const presented = result.unwrap();
|
|
||||||
|
|
||||||
expect(presented).toEqual({
|
expect(mockOutput.present).toHaveBeenCalledWith({
|
||||||
items: [
|
items: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
driver: driver1,
|
driver: driver1,
|
||||||
@@ -115,6 +121,7 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
mockDriverStatsService,
|
mockDriverStatsService,
|
||||||
mockGetDriverAvatar,
|
mockGetDriverAvatar,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
|
mockOutput,
|
||||||
);
|
);
|
||||||
|
|
||||||
mockDriverFindAll.mockResolvedValue([]);
|
mockDriverFindAll.mockResolvedValue([]);
|
||||||
@@ -125,9 +132,8 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
const presented = result.unwrap();
|
|
||||||
|
|
||||||
expect(presented).toEqual({
|
expect(mockOutput.present).toHaveBeenCalledWith({
|
||||||
items: [],
|
items: [],
|
||||||
totalRaces: 0,
|
totalRaces: 0,
|
||||||
totalWins: 0,
|
totalWins: 0,
|
||||||
@@ -142,6 +148,7 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
mockDriverStatsService,
|
mockDriverStatsService,
|
||||||
mockGetDriverAvatar,
|
mockGetDriverAvatar,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
|
mockOutput,
|
||||||
);
|
);
|
||||||
|
|
||||||
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
|
||||||
@@ -157,9 +164,8 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
const presented = result.unwrap();
|
|
||||||
|
|
||||||
expect(presented).toEqual({
|
expect(mockOutput.present).toHaveBeenCalledWith({
|
||||||
items: [
|
items: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
driver: driver1,
|
driver: driver1,
|
||||||
@@ -186,6 +192,7 @@ describe('GetDriversLeaderboardUseCase', () => {
|
|||||||
mockDriverStatsService,
|
mockDriverStatsService,
|
||||||
mockGetDriverAvatar,
|
mockGetDriverAvatar,
|
||||||
mockLogger,
|
mockLogger,
|
||||||
|
mockOutput,
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = new Error('Repository error');
|
const error = new Error('Repository error');
|
||||||
|
|||||||
@@ -3,21 +3,27 @@ import {
|
|||||||
GetTotalDriversUseCase,
|
GetTotalDriversUseCase,
|
||||||
GetTotalDriversInput,
|
GetTotalDriversInput,
|
||||||
GetTotalDriversErrorCode,
|
GetTotalDriversErrorCode,
|
||||||
|
GetTotalDriversResult,
|
||||||
} from './GetTotalDriversUseCase';
|
} from './GetTotalDriversUseCase';
|
||||||
import { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
import { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
|
|
||||||
describe('GetTotalDriversUseCase', () => {
|
describe('GetTotalDriversUseCase', () => {
|
||||||
let useCase: GetTotalDriversUseCase;
|
let useCase: GetTotalDriversUseCase;
|
||||||
let driverRepository: {
|
let driverRepository: {
|
||||||
findAll: Mock;
|
findAll: Mock;
|
||||||
};
|
};
|
||||||
|
let output: UseCaseOutputPort<GetTotalDriversResult>;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
driverRepository = {
|
driverRepository = {
|
||||||
findAll: vi.fn(),
|
findAll: vi.fn(),
|
||||||
};
|
};
|
||||||
|
output = {
|
||||||
|
present: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
useCase = new GetTotalDriversUseCase(driverRepository as unknown as IDriverRepository);
|
useCase = new GetTotalDriversUseCase(driverRepository as unknown as IDriverRepository, output);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return total number of drivers', async () => {
|
it('should return total number of drivers', async () => {
|
||||||
@@ -30,7 +36,7 @@ describe('GetTotalDriversUseCase', () => {
|
|||||||
const result = await useCase.execute(input);
|
const result = await useCase.execute(input);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toEqual({ totalDrivers: 2 });
|
expect(output.present).toHaveBeenCalledWith({ totalDrivers: 2 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error on repository failure', async () => {
|
it('should return error on repository failure', async () => {
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import {
|
|||||||
IsDriverRegisteredForRaceUseCase,
|
IsDriverRegisteredForRaceUseCase,
|
||||||
type IsDriverRegisteredForRaceInput,
|
type IsDriverRegisteredForRaceInput,
|
||||||
type IsDriverRegisteredForRaceErrorCode,
|
type IsDriverRegisteredForRaceErrorCode,
|
||||||
|
type IsDriverRegisteredForRaceResult,
|
||||||
} from './IsDriverRegisteredForRaceUseCase';
|
} from './IsDriverRegisteredForRaceUseCase';
|
||||||
import { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
|
import { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
|
|
||||||
describe('IsDriverRegisteredForRaceUseCase', () => {
|
describe('IsDriverRegisteredForRaceUseCase', () => {
|
||||||
let useCase: IsDriverRegisteredForRaceUseCase;
|
let useCase: IsDriverRegisteredForRaceUseCase;
|
||||||
@@ -19,6 +21,7 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
|
|||||||
warn: Mock;
|
warn: Mock;
|
||||||
error: Mock;
|
error: Mock;
|
||||||
};
|
};
|
||||||
|
let output: UseCaseOutputPort<IsDriverRegisteredForRaceResult>;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
registrationRepository = {
|
registrationRepository = {
|
||||||
isRegistered: vi.fn(),
|
isRegistered: vi.fn(),
|
||||||
@@ -29,9 +32,13 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
|
|||||||
warn: vi.fn(),
|
warn: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
};
|
};
|
||||||
|
output = {
|
||||||
|
present: vi.fn(),
|
||||||
|
};
|
||||||
useCase = new IsDriverRegisteredForRaceUseCase(
|
useCase = new IsDriverRegisteredForRaceUseCase(
|
||||||
registrationRepository as unknown as IRaceRegistrationRepository,
|
registrationRepository as unknown as IRaceRegistrationRepository,
|
||||||
logger as unknown as Logger,
|
logger as unknown as Logger,
|
||||||
|
output,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,7 +50,7 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
|
|||||||
const result = await useCase.execute(params);
|
const result = await useCase.execute(params);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toEqual({
|
expect(output.present).toHaveBeenCalledWith({
|
||||||
raceId: params.raceId,
|
raceId: params.raceId,
|
||||||
driverId: params.driverId,
|
driverId: params.driverId,
|
||||||
isRegistered: true,
|
isRegistered: true,
|
||||||
@@ -58,7 +65,7 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
|
|||||||
const result = await useCase.execute(params);
|
const result = await useCase.execute(params);
|
||||||
|
|
||||||
expect(result.isOk()).toBe(true);
|
expect(result.isOk()).toBe(true);
|
||||||
expect(result.unwrap()).toEqual({
|
expect(output.present).toHaveBeenCalledWith({
|
||||||
raceId: params.raceId,
|
raceId: params.raceId,
|
||||||
driverId: params.driverId,
|
driverId: params.driverId,
|
||||||
isRegistered: false,
|
isRegistered: false,
|
||||||
|
|||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -50,6 +50,7 @@
|
|||||||
"eslint-import-resolver-typescript": "2.7.1",
|
"eslint-import-resolver-typescript": "2.7.1",
|
||||||
"eslint-plugin-boundaries": "^5.3.1",
|
"eslint-plugin-boundaries": "^5.3.1",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
|
"faker": "^6.6.6",
|
||||||
"glob": "^13.0.0",
|
"glob": "^13.0.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
@@ -87,7 +88,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/testing": "^10.4.20",
|
"@nestjs/testing": "^10.4.20",
|
||||||
"ts-node-dev": "^2.0.0"
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"tsconfig-paths": "^3.15.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apps/api/node_modules/reflect-metadata": {
|
"apps/api/node_modules/reflect-metadata": {
|
||||||
@@ -8126,6 +8128,13 @@
|
|||||||
"@types/yauzl": "^2.9.1"
|
"@types/yauzl": "^2.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/faker": {
|
||||||
|
"version": "6.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/faker/-/faker-6.6.6.tgz",
|
||||||
|
"integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
|||||||
@@ -35,9 +35,10 @@
|
|||||||
"commander": "^11.0.0",
|
"commander": "^11.0.0",
|
||||||
"electron": "^39.2.7",
|
"electron": "^39.2.7",
|
||||||
"eslint": "^8.0.0",
|
"eslint": "^8.0.0",
|
||||||
|
"eslint-import-resolver-typescript": "2.7.1",
|
||||||
"eslint-plugin-boundaries": "^5.3.1",
|
"eslint-plugin-boundaries": "^5.3.1",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-import-resolver-typescript": "2.7.1",
|
"faker": "^6.6.6",
|
||||||
"glob": "^13.0.0",
|
"glob": "^13.0.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user