inmemory to postgres
This commit is contained in:
@@ -41,8 +41,13 @@ Access:
|
|||||||
## Available Commands
|
## Available Commands
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
- `npm run docker:dev` - Start dev environment
|
- `npm run docker:dev` - Start dev environment (alias of `docker:dev:up`)
|
||||||
|
- `npm run docker:dev:up` - Start dev environment
|
||||||
|
- `npm run docker:dev:postgres` - Start dev environment with `GRIDPILOT_API_PERSISTENCE=postgres`
|
||||||
|
- `npm run docker:dev:inmemory` - Start dev environment with `GRIDPILOT_API_PERSISTENCE=inmemory`
|
||||||
- `npm run docker:dev:build` - Rebuild and start
|
- `npm run docker:dev:build` - Rebuild and start
|
||||||
|
- `npm run docker:dev:restart` - Restart services
|
||||||
|
- `npm run docker:dev:ps` - Show service status
|
||||||
- `npm run docker:dev:down` - Stop services
|
- `npm run docker:dev:down` - Stop services
|
||||||
- `npm run docker:dev:logs` - View logs
|
- `npm run docker:dev:logs` - View logs
|
||||||
- `npm run docker:dev:clean` - Stop and remove volumes
|
- `npm run docker:dev:clean` - Stop and remove volumes
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import type { IPenaltyRepository } from '@core/racing/domain/repositories/IPenal
|
|||||||
import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
||||||
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
||||||
import { createRacingSeed } from './racing/RacingSeed';
|
import { createRacingSeed } from './racing/RacingSeed';
|
||||||
|
import { getApiPersistence } from '../../apps/api/src/env';
|
||||||
|
import { seedId } from './racing/SeedIdHelper';
|
||||||
|
|
||||||
export type RacingSeedDependencies = {
|
export type RacingSeedDependencies = {
|
||||||
driverRepository: IDriverRepository;
|
driverRepository: IDriverRepository;
|
||||||
@@ -60,7 +62,8 @@ export class SeedRacingData {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const seed = createRacingSeed();
|
const persistence = getApiPersistence();
|
||||||
|
const seed = createRacingSeed({ persistence });
|
||||||
|
|
||||||
let sponsorshipRequestsSeededViaRepo = false;
|
let sponsorshipRequestsSeededViaRepo = false;
|
||||||
const seedableSponsorshipRequests = this.seedDeps
|
const seedableSponsorshipRequests = this.seedDeps
|
||||||
@@ -97,7 +100,7 @@ export class SeedRacingData {
|
|||||||
|
|
||||||
const activeSeasons = seed.seasons.filter((season) => season.status.isActive());
|
const activeSeasons = seed.seasons.filter((season) => season.status.isActive());
|
||||||
for (const season of activeSeasons) {
|
for (const season of activeSeasons) {
|
||||||
const presetId = this.selectScoringPresetIdForSeason(season);
|
const presetId = this.selectScoringPresetIdForSeason(season, persistence);
|
||||||
const preset = getLeagueScoringPresetById(presetId);
|
const preset = getLeagueScoringPresetById(presetId);
|
||||||
|
|
||||||
if (!preset) {
|
if (!preset) {
|
||||||
@@ -276,7 +279,7 @@ export class SeedRacingData {
|
|||||||
const existing = await this.seedDeps.leagueScoringConfigRepository.findBySeasonId(season.id);
|
const existing = await this.seedDeps.leagueScoringConfigRepository.findBySeasonId(season.id);
|
||||||
if (existing) continue;
|
if (existing) continue;
|
||||||
|
|
||||||
const presetId = this.selectScoringPresetIdForSeason(season);
|
const presetId = this.selectScoringPresetIdForSeason(season, 'postgres');
|
||||||
const preset = getLeagueScoringPresetById(presetId);
|
const preset = getLeagueScoringPresetById(presetId);
|
||||||
|
|
||||||
if (!preset) {
|
if (!preset) {
|
||||||
@@ -297,13 +300,16 @@ export class SeedRacingData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectScoringPresetIdForSeason(season: Season): string {
|
private selectScoringPresetIdForSeason(season: Season, persistence: 'postgres' | 'inmemory'): string {
|
||||||
if (season.leagueId === 'league-5' && season.status.isActive()) {
|
const expectedLeagueId = seedId('league-5', persistence);
|
||||||
|
const expectedSeasonId = seedId('season-1-b', persistence);
|
||||||
|
|
||||||
|
if (season.leagueId === expectedLeagueId && season.status.isActive()) {
|
||||||
return 'sprint-main-driver';
|
return 'sprint-main-driver';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (season.leagueId === 'league-3') {
|
if (season.leagueId === seedId('league-3', persistence)) {
|
||||||
return season.id.endsWith('-b') ? 'sprint-main-team' : 'club-default-nations';
|
return season.id === expectedSeasonId ? 'sprint-main-team' : 'club-default-nations';
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = /^league-(\d+)$/.exec(season.leagueId);
|
const match = /^league-(\d+)$/.exec(season.leagueId);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Driver } from '@core/racing/domain/entities/Driver';
|
import { Driver } from '@core/racing/domain/entities/Driver';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingDriverFactory {
|
export class RacingDriverFactory {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly driverCount: number,
|
private readonly driverCount: number,
|
||||||
private readonly baseDate: Date,
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(): Driver[] {
|
create(): Driver[] {
|
||||||
@@ -14,7 +16,7 @@ export class RacingDriverFactory {
|
|||||||
const i = idx + 1;
|
const i = idx + 1;
|
||||||
|
|
||||||
return Driver.create({
|
return Driver.create({
|
||||||
id: `driver-${i}`,
|
id: seedId(`driver-${i}`, this.persistence),
|
||||||
iracingId: String(100000 + i),
|
iracingId: String(100000 + i),
|
||||||
name: faker.person.fullName(),
|
name: faker.person.fullName(),
|
||||||
country: faker.helpers.arrayElement(countries),
|
country: faker.helpers.arrayElement(countries),
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ 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 type { FeedItem } from '@core/social/domain/types/FeedItem';
|
import type { FeedItem } from '@core/social/domain/types/FeedItem';
|
||||||
import type { Friendship } from './RacingSeed';
|
import type { Friendship } from './RacingSeed';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingFeedFactory {
|
export class RacingFeedFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
|
) {}
|
||||||
|
|
||||||
create(drivers: Driver[], friendships: Friendship[], races: Race[], leagues: League[]): FeedItem[] {
|
create(drivers: Driver[], friendships: Friendship[], races: Race[], leagues: League[]): FeedItem[] {
|
||||||
const items: FeedItem[] = [];
|
const items: FeedItem[] = [];
|
||||||
@@ -18,13 +22,13 @@ export class RacingFeedFactory {
|
|||||||
const now = this.addMinutes(this.baseDate, 10);
|
const now = this.addMinutes(this.baseDate, 10);
|
||||||
|
|
||||||
for (let i = 2; i <= 10; i++) {
|
for (let i = 2; i <= 10; i++) {
|
||||||
const actor = drivers.find((d) => d.id === `driver-${i}`);
|
const actor = drivers.find((d) => d.id === seedId(`driver-${i}`, this.persistence));
|
||||||
if (!actor) continue;
|
if (!actor) continue;
|
||||||
|
|
||||||
if (!friendMap.has(`driver-1:${actor.id}`)) continue;
|
if (!friendMap.has(`driver-1:${actor.id}`)) continue;
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
id: `feed:${actor.id}:joined:${i}`,
|
id: seedId(`feed:${actor.id}:joined:${i}`, this.persistence),
|
||||||
type: 'friend-joined-league',
|
type: 'friend-joined-league',
|
||||||
timestamp: this.addMinutes(now, -(i * 7)),
|
timestamp: this.addMinutes(now, -(i * 7)),
|
||||||
actorDriverId: actor.id,
|
actorDriverId: actor.id,
|
||||||
@@ -38,7 +42,7 @@ export class RacingFeedFactory {
|
|||||||
|
|
||||||
if (completedRace) {
|
if (completedRace) {
|
||||||
items.push({
|
items.push({
|
||||||
id: `feed:${actor.id}:result:${i}`,
|
id: seedId(`feed:${actor.id}:result:${i}`, this.persistence),
|
||||||
type: 'friend-finished-race',
|
type: 'friend-finished-race',
|
||||||
timestamp: this.addMinutes(now, -(i * 7 + 3)),
|
timestamp: this.addMinutes(now, -(i * 7 + 3)),
|
||||||
actorDriverId: actor.id,
|
actorDriverId: actor.id,
|
||||||
@@ -56,7 +60,7 @@ export class RacingFeedFactory {
|
|||||||
|
|
||||||
if (upcomingRace) {
|
if (upcomingRace) {
|
||||||
items.push({
|
items.push({
|
||||||
id: `feed:system:scheduled:${upcomingRace.id}`,
|
id: seedId(`feed:system:scheduled:${upcomingRace.id}`, this.persistence),
|
||||||
type: 'new-race-scheduled',
|
type: 'new-race-scheduled',
|
||||||
timestamp: this.addMinutes(now, -3),
|
timestamp: this.addMinutes(now, -3),
|
||||||
leagueId: upcomingRace.leagueId,
|
leagueId: upcomingRace.leagueId,
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
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 { Driver } from '@core/racing/domain/entities/Driver';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingLeagueFactory {
|
export class RacingLeagueFactory {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly baseDate: Date,
|
private readonly baseDate: Date,
|
||||||
private readonly drivers: Driver[],
|
private readonly drivers: Driver[],
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(): League[] {
|
create(): League[] {
|
||||||
@@ -54,7 +56,7 @@ export class RacingLeagueFactory {
|
|||||||
socialLinks?: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string };
|
socialLinks?: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string };
|
||||||
participantCount?: number;
|
participantCount?: number;
|
||||||
} = {
|
} = {
|
||||||
id: `league-${i}`,
|
id: seedId(`league-${i}`, this.persistence),
|
||||||
name: faker.company.name() + ' Racing League',
|
name: faker.company.name() + ' Racing League',
|
||||||
description: faker.lorem.sentences(2),
|
description: faker.lorem.sentences(2),
|
||||||
ownerId: owner.id.toString(),
|
ownerId: owner.id.toString(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { LeagueWalletId } from '@core/racing/domain/entities/league-wallet/Leagu
|
|||||||
import { Transaction } from '@core/racing/domain/entities/league-wallet/Transaction';
|
import { Transaction } from '@core/racing/domain/entities/league-wallet/Transaction';
|
||||||
import { TransactionId } from '@core/racing/domain/entities/league-wallet/TransactionId';
|
import { TransactionId } from '@core/racing/domain/entities/league-wallet/TransactionId';
|
||||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
type LeagueWalletSeed = {
|
type LeagueWalletSeed = {
|
||||||
wallets: LeagueWallet[];
|
wallets: LeagueWallet[];
|
||||||
@@ -12,7 +13,10 @@ type LeagueWalletSeed = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class RacingLeagueWalletFactory {
|
export class RacingLeagueWalletFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
|
) {}
|
||||||
|
|
||||||
create(leagues: League[]): LeagueWalletSeed {
|
create(leagues: League[]): LeagueWalletSeed {
|
||||||
const wallets: LeagueWallet[] = [];
|
const wallets: LeagueWallet[] = [];
|
||||||
@@ -20,7 +24,7 @@ export class RacingLeagueWalletFactory {
|
|||||||
|
|
||||||
for (const league of leagues) {
|
for (const league of leagues) {
|
||||||
const leagueId = league.id.toString();
|
const leagueId = league.id.toString();
|
||||||
const walletId = `wallet-${leagueId}`;
|
const walletId = seedId(`wallet-${leagueId}`, this.persistence);
|
||||||
const createdAt = faker.date.past({ years: 2, refDate: this.baseDate });
|
const createdAt = faker.date.past({ years: 2, refDate: this.baseDate });
|
||||||
|
|
||||||
// Ensure coverage:
|
// Ensure coverage:
|
||||||
@@ -55,7 +59,7 @@ export class RacingLeagueWalletFactory {
|
|||||||
: faker.number.int({ min: 2, max: 18 });
|
: faker.number.int({ min: 2, max: 18 });
|
||||||
|
|
||||||
for (let i = 0; i < transactionCount; i++) {
|
for (let i = 0; i < transactionCount; i++) {
|
||||||
const id = TransactionId.create(`tx-${leagueId}-${i + 1}`);
|
const id = TransactionId.create(seedId(`tx-${leagueId}-${i + 1}`, this.persistence));
|
||||||
const type = this.pickTransactionType(i, leagueId);
|
const type = this.pickTransactionType(i, leagueId);
|
||||||
const amount = this.pickAmount(type, currency, leagueId);
|
const amount = this.pickAmount(type, currency, leagueId);
|
||||||
|
|
||||||
@@ -100,7 +104,7 @@ export class RacingLeagueWalletFactory {
|
|||||||
// Explicit edge-case: pending prize payout shows up in "pending payouts"
|
// Explicit edge-case: pending prize payout shows up in "pending payouts"
|
||||||
if (leagueId === 'league-5') {
|
if (leagueId === 'league-5') {
|
||||||
const pendingPrize = Transaction.create({
|
const pendingPrize = Transaction.create({
|
||||||
id: TransactionId.create(`tx-${leagueId}-pending-prize`),
|
id: TransactionId.create(seedId(`tx-${leagueId}-pending-prize`, this.persistence)),
|
||||||
walletId: LeagueWalletId.create(walletId),
|
walletId: LeagueWalletId.create(walletId),
|
||||||
type: 'prize_payout',
|
type: 'prize_payout',
|
||||||
amount: Money.create(600, currency),
|
amount: Money.create(600, currency),
|
||||||
@@ -108,7 +112,7 @@ export class RacingLeagueWalletFactory {
|
|||||||
createdAt: faker.date.recent({ days: 15, refDate: this.baseDate }),
|
createdAt: faker.date.recent({ days: 15, refDate: this.baseDate }),
|
||||||
completedAt: undefined,
|
completedAt: undefined,
|
||||||
description: 'Season prize pool payout (pending)',
|
description: 'Season prize pool payout (pending)',
|
||||||
metadata: { seasonId: 'season-2', placement: 'P1-P3' },
|
metadata: { seasonId: seedId('season-2', this.persistence), placement: 'P1-P3' },
|
||||||
});
|
});
|
||||||
|
|
||||||
transactions.push(pendingPrize);
|
transactions.push(pendingPrize);
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import { JoinRequest } from '@core/racing/domain/entities/JoinRequest';
|
|||||||
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
|
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
|
||||||
import { Race } from '@core/racing/domain/entities/Race';
|
import { Race } from '@core/racing/domain/entities/Race';
|
||||||
import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration';
|
import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingMembershipFactory {
|
export class RacingMembershipFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
|
) {}
|
||||||
|
|
||||||
createLeagueMemberships(drivers: Driver[], leagues: League[]): LeagueMembership[] {
|
createLeagueMemberships(drivers: Driver[], leagues: League[]): LeagueMembership[] {
|
||||||
const memberships: LeagueMembership[] = [];
|
const memberships: LeagueMembership[] = [];
|
||||||
@@ -34,19 +38,20 @@ export class RacingMembershipFactory {
|
|||||||
|
|
||||||
// Empty league: intentionally no memberships.
|
// Empty league: intentionally no memberships.
|
||||||
// (Keep `league-2` empty if it exists.)
|
// (Keep `league-2` empty if it exists.)
|
||||||
// Widen the type to avoid TS2367 “no overlap” comparisons in some build modes.
|
// 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;
|
const emptyLeagueId: string | undefined = leagueById.has(seedId('league-2', this.persistence)) ? (seedId('league-2', this.persistence) as string) : undefined;
|
||||||
|
|
||||||
// Demo league: "full" + overbooked with pending/inactive members.
|
// Demo league: "full" + overbooked with pending/inactive members.
|
||||||
const demoLeague = leagueById.get('league-5');
|
const demoLeague = leagueById.get(seedId('league-5', this.persistence));
|
||||||
if (demoLeague) {
|
if (demoLeague) {
|
||||||
const maxDrivers = demoLeague.settings.maxDrivers ?? 32;
|
const maxDrivers = demoLeague.settings.maxDrivers ?? 32;
|
||||||
const activeDrivers = drivers.slice(0, Math.min(maxDrivers, drivers.length));
|
const activeDrivers = drivers.slice(0, Math.min(maxDrivers, drivers.length));
|
||||||
|
|
||||||
activeDrivers.forEach((driver, idx) => {
|
activeDrivers.forEach((driver, idx) => {
|
||||||
const driverId = driver.id.toString();
|
const driverId = driver.id.toString();
|
||||||
|
const expectedDriverId = seedId('driver-1', this.persistence);
|
||||||
const role =
|
const role =
|
||||||
driverId === 'driver-1'
|
driverId === expectedDriverId
|
||||||
? 'owner'
|
? 'owner'
|
||||||
: idx === 1 || idx === 2
|
: idx === 1 || idx === 2
|
||||||
? 'admin'
|
? 'admin'
|
||||||
@@ -71,7 +76,7 @@ export class RacingMembershipFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// League with mixed statuses and roles (but not full).
|
// League with mixed statuses and roles (but not full).
|
||||||
const league1 = leagueById.get('league-1');
|
const league1 = leagueById.get(seedId('league-1', this.persistence));
|
||||||
if (league1) {
|
if (league1) {
|
||||||
const pick = drivers.slice(15, 25);
|
const pick = drivers.slice(15, 25);
|
||||||
pick.forEach((driver, idx) => {
|
pick.forEach((driver, idx) => {
|
||||||
@@ -86,7 +91,7 @@ export class RacingMembershipFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// League with only pending memberships (tests "pending list" UX).
|
// League with only pending memberships (tests "pending list" UX).
|
||||||
const league4 = leagueById.get('league-4');
|
const league4 = leagueById.get(seedId('league-4', this.persistence));
|
||||||
if (league4) {
|
if (league4) {
|
||||||
drivers.slice(40, 48).forEach((driver, idx) => {
|
drivers.slice(40, 48).forEach((driver, idx) => {
|
||||||
add({
|
add({
|
||||||
@@ -106,10 +111,10 @@ export class RacingMembershipFactory {
|
|||||||
|
|
||||||
for (const league of leagues) {
|
for (const league of leagues) {
|
||||||
const leagueId = league.id.toString();
|
const leagueId = league.id.toString();
|
||||||
if (leagueId === 'league-5') continue;
|
if (leagueId === seedId('league-5', this.persistence)) continue;
|
||||||
if (emptyLeagueId && leagueId === emptyLeagueId) continue;
|
if (emptyLeagueId && leagueId === emptyLeagueId) continue;
|
||||||
|
|
||||||
if (driverNumber % 11 === 0 && leagueId === 'league-3') {
|
if (driverNumber % 11 === 0 && leagueId === seedId('league-3', this.persistence)) {
|
||||||
add({
|
add({
|
||||||
leagueId,
|
leagueId,
|
||||||
driverId,
|
driverId,
|
||||||
@@ -157,7 +162,7 @@ export class RacingMembershipFactory {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// League with lots of requests + membership/request conflicts (everyone is a member of league-5 already).
|
// League with lots of requests + membership/request conflicts (everyone is a member of league-5 already).
|
||||||
const demoLeagueId = 'league-5';
|
const demoLeagueId = seedId('league-5', this.persistence);
|
||||||
const demoDrivers = drivers.slice(10, 35);
|
const demoDrivers = drivers.slice(10, 35);
|
||||||
demoDrivers.forEach((driver, idx) => {
|
demoDrivers.forEach((driver, idx) => {
|
||||||
const message =
|
const message =
|
||||||
@@ -178,7 +183,7 @@ export class RacingMembershipFactory {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// League with a few "normal" requests (only drivers who are NOT members already).
|
// League with a few "normal" requests (only drivers who are NOT members already).
|
||||||
const targetLeagueId = 'league-1';
|
const targetLeagueId = seedId('league-1', this.persistence);
|
||||||
const nonMembers = drivers
|
const nonMembers = drivers
|
||||||
.filter(driver => !membershipIds.has(`${targetLeagueId}:${driver.id.toString()}`))
|
.filter(driver => !membershipIds.has(`${targetLeagueId}:${driver.id.toString()}`))
|
||||||
.slice(0, 6);
|
.slice(0, 6);
|
||||||
@@ -193,11 +198,11 @@ export class RacingMembershipFactory {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Single request with no message (explicit id).
|
// Single request with no message (explicit id).
|
||||||
const league3Exists = leagues.some(l => l.id.toString() === 'league-3');
|
const league3Exists = leagues.some(l => l.id.toString() === seedId('league-3', this.persistence));
|
||||||
if (league3Exists && drivers[0]) {
|
if (league3Exists && drivers[0]) {
|
||||||
addRequest({
|
addRequest({
|
||||||
id: 'league-3-join-req-1',
|
id: seedId('league-3-join-req-1', this.persistence),
|
||||||
leagueId: 'league-3',
|
leagueId: seedId('league-3', this.persistence),
|
||||||
driverId: drivers[0].id.toString(),
|
driverId: drivers[0].id.toString(),
|
||||||
requestedAt: this.addDays(this.baseDate, -9),
|
requestedAt: this.addDays(this.baseDate, -9),
|
||||||
});
|
});
|
||||||
@@ -206,16 +211,16 @@ export class RacingMembershipFactory {
|
|||||||
// Duplicate id edge case (last write wins in in-memory repo).
|
// Duplicate id edge case (last write wins in in-memory repo).
|
||||||
if (drivers[1]) {
|
if (drivers[1]) {
|
||||||
addRequest({
|
addRequest({
|
||||||
id: 'dup-league-join-req-1',
|
id: seedId('dup-league-join-req-1', this.persistence),
|
||||||
leagueId: 'league-7',
|
leagueId: seedId('league-7', this.persistence),
|
||||||
driverId: drivers[1].id.toString(),
|
driverId: drivers[1].id.toString(),
|
||||||
requestedAt: this.addDays(this.baseDate, -2),
|
requestedAt: this.addDays(this.baseDate, -2),
|
||||||
message: 'First request message (will be overwritten).',
|
message: 'First request message (will be overwritten).',
|
||||||
});
|
});
|
||||||
|
|
||||||
addRequest({
|
addRequest({
|
||||||
id: 'dup-league-join-req-1',
|
id: seedId('dup-league-join-req-1', this.persistence),
|
||||||
leagueId: 'league-7',
|
leagueId: seedId('league-7', this.persistence),
|
||||||
driverId: drivers[1].id.toString(),
|
driverId: drivers[1].id.toString(),
|
||||||
requestedAt: this.addDays(this.baseDate, -1),
|
requestedAt: this.addDays(this.baseDate, -1),
|
||||||
message: 'Updated request message (duplicate id).',
|
message: 'Updated request message (duplicate id).',
|
||||||
@@ -223,10 +228,11 @@ export class RacingMembershipFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Explicit conflict: join request exists even though membership exists.
|
// Explicit conflict: join request exists even though membership exists.
|
||||||
const driver1 = drivers.find(d => d.id.toString() === 'driver-1');
|
const expectedDriverId = seedId('driver-1', this.persistence);
|
||||||
|
const driver1 = drivers.find(d => d.id.toString() === expectedDriverId);
|
||||||
if (driver1) {
|
if (driver1) {
|
||||||
addRequest({
|
addRequest({
|
||||||
id: 'conflict-req-league-5-driver-1',
|
id: seedId('conflict-req-league-5-driver-1', this.persistence),
|
||||||
leagueId: demoLeagueId,
|
leagueId: demoLeagueId,
|
||||||
driverId: driver1.id.toString(),
|
driverId: driver1.id.toString(),
|
||||||
requestedAt: this.addDays(this.baseDate, -15),
|
requestedAt: this.addDays(this.baseDate, -15),
|
||||||
@@ -311,12 +317,12 @@ export class RacingMembershipFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Keep a tiny curated "happy path" for the demo league as well
|
// Keep a tiny curated "happy path" for the demo league as well
|
||||||
const upcomingDemoLeague = races.filter((r) => r.status.toString() === 'scheduled' && r.leagueId === 'league-5').slice(0, 3);
|
const upcomingDemoLeague = races.filter((r) => r.status.toString() === 'scheduled' && r.leagueId === seedId('league-5', this.persistence)).slice(0, 3);
|
||||||
for (const race of upcomingDemoLeague) {
|
for (const race of upcomingDemoLeague) {
|
||||||
registrations.push(
|
registrations.push(
|
||||||
RaceRegistration.create({
|
RaceRegistration.create({
|
||||||
raceId: race.id,
|
raceId: race.id,
|
||||||
driverId: 'driver-1',
|
driverId: seedId('driver-1', this.persistence),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
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';
|
import { Track } from '@core/racing/domain/entities/Track';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingRaceFactory {
|
export class RacingRaceFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
|
) {}
|
||||||
|
|
||||||
create(leagues: League[], tracks: Track[]): Race[] {
|
create(leagues: League[], tracks: Track[]): Race[] {
|
||||||
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'];
|
||||||
@@ -51,7 +55,7 @@ export class RacingRaceFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
id: `race-${i}`,
|
id: seedId(`race-${i}`, this.persistence),
|
||||||
leagueId,
|
leagueId,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
track: track.name.toString(),
|
track: track.name.toString(),
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { Driver } from '@core/racing/domain/entities/Driver';
|
import { Driver } from '@core/racing/domain/entities/Driver';
|
||||||
import { Race } from '@core/racing/domain/entities/Race';
|
import { Race } from '@core/racing/domain/entities/Race';
|
||||||
import { Result as RaceResult } from '@core/racing/domain/entities/result/Result';
|
import { Result as RaceResult } from '@core/racing/domain/entities/result/Result';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingResultFactory {
|
export class RacingResultFactory {
|
||||||
|
constructor(private readonly persistence: 'postgres' | 'inmemory' = 'inmemory') {}
|
||||||
|
|
||||||
create(drivers: Driver[], races: Race[]): RaceResult[] {
|
create(drivers: Driver[], races: Race[]): RaceResult[] {
|
||||||
const results: RaceResult[] = [];
|
const results: RaceResult[] = [];
|
||||||
const completed = races.filter((r) => r.status.toString() === 'completed');
|
const completed = races.filter((r) => r.status.toString() === 'completed');
|
||||||
@@ -50,7 +53,7 @@ export class RacingResultFactory {
|
|||||||
|
|
||||||
results.push(
|
results.push(
|
||||||
RaceResult.create({
|
RaceResult.create({
|
||||||
id: `${race.id}:${driver.id}`,
|
id: seedId(`${race.id}:${driver.id}`, this.persistence),
|
||||||
raceId: race.id,
|
raceId: race.id,
|
||||||
driverId: driver.id,
|
driverId: driver.id,
|
||||||
position,
|
position,
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ import { SeasonSponsorship } from '@core/racing/domain/entities/season/SeasonSpo
|
|||||||
import type { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
import type { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||||
import type { SeasonStatusValue } from '@core/racing/domain/value-objects/SeasonStatus';
|
import type { SeasonStatusValue } from '@core/racing/domain/value-objects/SeasonStatus';
|
||||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingSeasonSponsorshipFactory {
|
export class RacingSeasonSponsorshipFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
|
) {}
|
||||||
|
|
||||||
createSeasons(leagues: League[]): Season[] {
|
createSeasons(leagues: League[]): Season[] {
|
||||||
const seasons: Season[] = [];
|
const seasons: Season[] = [];
|
||||||
@@ -16,10 +20,10 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
for (const league of leagues) {
|
for (const league of leagues) {
|
||||||
const leagueId = league.id.toString();
|
const leagueId = league.id.toString();
|
||||||
|
|
||||||
if (leagueId === 'league-5') {
|
if (leagueId === seedId('league-5', this.persistence)) {
|
||||||
seasons.push(
|
seasons.push(
|
||||||
Season.create({
|
Season.create({
|
||||||
id: 'season-1',
|
id: seedId('season-1', this.persistence),
|
||||||
leagueId,
|
leagueId,
|
||||||
gameId: 'iracing',
|
gameId: 'iracing',
|
||||||
name: 'Season 1 (GT Sprint)',
|
name: 'Season 1 (GT Sprint)',
|
||||||
@@ -29,7 +33,7 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
startDate: this.daysFromBase(-30),
|
startDate: this.daysFromBase(-30),
|
||||||
}),
|
}),
|
||||||
Season.create({
|
Season.create({
|
||||||
id: 'season-2',
|
id: seedId('season-2', this.persistence),
|
||||||
leagueId,
|
leagueId,
|
||||||
gameId: 'iracing',
|
gameId: 'iracing',
|
||||||
name: 'Season 2 (Endurance Cup)',
|
name: 'Season 2 (Endurance Cup)',
|
||||||
@@ -40,7 +44,7 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
endDate: this.daysFromBase(-60),
|
endDate: this.daysFromBase(-60),
|
||||||
}),
|
}),
|
||||||
Season.create({
|
Season.create({
|
||||||
id: 'season-3',
|
id: seedId('season-3', this.persistence),
|
||||||
leagueId,
|
leagueId,
|
||||||
gameId: 'iracing',
|
gameId: 'iracing',
|
||||||
name: 'Season 3 (Planned)',
|
name: 'Season 3 (Planned)',
|
||||||
@@ -53,10 +57,10 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leagueId === 'league-3') {
|
if (leagueId === seedId('league-3', this.persistence)) {
|
||||||
seasons.push(
|
seasons.push(
|
||||||
Season.create({
|
Season.create({
|
||||||
id: 'league-3-season-a',
|
id: seedId('league-3-season-a', this.persistence),
|
||||||
leagueId,
|
leagueId,
|
||||||
gameId: 'iracing',
|
gameId: 'iracing',
|
||||||
name: 'Split Season A',
|
name: 'Split Season A',
|
||||||
@@ -66,7 +70,7 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
startDate: this.daysFromBase(-10),
|
startDate: this.daysFromBase(-10),
|
||||||
}),
|
}),
|
||||||
Season.create({
|
Season.create({
|
||||||
id: 'league-3-season-b',
|
id: seedId('league-3-season-b', this.persistence),
|
||||||
leagueId,
|
leagueId,
|
||||||
gameId: 'iracing',
|
gameId: 'iracing',
|
||||||
name: 'Split Season B',
|
name: 'Split Season B',
|
||||||
@@ -80,16 +84,16 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseYear = this.baseDate.getUTCFullYear();
|
const baseYear = this.baseDate.getUTCFullYear();
|
||||||
const seasonCount = leagueId === 'league-2' ? 1 : faker.number.int({ min: 1, max: 3 });
|
const seasonCount = leagueId === seedId('league-2', this.persistence) ? 1 : faker.number.int({ min: 1, max: 3 });
|
||||||
|
|
||||||
for (let i = 0; i < seasonCount; i++) {
|
for (let i = 0; i < seasonCount; i++) {
|
||||||
const id = `${leagueId}-season-${i + 1}`;
|
const id = seedId(`${leagueId}-season-${i + 1}`, this.persistence);
|
||||||
const isFirst = i === 0;
|
const isFirst = i === 0;
|
||||||
|
|
||||||
const status: SeasonStatusValue =
|
const status: SeasonStatusValue =
|
||||||
leagueId === 'league-1' && isFirst
|
leagueId === seedId('league-1', this.persistence) && isFirst
|
||||||
? 'active'
|
? 'active'
|
||||||
: leagueId === 'league-2'
|
: leagueId === seedId('league-2', this.persistence)
|
||||||
? 'planned'
|
? 'planned'
|
||||||
: isFirst
|
: isFirst
|
||||||
? faker.helpers.arrayElement(['active', 'planned'] as const)
|
? faker.helpers.arrayElement(['active', 'planned'] as const)
|
||||||
@@ -131,8 +135,9 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
const sponsorIds = sponsors.map((s) => s.id.toString());
|
const sponsorIds = sponsors.map((s) => s.id.toString());
|
||||||
|
|
||||||
for (const season of seasons) {
|
for (const season of seasons) {
|
||||||
|
const expectedSeasonId = seedId('season-1', this.persistence);
|
||||||
const sponsorshipCount =
|
const sponsorshipCount =
|
||||||
season.id === 'season-1'
|
season.id === expectedSeasonId
|
||||||
? 2
|
? 2
|
||||||
: season.status.isActive()
|
: season.status.isActive()
|
||||||
? faker.number.int({ min: 0, max: 2 })
|
? faker.number.int({ min: 0, max: 2 })
|
||||||
@@ -152,7 +157,7 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
usedSponsorIds.add(sponsorId);
|
usedSponsorIds.add(sponsorId);
|
||||||
|
|
||||||
const base = SeasonSponsorship.create({
|
const base = SeasonSponsorship.create({
|
||||||
id: `season-sponsorship-${season.id}-${i + 1}`,
|
id: seedId(`season-sponsorship-${season.id}-${i + 1}`, this.persistence),
|
||||||
seasonId: season.id,
|
seasonId: season.id,
|
||||||
leagueId: season.leagueId,
|
leagueId: season.leagueId,
|
||||||
sponsorId,
|
sponsorId,
|
||||||
@@ -190,7 +195,8 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
const sponsorIds = sponsors.map((s) => s.id.toString());
|
const sponsorIds = sponsors.map((s) => s.id.toString());
|
||||||
|
|
||||||
for (const season of seasons) {
|
for (const season of seasons) {
|
||||||
const isHighTrafficDemo = season.id === 'season-1';
|
const expectedSeasonId = seedId('season-1', this.persistence);
|
||||||
|
const isHighTrafficDemo = season.id === expectedSeasonId;
|
||||||
const maxRequests =
|
const maxRequests =
|
||||||
isHighTrafficDemo
|
isHighTrafficDemo
|
||||||
? 8
|
? 8
|
||||||
@@ -204,7 +210,7 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
|
|
||||||
const sponsorId =
|
const sponsorId =
|
||||||
isHighTrafficDemo && i === 0
|
isHighTrafficDemo && i === 0
|
||||||
? 'demo-sponsor-1'
|
? seedId('demo-sponsor-1', this.persistence)
|
||||||
: faker.helpers.arrayElement(sponsorIds);
|
: faker.helpers.arrayElement(sponsorIds);
|
||||||
|
|
||||||
const offeredAmount = Money.create(
|
const offeredAmount = Money.create(
|
||||||
@@ -230,7 +236,7 @@ export class RacingSeasonSponsorshipFactory {
|
|||||||
|
|
||||||
requests.push(
|
requests.push(
|
||||||
SponsorshipRequest.create({
|
SponsorshipRequest.create({
|
||||||
id: `sponsorship-request-${season.id}-${i + 1}`,
|
id: seedId(`sponsorship-request-${season.id}-${i + 1}`, this.persistence),
|
||||||
sponsorId,
|
sponsorId,
|
||||||
entityType: 'season',
|
entityType: 'season',
|
||||||
entityId: season.id,
|
entityId: season.id,
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export type RacingSeed = {
|
|||||||
export type RacingSeedOptions = {
|
export type RacingSeedOptions = {
|
||||||
driverCount?: number;
|
driverCount?: number;
|
||||||
baseDate?: Date;
|
baseDate?: Date;
|
||||||
|
persistence?: 'postgres' | 'inmemory';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const racingSeedDefaults: Readonly<
|
export const racingSeedDefaults: Readonly<
|
||||||
@@ -69,33 +70,36 @@ export const racingSeedDefaults: Readonly<
|
|||||||
> = {
|
> = {
|
||||||
driverCount: 100,
|
driverCount: 100,
|
||||||
baseDate: new Date(),
|
baseDate: new Date(),
|
||||||
|
persistence: 'inmemory',
|
||||||
};
|
};
|
||||||
|
|
||||||
class RacingSeedFactory {
|
class RacingSeedFactory {
|
||||||
private readonly driverCount: number;
|
private readonly driverCount: number;
|
||||||
private readonly baseDate: Date;
|
private readonly baseDate: Date;
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory';
|
||||||
|
|
||||||
constructor(options: RacingSeedOptions) {
|
constructor(options: RacingSeedOptions) {
|
||||||
this.driverCount = options.driverCount ?? racingSeedDefaults.driverCount;
|
this.driverCount = options.driverCount ?? racingSeedDefaults.driverCount;
|
||||||
this.baseDate = options.baseDate ?? racingSeedDefaults.baseDate;
|
this.baseDate = options.baseDate ?? racingSeedDefaults.baseDate;
|
||||||
|
this.persistence = options.persistence ?? racingSeedDefaults.persistence;
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): RacingSeed {
|
create(): RacingSeed {
|
||||||
const driverFactory = new RacingDriverFactory(this.driverCount, this.baseDate);
|
const driverFactory = new RacingDriverFactory(this.driverCount, this.baseDate, this.persistence);
|
||||||
const trackFactory = new RacingTrackFactory();
|
const trackFactory = new RacingTrackFactory();
|
||||||
const raceFactory = new RacingRaceFactory(this.baseDate);
|
const raceFactory = new RacingRaceFactory(this.baseDate, this.persistence);
|
||||||
const resultFactory = new RacingResultFactory();
|
const resultFactory = new RacingResultFactory(this.persistence);
|
||||||
const standingFactory = new RacingStandingFactory();
|
const standingFactory = new RacingStandingFactory();
|
||||||
const membershipFactory = new RacingMembershipFactory(this.baseDate);
|
const membershipFactory = new RacingMembershipFactory(this.baseDate, this.persistence);
|
||||||
const sponsorFactory = new RacingSponsorFactory(this.baseDate);
|
const sponsorFactory = new RacingSponsorFactory(this.baseDate, this.persistence);
|
||||||
const seasonSponsorshipFactory = new RacingSeasonSponsorshipFactory(this.baseDate);
|
const seasonSponsorshipFactory = new RacingSeasonSponsorshipFactory(this.baseDate, this.persistence);
|
||||||
const leagueWalletFactory = new RacingLeagueWalletFactory(this.baseDate);
|
const leagueWalletFactory = new RacingLeagueWalletFactory(this.baseDate, this.persistence);
|
||||||
const friendshipFactory = new RacingFriendshipFactory();
|
const friendshipFactory = new RacingFriendshipFactory();
|
||||||
const feedFactory = new RacingFeedFactory(this.baseDate);
|
const feedFactory = new RacingFeedFactory(this.baseDate, this.persistence);
|
||||||
|
|
||||||
const drivers = driverFactory.create();
|
const drivers = driverFactory.create();
|
||||||
const tracks = trackFactory.create();
|
const tracks = trackFactory.create();
|
||||||
const leagueFactory = new RacingLeagueFactory(this.baseDate, drivers);
|
const leagueFactory = new RacingLeagueFactory(this.baseDate, drivers, this.persistence);
|
||||||
const leagues = leagueFactory.create();
|
const leagues = leagueFactory.create();
|
||||||
const sponsors = sponsorFactory.create();
|
const sponsors = sponsorFactory.create();
|
||||||
const seasons = seasonSponsorshipFactory.createSeasons(leagues);
|
const seasons = seasonSponsorshipFactory.createSeasons(leagues);
|
||||||
@@ -107,7 +111,7 @@ class RacingSeedFactory {
|
|||||||
|
|
||||||
const { wallets: leagueWallets, transactions: leagueWalletTransactions } = leagueWalletFactory.create(leagues);
|
const { wallets: leagueWallets, transactions: leagueWalletTransactions } = leagueWalletFactory.create(leagues);
|
||||||
|
|
||||||
const teamFactory = new RacingTeamFactory(this.baseDate);
|
const teamFactory = new RacingTeamFactory(this.baseDate, this.persistence);
|
||||||
const teams = teamFactory.createTeams(drivers, leagues);
|
const teams = teamFactory.createTeams(drivers, leagues);
|
||||||
const races = raceFactory.create(leagues, tracks);
|
const races = raceFactory.create(leagues, tracks);
|
||||||
const results = resultFactory.create(drivers, races);
|
const results = resultFactory.create(drivers, races);
|
||||||
@@ -116,7 +120,7 @@ class RacingSeedFactory {
|
|||||||
const teamMemberships = teamFactory.createTeamMemberships(drivers, teams);
|
const teamMemberships = teamFactory.createTeamMemberships(drivers, teams);
|
||||||
const teamJoinRequests = teamFactory.createTeamJoinRequests(drivers, teams, teamMemberships);
|
const teamJoinRequests = teamFactory.createTeamJoinRequests(drivers, teams, teamMemberships);
|
||||||
|
|
||||||
const stewardingFactory = new RacingStewardingFactory(this.baseDate);
|
const stewardingFactory = new RacingStewardingFactory(this.baseDate, this.persistence);
|
||||||
const { protests, penalties } = stewardingFactory.create(races, drivers, leagueMemberships);
|
const { protests, penalties } = stewardingFactory.create(races, drivers, leagueMemberships);
|
||||||
|
|
||||||
const friendships = friendshipFactory.create(drivers);
|
const friendships = friendshipFactory.create(drivers);
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingSponsorFactory {
|
export class RacingSponsorFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
|
) {}
|
||||||
|
|
||||||
create(): Sponsor[] {
|
create(): Sponsor[] {
|
||||||
const demoSponsor = Sponsor.create({
|
const demoSponsor = Sponsor.create({
|
||||||
id: 'demo-sponsor-1',
|
id: seedId('demo-sponsor-1', this.persistence),
|
||||||
name: 'GridPilot Sim Racing Supply',
|
name: 'GridPilot Sim Racing Supply',
|
||||||
contactEmail: 'partnerships@gridpilot.example',
|
contactEmail: 'partnerships@gridpilot.example',
|
||||||
logoUrl: 'http://localhost:3000/images/header.jpeg',
|
logoUrl: 'http://localhost:3000/images/header.jpeg',
|
||||||
@@ -111,7 +115,7 @@ export class RacingSponsorFactory {
|
|||||||
const websiteUrl = websiteUrls[idx % websiteUrls.length]!;
|
const websiteUrl = websiteUrls[idx % websiteUrls.length]!;
|
||||||
|
|
||||||
return Sponsor.create({
|
return Sponsor.create({
|
||||||
id: `sponsor-${i}`,
|
id: seedId(`sponsor-${i}`, this.persistence),
|
||||||
name,
|
name,
|
||||||
contactEmail: `partnerships+${safeName}@example.com`,
|
contactEmail: `partnerships+${safeName}@example.com`,
|
||||||
...(logoUrl ? { logoUrl } : {}),
|
...(logoUrl ? { logoUrl } : {}),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMember
|
|||||||
import { Protest } from '@core/racing/domain/entities/Protest';
|
import { Protest } from '@core/racing/domain/entities/Protest';
|
||||||
import type { Race } from '@core/racing/domain/entities/Race';
|
import type { Race } from '@core/racing/domain/entities/Race';
|
||||||
import { Penalty } from '@core/racing/domain/entities/penalty/Penalty';
|
import { Penalty } from '@core/racing/domain/entities/penalty/Penalty';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
type StewardingSeed = {
|
type StewardingSeed = {
|
||||||
protests: Protest[];
|
protests: Protest[];
|
||||||
@@ -11,7 +12,10 @@ type StewardingSeed = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class RacingStewardingFactory {
|
export class RacingStewardingFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
|
) {}
|
||||||
|
|
||||||
create(races: Race[], drivers: Driver[], leagueMemberships: LeagueMembership[]): StewardingSeed {
|
create(races: Race[], drivers: Driver[], leagueMemberships: LeagueMembership[]): StewardingSeed {
|
||||||
const protests: Protest[] = [];
|
const protests: Protest[] = [];
|
||||||
@@ -57,7 +61,7 @@ export class RacingStewardingFactory {
|
|||||||
if (firstRace) {
|
if (firstRace) {
|
||||||
protests.push(
|
protests.push(
|
||||||
Protest.create({
|
Protest.create({
|
||||||
id: 'protest-1',
|
id: seedId('protest-1', this.persistence),
|
||||||
raceId: firstRace.id.toString(),
|
raceId: firstRace.id.toString(),
|
||||||
protestingDriverId: protester,
|
protestingDriverId: protester,
|
||||||
accusedDriverId: accused,
|
accusedDriverId: accused,
|
||||||
@@ -76,7 +80,7 @@ export class RacingStewardingFactory {
|
|||||||
// No penalty yet (pending), but seed a direct steward warning on same race.
|
// No penalty yet (pending), but seed a direct steward warning on same race.
|
||||||
penalties.push(
|
penalties.push(
|
||||||
Penalty.create({
|
Penalty.create({
|
||||||
id: 'penalty-1',
|
id: seedId('penalty-1', this.persistence),
|
||||||
leagueId: 'league-5',
|
leagueId: 'league-5',
|
||||||
raceId: firstRace.id.toString(),
|
raceId: firstRace.id.toString(),
|
||||||
driverId: accused,
|
driverId: accused,
|
||||||
@@ -94,7 +98,7 @@ export class RacingStewardingFactory {
|
|||||||
if (secondRace) {
|
if (secondRace) {
|
||||||
protests.push(
|
protests.push(
|
||||||
Protest.create({
|
Protest.create({
|
||||||
id: 'protest-2',
|
id: seedId('protest-2', this.persistence),
|
||||||
raceId: secondRace.id.toString(),
|
raceId: secondRace.id.toString(),
|
||||||
protestingDriverId: spare,
|
protestingDriverId: spare,
|
||||||
accusedDriverId: accused,
|
accusedDriverId: accused,
|
||||||
@@ -113,14 +117,14 @@ export class RacingStewardingFactory {
|
|||||||
// Under review penalty still pending (linked to protest)
|
// Under review penalty still pending (linked to protest)
|
||||||
penalties.push(
|
penalties.push(
|
||||||
Penalty.create({
|
Penalty.create({
|
||||||
id: 'penalty-2',
|
id: seedId('penalty-2', this.persistence),
|
||||||
leagueId: 'league-5',
|
leagueId: 'league-5',
|
||||||
raceId: secondRace.id.toString(),
|
raceId: secondRace.id.toString(),
|
||||||
driverId: accused,
|
driverId: accused,
|
||||||
type: 'time_penalty',
|
type: 'time_penalty',
|
||||||
value: 10,
|
value: 10,
|
||||||
reason: 'Unsafe rejoin (protest pending review)',
|
reason: 'Unsafe rejoin (protest pending review)',
|
||||||
protestId: 'protest-2',
|
protestId: seedId('protest-2', this.persistence),
|
||||||
issuedBy: steward,
|
issuedBy: steward,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
issuedAt: faker.date.recent({ days: 10, refDate: this.baseDate }),
|
issuedAt: faker.date.recent({ days: 10, refDate: this.baseDate }),
|
||||||
@@ -131,7 +135,7 @@ export class RacingStewardingFactory {
|
|||||||
|
|
||||||
if (thirdRace) {
|
if (thirdRace) {
|
||||||
const upheld = Protest.create({
|
const upheld = Protest.create({
|
||||||
id: 'protest-3',
|
id: seedId('protest-3', this.persistence),
|
||||||
raceId: thirdRace.id.toString(),
|
raceId: thirdRace.id.toString(),
|
||||||
protestingDriverId: protester,
|
protestingDriverId: protester,
|
||||||
accusedDriverId: spare,
|
accusedDriverId: spare,
|
||||||
@@ -151,7 +155,7 @@ export class RacingStewardingFactory {
|
|||||||
|
|
||||||
penalties.push(
|
penalties.push(
|
||||||
Penalty.create({
|
Penalty.create({
|
||||||
id: 'penalty-3',
|
id: seedId('penalty-3', this.persistence),
|
||||||
leagueId: 'league-5',
|
leagueId: 'league-5',
|
||||||
raceId: thirdRace.id.toString(),
|
raceId: thirdRace.id.toString(),
|
||||||
driverId: spare,
|
driverId: spare,
|
||||||
@@ -168,7 +172,7 @@ export class RacingStewardingFactory {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const dismissed = Protest.create({
|
const dismissed = Protest.create({
|
||||||
id: 'protest-4',
|
id: seedId('protest-4', this.persistence),
|
||||||
raceId: thirdRace.id.toString(),
|
raceId: thirdRace.id.toString(),
|
||||||
protestingDriverId: accused,
|
protestingDriverId: accused,
|
||||||
accusedDriverId: protester,
|
accusedDriverId: protester,
|
||||||
@@ -210,7 +214,7 @@ export class RacingStewardingFactory {
|
|||||||
const status = faker.helpers.arrayElement(['awaiting_defense', 'withdrawn'] as const);
|
const status = faker.helpers.arrayElement(['awaiting_defense', 'withdrawn'] as const);
|
||||||
|
|
||||||
const protest = Protest.create({
|
const protest = Protest.create({
|
||||||
id: `protest-${leagueId}-${race.id.toString()}`,
|
id: seedId(`protest-${leagueId}-${race.id.toString()}`, this.persistence),
|
||||||
raceId: race.id.toString(),
|
raceId: race.id.toString(),
|
||||||
protestingDriverId: a,
|
protestingDriverId: a,
|
||||||
accusedDriverId: b,
|
accusedDriverId: b,
|
||||||
@@ -239,7 +243,7 @@ export class RacingStewardingFactory {
|
|||||||
// A non-protest-linked penalty can still exist for the same race.
|
// A non-protest-linked penalty can still exist for the same race.
|
||||||
penalties.push(
|
penalties.push(
|
||||||
Penalty.create({
|
Penalty.create({
|
||||||
id: `penalty-${leagueId}-${race.id.toString()}`,
|
id: seedId(`penalty-${leagueId}-${race.id.toString()}`, this.persistence),
|
||||||
leagueId,
|
leagueId,
|
||||||
raceId: race.id.toString(),
|
raceId: race.id.toString(),
|
||||||
driverId: b,
|
driverId: b,
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ 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 { TeamJoinRequest, TeamMembership } from '@core/racing/domain/types/TeamMembership';
|
import type { TeamJoinRequest, TeamMembership } from '@core/racing/domain/types/TeamMembership';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { seedId } from './SeedIdHelper';
|
||||||
|
|
||||||
export class RacingTeamFactory {
|
export class RacingTeamFactory {
|
||||||
constructor(private readonly baseDate: Date) {}
|
constructor(
|
||||||
|
private readonly baseDate: Date,
|
||||||
|
private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
|
||||||
|
) {}
|
||||||
|
|
||||||
createTeams(drivers: Driver[], leagues: League[]): Team[] {
|
createTeams(drivers: Driver[], leagues: League[]): Team[] {
|
||||||
const teamCount = 15;
|
const teamCount = 15;
|
||||||
@@ -19,7 +23,7 @@ export class RacingTeamFactory {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return Team.create({
|
return Team.create({
|
||||||
id: `team-${i}`,
|
id: seedId(`team-${i}`, this.persistence),
|
||||||
name: faker.company.name() + ' Racing',
|
name: faker.company.name() + ' Racing',
|
||||||
tag: faker.string.alpha({ length: 4, casing: 'upper' }),
|
tag: faker.string.alpha({ length: 4, casing: 'upper' }),
|
||||||
description: faker.lorem.sentences(2),
|
description: faker.lorem.sentences(2),
|
||||||
@@ -128,7 +132,7 @@ export class RacingTeamFactory {
|
|||||||
|
|
||||||
candidateDriverIds.forEach((driverId, idx) => {
|
candidateDriverIds.forEach((driverId, idx) => {
|
||||||
addRequest({
|
addRequest({
|
||||||
id: `team-join-${team1.id.toString()}-${driverId}`,
|
id: seedId(`team-join-${team1.id.toString()}-${driverId}`, this.persistence),
|
||||||
teamId: team1.id.toString(),
|
teamId: team1.id.toString(),
|
||||||
driverId,
|
driverId,
|
||||||
requestedAt: this.addDays(this.baseDate, -(5 + idx)),
|
requestedAt: this.addDays(this.baseDate, -(5 + idx)),
|
||||||
@@ -142,7 +146,7 @@ export class RacingTeamFactory {
|
|||||||
|
|
||||||
// Conflict edge case: owner submits a join request to own team.
|
// Conflict edge case: owner submits a join request to own team.
|
||||||
addRequest({
|
addRequest({
|
||||||
id: `team-join-${team1.id.toString()}-${team1.ownerId.toString()}-conflict`,
|
id: seedId(`team-join-${team1.id.toString()}-${team1.ownerId.toString()}-conflict`, this.persistence),
|
||||||
teamId: team1.id.toString(),
|
teamId: team1.id.toString(),
|
||||||
driverId: team1.ownerId.toString(),
|
driverId: team1.ownerId.toString(),
|
||||||
requestedAt: this.addDays(this.baseDate, -1),
|
requestedAt: this.addDays(this.baseDate, -1),
|
||||||
@@ -154,7 +158,7 @@ export class RacingTeamFactory {
|
|||||||
if (team3 && drivers[0]) {
|
if (team3 && drivers[0]) {
|
||||||
const driverId = drivers[0].id.toString();
|
const driverId = drivers[0].id.toString();
|
||||||
addRequest({
|
addRequest({
|
||||||
id: 'dup-team-join-req-1',
|
id: seedId('dup-team-join-req-1', this.persistence),
|
||||||
teamId: team3.id.toString(),
|
teamId: team3.id.toString(),
|
||||||
driverId,
|
driverId,
|
||||||
requestedAt: this.addDays(this.baseDate, -10),
|
requestedAt: this.addDays(this.baseDate, -10),
|
||||||
@@ -162,7 +166,7 @@ export class RacingTeamFactory {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addRequest({
|
addRequest({
|
||||||
id: 'dup-team-join-req-1',
|
id: seedId('dup-team-join-req-1', this.persistence),
|
||||||
teamId: team3.id.toString(),
|
teamId: team3.id.toString(),
|
||||||
driverId,
|
driverId,
|
||||||
requestedAt: this.addDays(this.baseDate, -9),
|
requestedAt: this.addDays(this.baseDate, -9),
|
||||||
|
|||||||
128
adapters/bootstrap/racing/SeedIdHelper.test.ts
Normal file
128
adapters/bootstrap/racing/SeedIdHelper.test.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { stableUuidFromSeedKey, seedId, seedUuid } from './SeedIdHelper';
|
||||||
|
|
||||||
|
describe('SeedIdHelper', () => {
|
||||||
|
describe('stableUuidFromSeedKey', () => {
|
||||||
|
it('should return a valid UUID v4 format', () => {
|
||||||
|
const uuid = stableUuidFromSeedKey('team-3');
|
||||||
|
|
||||||
|
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
||||||
|
// where y is one of [8, 9, a, b]
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
expect(uuid).toMatch(uuidRegex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be deterministic - same input produces same output', () => {
|
||||||
|
const input = 'driver-1';
|
||||||
|
const uuid1 = stableUuidFromSeedKey(input);
|
||||||
|
const uuid2 = stableUuidFromSeedKey(input);
|
||||||
|
|
||||||
|
expect(uuid1).toBe(uuid2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should produce different UUIDs for different inputs', () => {
|
||||||
|
const uuid1 = stableUuidFromSeedKey('team-1');
|
||||||
|
const uuid2 = stableUuidFromSeedKey('team-2');
|
||||||
|
|
||||||
|
expect(uuid1).not.toBe(uuid2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle various seed patterns', () => {
|
||||||
|
const testCases = [
|
||||||
|
'team-3',
|
||||||
|
'driver-1',
|
||||||
|
'league-5',
|
||||||
|
'race-10',
|
||||||
|
'sponsor-1',
|
||||||
|
'season-1',
|
||||||
|
'demo-sponsor-1',
|
||||||
|
'league-3-season-a',
|
||||||
|
'team-join-team-1-driver-5',
|
||||||
|
'dup-team-join-req-1',
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach((seed) => {
|
||||||
|
const uuid = stableUuidFromSeedKey(seed);
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
expect(uuid).toMatch(uuidRegex);
|
||||||
|
expect(uuid.length).toBe(36);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty string', () => {
|
||||||
|
const uuid = stableUuidFromSeedKey('');
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
expect(uuid).toMatch(uuidRegex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle long seed strings', () => {
|
||||||
|
const longSeed = 'a'.repeat(1000);
|
||||||
|
const uuid = stableUuidFromSeedKey(longSeed);
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
expect(uuid).toMatch(uuidRegex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('seedId', () => {
|
||||||
|
it('should return UUID for postgres mode', () => {
|
||||||
|
const result = seedId('team-3', 'postgres');
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
expect(result).toMatch(uuidRegex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return original string for inmemory mode', () => {
|
||||||
|
const result = seedId('team-3', 'inmemory');
|
||||||
|
expect(result).toBe('team-3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be deterministic for postgres mode', () => {
|
||||||
|
const result1 = seedId('driver-5', 'postgres');
|
||||||
|
const result2 = seedId('driver-5', 'postgres');
|
||||||
|
expect(result1).toBe(result2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve original string for inmemory mode', () => {
|
||||||
|
const testCases = ['team-3', 'driver-1', 'league-5', 'race-10'];
|
||||||
|
|
||||||
|
testCases.forEach((seed) => {
|
||||||
|
const result = seedId(seed, 'inmemory');
|
||||||
|
expect(result).toBe(seed);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('seedUuid', () => {
|
||||||
|
it('should return a valid UUID', () => {
|
||||||
|
const uuid = seedUuid('team-3');
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
expect(uuid).toMatch(uuidRegex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be deterministic', () => {
|
||||||
|
const uuid1 = seedUuid('driver-1');
|
||||||
|
const uuid2 = seedUuid('driver-1');
|
||||||
|
expect(uuid1).toBe(uuid2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should produce different UUIDs for different inputs', () => {
|
||||||
|
const uuid1 = seedUuid('team-1');
|
||||||
|
const uuid2 = seedUuid('team-2');
|
||||||
|
expect(uuid1).not.toBe(uuid2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UUID v4 compliance', () => {
|
||||||
|
it('should have version 4 bits set correctly', () => {
|
||||||
|
const uuid = stableUuidFromSeedKey('test');
|
||||||
|
// Version is in the 13th character (0-indexed), should be '4'
|
||||||
|
expect(uuid.charAt(14)).toBe('4');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have variant bits set correctly', () => {
|
||||||
|
const uuid = stableUuidFromSeedKey('test');
|
||||||
|
// Variant is in the 19th character (0-indexed), should be 8, 9, a, or b
|
||||||
|
const variantChar = uuid.charAt(19);
|
||||||
|
expect(['8', '9', 'a', 'b']).toContain(variantChar.toLowerCase());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
76
adapters/bootstrap/racing/SeedIdHelper.ts
Normal file
76
adapters/bootstrap/racing/SeedIdHelper.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a deterministic UUID v4 from a seed string.
|
||||||
|
*
|
||||||
|
* This is used for postgres seeding to ensure all IDs are valid UUIDs
|
||||||
|
* while maintaining determinism across runs (important for tests and reproducible seeds).
|
||||||
|
*
|
||||||
|
* The function uses SHA-256 hash of the seed, then formats it as a UUID v4
|
||||||
|
* with proper version (0100) and variant (10xx) bits.
|
||||||
|
*
|
||||||
|
* @param seedKey - The deterministic seed string (e.g., "team-3", "driver-1")
|
||||||
|
* @returns A valid UUID v4 string
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* stableUuidFromSeedKey("team-3")
|
||||||
|
* // Returns: "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" (deterministic)
|
||||||
|
*/
|
||||||
|
export function stableUuidFromSeedKey(seedKey: string): string {
|
||||||
|
// Create a deterministic hash from the seed
|
||||||
|
const hash = createHash('sha256').update(seedKey).digest('hex');
|
||||||
|
|
||||||
|
// Take first 32 characters for UUID
|
||||||
|
const uuidHex = hash.substring(0, 32);
|
||||||
|
|
||||||
|
// Format as UUID v4 with proper version and variant bits
|
||||||
|
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
||||||
|
// where x is any hex digit, y is one of [8, 9, a, b]
|
||||||
|
|
||||||
|
// Build the UUID step by step
|
||||||
|
const part1 = uuidHex.substring(0, 8);
|
||||||
|
const part2 = uuidHex.substring(8, 12);
|
||||||
|
const part3 = '4' + uuidHex.substring(13, 16); // Version 4
|
||||||
|
const variantChar = ['8', '9', 'a', 'b'][parseInt(uuidHex.charAt(16), 16) % 4];
|
||||||
|
const part4 = variantChar + uuidHex.substring(17, 20);
|
||||||
|
const part5 = uuidHex.substring(20, 32);
|
||||||
|
|
||||||
|
return `${part1}-${part2}-${part3}-${part4}-${part5}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an ID appropriate for the current persistence mode.
|
||||||
|
*
|
||||||
|
* For postgres mode: returns a deterministic UUID
|
||||||
|
* For inmemory mode: returns the original deterministic string ID
|
||||||
|
*
|
||||||
|
* @param seedKey - The deterministic seed string (e.g., "team-3")
|
||||||
|
* @param mode - The persistence mode
|
||||||
|
* @returns Either a UUID (postgres) or the original string (inmemory)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* seedId("team-3", "postgres") // Returns UUID
|
||||||
|
* seedId("team-3", "inmemory") // Returns "team-3"
|
||||||
|
*/
|
||||||
|
export function seedId(seedKey: string, mode: 'postgres' | 'inmemory'): string {
|
||||||
|
if (mode === 'postgres') {
|
||||||
|
return stableUuidFromSeedKey(seedKey);
|
||||||
|
}
|
||||||
|
return seedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a UUID for postgres seeding.
|
||||||
|
*
|
||||||
|
* Convenience wrapper around seedId with mode already set to 'postgres'.
|
||||||
|
* Use this when you know you're in postgres mode.
|
||||||
|
*
|
||||||
|
* @param seedKey - The deterministic seed string
|
||||||
|
* @returns A valid UUID v4 string
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* seedUuid("driver-1") // Returns UUID
|
||||||
|
*/
|
||||||
|
export function seedUuid(seedKey: string): string {
|
||||||
|
return stableUuidFromSeedKey(seedKey);
|
||||||
|
}
|
||||||
@@ -79,10 +79,15 @@
|
|||||||
"dev": "echo 'Development server placeholder - to be configured'",
|
"dev": "echo 'Development server placeholder - to be configured'",
|
||||||
"lint": "npx eslint apps/api/src --ext .ts,.tsx --max-warnings 0",
|
"lint": "npx eslint apps/api/src --ext .ts,.tsx --max-warnings 0",
|
||||||
"docker:dev": "COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up",
|
"docker:dev": "COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up",
|
||||||
|
"docker:dev:up": "COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up",
|
||||||
|
"docker:dev:postgres": "sh -lc \"GRIDPILOT_API_PERSISTENCE=postgres npm run docker:dev:up\"",
|
||||||
|
"docker:dev:inmemory": "sh -lc \"GRIDPILOT_API_PERSISTENCE=inmemory npm run docker:dev:up\"",
|
||||||
"docker:dev:build": "COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up --build",
|
"docker:dev:build": "COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up --build",
|
||||||
"docker:dev:clean": "docker-compose -p gridpilot-dev -f docker-compose.dev.yml down -v",
|
"docker:dev:restart": "docker-compose -p gridpilot-dev -f docker-compose.dev.yml restart",
|
||||||
|
"docker:dev:ps": "docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps",
|
||||||
"docker:dev:down": "docker-compose -p gridpilot-dev -f docker-compose.dev.yml down",
|
"docker:dev:down": "docker-compose -p gridpilot-dev -f docker-compose.dev.yml down",
|
||||||
"docker:dev:logs": "docker-compose -p gridpilot-dev -f docker-compose.dev.yml logs -f",
|
"docker:dev:logs": "docker-compose -p gridpilot-dev -f docker-compose.dev.yml logs -f",
|
||||||
|
"docker:dev:clean": "docker-compose -p gridpilot-dev -f docker-compose.dev.yml down -v",
|
||||||
"docker:e2e:down": "docker-compose -f docker/docker-compose.e2e.yml down",
|
"docker:e2e:down": "docker-compose -f docker/docker-compose.e2e.yml down",
|
||||||
"docker:e2e:up": "docker-compose -f docker/docker-compose.e2e.yml up -d",
|
"docker:e2e:up": "docker-compose -f docker/docker-compose.e2e.yml up -d",
|
||||||
"docker:prod": "docker-compose -p gridpilot-prod -f docker-compose.prod.yml up -d",
|
"docker:prod": "docker-compose -p gridpilot-prod -f docker-compose.prod.yml up -d",
|
||||||
|
|||||||
Reference in New Issue
Block a user