more seeds
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Driver } from '@core/racing/domain/entities/Driver';
|
||||
import { League } from '@core/racing/domain/entities/League';
|
||||
import { JoinRequest } from '@core/racing/domain/entities/JoinRequest';
|
||||
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
|
||||
import { Race } from '@core/racing/domain/entities/Race';
|
||||
import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration';
|
||||
@@ -9,44 +10,308 @@ export class RacingMembershipFactory {
|
||||
|
||||
createLeagueMemberships(drivers: Driver[], leagues: League[]): LeagueMembership[] {
|
||||
const memberships: LeagueMembership[] = [];
|
||||
const leagueById = new Map(leagues.map(l => [l.id.toString(), l]));
|
||||
|
||||
for (const driver of drivers) {
|
||||
const driverId = driver.id;
|
||||
|
||||
const add = (props: {
|
||||
leagueId: string;
|
||||
driverId: string;
|
||||
role: 'owner' | 'admin' | 'steward' | 'member';
|
||||
status: 'active' | 'inactive' | 'pending';
|
||||
joinedDaysAgo: number;
|
||||
id?: string;
|
||||
}): void => {
|
||||
memberships.push(
|
||||
LeagueMembership.create({
|
||||
leagueId: 'league-5',
|
||||
driverId,
|
||||
role: driverId === 'driver-1' ? 'owner' : 'member',
|
||||
status: 'active',
|
||||
joinedAt: this.addDays(this.baseDate, -60),
|
||||
leagueId: props.leagueId,
|
||||
driverId: props.driverId,
|
||||
role: props.role,
|
||||
status: props.status,
|
||||
joinedAt: this.addDays(this.baseDate, -props.joinedDaysAgo),
|
||||
...(props.id !== undefined ? { id: props.id } : {}),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// Empty league: intentionally no memberships.
|
||||
// (Keep `league-2` empty if it exists.)
|
||||
// Widen the type to avoid TS2367 “no overlap” comparisons in some build modes.
|
||||
const emptyLeagueId: string | undefined = leagueById.has('league-2') ? ('league-2' as string) : undefined;
|
||||
|
||||
// Demo league: "full" + overbooked with pending/inactive members.
|
||||
const demoLeague = leagueById.get('league-5');
|
||||
if (demoLeague) {
|
||||
const maxDrivers = demoLeague.settings.maxDrivers ?? 32;
|
||||
const activeDrivers = drivers.slice(0, Math.min(maxDrivers, drivers.length));
|
||||
|
||||
activeDrivers.forEach((driver, idx) => {
|
||||
const driverId = driver.id.toString();
|
||||
const role =
|
||||
driverId === 'driver-1'
|
||||
? 'owner'
|
||||
: idx === 1 || idx === 2
|
||||
? 'admin'
|
||||
: idx === 3 || idx === 4
|
||||
? 'steward'
|
||||
: 'member';
|
||||
|
||||
add({ leagueId: demoLeague.id.toString(), driverId, role, status: 'active', joinedDaysAgo: 60 - idx });
|
||||
});
|
||||
|
||||
// Over-cap edge cases (membership exists but not active / pending)
|
||||
const overbooked = drivers.slice(activeDrivers.length, activeDrivers.length + 4);
|
||||
overbooked.forEach((driver, idx) => {
|
||||
add({
|
||||
leagueId: demoLeague.id.toString(),
|
||||
driverId: driver.id.toString(),
|
||||
role: 'member',
|
||||
status: idx % 2 === 0 ? 'pending' : 'inactive',
|
||||
joinedDaysAgo: 10 + idx,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// League with mixed statuses and roles (but not full).
|
||||
const league1 = leagueById.get('league-1');
|
||||
if (league1) {
|
||||
const pick = drivers.slice(15, 25);
|
||||
pick.forEach((driver, idx) => {
|
||||
add({
|
||||
leagueId: league1.id.toString(),
|
||||
driverId: driver.id.toString(),
|
||||
role: idx === 0 ? 'owner' : idx === 1 ? 'steward' : 'member',
|
||||
status: idx % 5 === 0 ? 'pending' : idx % 7 === 0 ? 'inactive' : 'active',
|
||||
joinedDaysAgo: 30 + idx,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// League with only pending memberships (tests "pending list" UX).
|
||||
const league4 = leagueById.get('league-4');
|
||||
if (league4) {
|
||||
drivers.slice(40, 48).forEach((driver, idx) => {
|
||||
add({
|
||||
leagueId: league4.id.toString(),
|
||||
driverId: driver.id.toString(),
|
||||
role: idx === 0 ? 'owner' : 'member',
|
||||
status: 'pending',
|
||||
joinedDaysAgo: 3 + idx,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Spread remaining drivers across remaining leagues to create realistic overlap.
|
||||
for (const driver of drivers) {
|
||||
const driverId = driver.id.toString();
|
||||
const driverNumber = Number(driverId.split('-')[1]);
|
||||
const extraLeague = leagues[(driverNumber % (leagues.length - 1)) + 1];
|
||||
|
||||
if (extraLeague) {
|
||||
memberships.push(
|
||||
LeagueMembership.create({
|
||||
leagueId: extraLeague.id.toString(),
|
||||
for (const league of leagues) {
|
||||
const leagueId = league.id.toString();
|
||||
if (leagueId === 'league-5') continue;
|
||||
if (emptyLeagueId && leagueId === emptyLeagueId) continue;
|
||||
|
||||
if (driverNumber % 11 === 0 && leagueId === 'league-3') {
|
||||
add({
|
||||
leagueId,
|
||||
driverId,
|
||||
role: 'member',
|
||||
status: 'inactive',
|
||||
joinedDaysAgo: 120,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sparse membership distribution (not every driver in every league)
|
||||
if ((driverNumber + Number(leagueId.split('-')[1] ?? 0)) % 9 === 0) {
|
||||
add({
|
||||
leagueId,
|
||||
driverId,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
joinedAt: this.addDays(this.baseDate, -40),
|
||||
}),
|
||||
);
|
||||
joinedDaysAgo: 45,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return memberships;
|
||||
}
|
||||
|
||||
createRaceRegistrations(races: Race[]): RaceRegistration[] {
|
||||
createLeagueJoinRequests(
|
||||
drivers: Driver[],
|
||||
leagues: League[],
|
||||
leagueMemberships: LeagueMembership[],
|
||||
): JoinRequest[] {
|
||||
const membershipIds = new Set<string>(leagueMemberships.map(m => m.id.toString()));
|
||||
const requests: JoinRequest[] = [];
|
||||
|
||||
const addRequest = (input: { leagueId: string; driverId: string; id?: string; message?: string; requestedAt?: Date }) => {
|
||||
requests.push(
|
||||
JoinRequest.create({
|
||||
leagueId: input.leagueId,
|
||||
driverId: input.driverId,
|
||||
...(input.id !== undefined && { id: input.id }),
|
||||
...(input.message !== undefined && { message: input.message }),
|
||||
...(input.requestedAt !== undefined && { requestedAt: input.requestedAt }),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// League with lots of requests + membership/request conflicts (everyone is a member of league-5 already).
|
||||
const demoLeagueId = 'league-5';
|
||||
const demoDrivers = drivers.slice(10, 35);
|
||||
demoDrivers.forEach((driver, idx) => {
|
||||
const message =
|
||||
idx % 4 === 0
|
||||
? 'Interested in consistent stewarding and clean racing.'
|
||||
: idx % 4 === 1
|
||||
? undefined
|
||||
: idx % 4 === 2
|
||||
? ''
|
||||
: 'Can I join mid-season and still be eligible for points?';
|
||||
|
||||
addRequest({
|
||||
leagueId: demoLeagueId,
|
||||
driverId: driver.id.toString(),
|
||||
requestedAt: this.addDays(this.baseDate, -(7 + idx)),
|
||||
...(message !== undefined && { message }),
|
||||
});
|
||||
});
|
||||
|
||||
// League with a few "normal" requests (only drivers who are NOT members already).
|
||||
const targetLeagueId = 'league-1';
|
||||
const nonMembers = drivers
|
||||
.filter(driver => !membershipIds.has(`${targetLeagueId}:${driver.id.toString()}`))
|
||||
.slice(0, 6);
|
||||
|
||||
nonMembers.forEach((driver, idx) => {
|
||||
addRequest({
|
||||
leagueId: targetLeagueId,
|
||||
driverId: driver.id.toString(),
|
||||
requestedAt: this.addDays(this.baseDate, -(3 + idx)),
|
||||
...(idx % 2 === 0 && { message: 'Looking for regular endurance rounds and stable race times.' }),
|
||||
});
|
||||
});
|
||||
|
||||
// Single request with no message (explicit id).
|
||||
const league3Exists = leagues.some(l => l.id.toString() === 'league-3');
|
||||
if (league3Exists && drivers[0]) {
|
||||
addRequest({
|
||||
id: 'league-3-join-req-1',
|
||||
leagueId: 'league-3',
|
||||
driverId: drivers[0].id.toString(),
|
||||
requestedAt: this.addDays(this.baseDate, -9),
|
||||
});
|
||||
}
|
||||
|
||||
// Duplicate id edge case (last write wins in in-memory repo).
|
||||
if (drivers[1]) {
|
||||
addRequest({
|
||||
id: 'dup-league-join-req-1',
|
||||
leagueId: 'league-7',
|
||||
driverId: drivers[1].id.toString(),
|
||||
requestedAt: this.addDays(this.baseDate, -2),
|
||||
message: 'First request message (will be overwritten).',
|
||||
});
|
||||
|
||||
addRequest({
|
||||
id: 'dup-league-join-req-1',
|
||||
leagueId: 'league-7',
|
||||
driverId: drivers[1].id.toString(),
|
||||
requestedAt: this.addDays(this.baseDate, -1),
|
||||
message: 'Updated request message (duplicate id).',
|
||||
});
|
||||
}
|
||||
|
||||
// Explicit conflict: join request exists even though membership exists.
|
||||
const driver1 = drivers.find(d => d.id.toString() === 'driver-1');
|
||||
if (driver1) {
|
||||
addRequest({
|
||||
id: 'conflict-req-league-5-driver-1',
|
||||
leagueId: demoLeagueId,
|
||||
driverId: driver1.id.toString(),
|
||||
requestedAt: this.addDays(this.baseDate, -15),
|
||||
message: 'Testing UI edge case: request exists for an existing member.',
|
||||
});
|
||||
}
|
||||
|
||||
return requests;
|
||||
}
|
||||
|
||||
createRaceRegistrations(
|
||||
races: Race[],
|
||||
drivers: Driver[],
|
||||
leagueMemberships: LeagueMembership[],
|
||||
): RaceRegistration[] {
|
||||
const registrations: RaceRegistration[] = [];
|
||||
const activeMembershipKey = new Set(
|
||||
leagueMemberships
|
||||
.filter(m => m.status.toString() === 'active')
|
||||
.map(m => `${m.leagueId.toString()}:${m.driverId.toString()}`),
|
||||
);
|
||||
|
||||
const scheduled = races.filter((r) => r.status === 'scheduled');
|
||||
|
||||
for (const race of scheduled) {
|
||||
const leagueId = race.leagueId.toString();
|
||||
const targetCount = (race as unknown as { registeredCount?: number }).registeredCount ?? 0;
|
||||
|
||||
// 25%: intentionally no registrations
|
||||
if (Number(race.id.toString().split('-')[1] ?? 0) % 4 === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const eligibleDrivers = drivers
|
||||
.map(d => d.id.toString())
|
||||
.filter(driverId => activeMembershipKey.has(`${leagueId}:${driverId}`));
|
||||
|
||||
const desired = Math.min(
|
||||
eligibleDrivers.length,
|
||||
Math.max(1, targetCount > 0 ? targetCount : 3),
|
||||
);
|
||||
|
||||
const start = Number(race.id.toString().split('-')[1] ?? 0);
|
||||
for (let i = 0; i < desired; i++) {
|
||||
const driverId = eligibleDrivers[(start + i) % eligibleDrivers.length];
|
||||
if (!driverId) continue;
|
||||
|
||||
registrations.push(
|
||||
RaceRegistration.create({
|
||||
raceId: race.id,
|
||||
driverId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Edge case: one "outsider" registration (driver not active in league)
|
||||
if (eligibleDrivers.length > 0 && drivers.length > eligibleDrivers.length) {
|
||||
const outsider = drivers
|
||||
.map(d => d.id.toString())
|
||||
.find(driverId => !activeMembershipKey.has(`${leagueId}:${driverId}`));
|
||||
|
||||
if (outsider && start % 7 === 0) {
|
||||
registrations.push(
|
||||
RaceRegistration.create({
|
||||
raceId: race.id,
|
||||
driverId: outsider,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Edge case: duplicate registration (should be ignored by repo if unique constrained)
|
||||
if (start % 9 === 0 && registrations.length > 0) {
|
||||
const last = registrations[registrations.length - 1]!;
|
||||
registrations.push(
|
||||
RaceRegistration.create({
|
||||
raceId: last.raceId.toString(),
|
||||
driverId: last.driverId.toString(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep a tiny curated "happy path" for the demo league as well
|
||||
const upcomingDemoLeague = races.filter((r) => r.status === 'scheduled' && r.leagueId === 'league-5').slice(0, 3);
|
||||
|
||||
for (const race of upcomingDemoLeague) {
|
||||
registrations.push(
|
||||
RaceRegistration.create({
|
||||
|
||||
Reference in New Issue
Block a user