wip
This commit is contained in:
@@ -33,17 +33,20 @@ function hashString(input: string): number {
|
||||
|
||||
export function getDriverAvatar(driverId: string): string {
|
||||
const index = hashString(driverId) % DRIVER_AVATARS.length;
|
||||
return DRIVER_AVATARS[index];
|
||||
const avatar = DRIVER_AVATARS[index] ?? DRIVER_AVATARS[0];
|
||||
return avatar;
|
||||
}
|
||||
|
||||
export function getTeamLogo(teamId: string): string {
|
||||
const index = hashString(teamId) % TEAM_LOGOS.length;
|
||||
return TEAM_LOGOS[index];
|
||||
const logo = TEAM_LOGOS[index] ?? TEAM_LOGOS[0];
|
||||
return logo;
|
||||
}
|
||||
|
||||
export function getLeagueBanner(leagueId: string): string {
|
||||
const index = hashString(leagueId) % LEAGUE_BANNERS.length;
|
||||
return LEAGUE_BANNERS[index];
|
||||
const banner = LEAGUE_BANNERS[index] ?? LEAGUE_BANNERS[0];
|
||||
return banner;
|
||||
}
|
||||
|
||||
export interface LeagueCoverImage {
|
||||
|
||||
@@ -81,7 +81,7 @@ export class DemoAvatarGenerationAdapter implements AvatarGenerationPort {
|
||||
|
||||
// For demo, return placeholder URLs based on suit color
|
||||
// In production, these would be actual AI-generated images
|
||||
const colorAvatars = this.placeholderAvatars[options.suitColor] ?? this.placeholderAvatars.blue;
|
||||
const colorAvatars = this.getPlaceholderAvatars(options.suitColor) ?? [];
|
||||
|
||||
// Generate unique URLs with a hash to simulate different generations
|
||||
const hash = this.generateHash((options.facePhotoUrl ?? '') + Date.now());
|
||||
@@ -104,6 +104,14 @@ export class DemoAvatarGenerationAdapter implements AvatarGenerationPort {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
private getPlaceholderAvatars(color: string): string[] | undefined {
|
||||
const avatars = this.placeholderAvatars[color];
|
||||
if (!avatars || avatars.length === 0) {
|
||||
return this.placeholderAvatars.blue;
|
||||
}
|
||||
return avatars;
|
||||
}
|
||||
|
||||
private generateHash(input: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < input.length; i += 1) {
|
||||
|
||||
@@ -7,7 +7,8 @@ export class DemoImageServiceAdapter implements ImageServicePort {
|
||||
getDriverAvatar(driverId: string): string {
|
||||
const numericSuffixMatch = driverId.match(/(\d+)$/);
|
||||
if (numericSuffixMatch) {
|
||||
const numericSuffix = Number.parseInt(numericSuffixMatch[1], 10);
|
||||
const numericSuffixString = numericSuffixMatch[1] ?? '';
|
||||
const numericSuffix = Number.parseInt(numericSuffixString, 10);
|
||||
return numericSuffix % 2 === 0 ? FEMALE_DEFAULT_AVATAR : MALE_DEFAULT_AVATAR;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,14 @@ export class InMemoryAvatarGenerationRepository implements IAvatarGenerationRepo
|
||||
|
||||
async findLatestByUserId(userId: string): Promise<AvatarGenerationRequest | null> {
|
||||
const userRequests = await this.findByUserId(userId);
|
||||
return userRequests.length > 0 ? userRequests[0] : null;
|
||||
if (userRequests.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const latest = userRequests[0];
|
||||
if (!latest) {
|
||||
return null;
|
||||
}
|
||||
return latest;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
|
||||
@@ -23,11 +23,12 @@ export function createFeedEvents(
|
||||
const completedRaces = races.filter((race) => race.status === 'completed');
|
||||
|
||||
// Focus the global feed around a stable “core” of demo drivers
|
||||
const coreDrivers = faker.helpers.shuffle(drivers).slice(0, 16);
|
||||
const coreDrivers = faker.helpers.shuffle(drivers).slice(0, Math.min(16, drivers.length));
|
||||
|
||||
coreDrivers.forEach((driver, index) => {
|
||||
const league = pickOne(leagues);
|
||||
const race = completedRaces[index % Math.max(1, completedRaces.length)];
|
||||
const raceSource = completedRaces.length > 0 ? completedRaces : races;
|
||||
const race = pickOne(raceSource);
|
||||
const minutesAgo = 10 + index * 5;
|
||||
const baseTimestamp = new Date(now.getTime() - minutesAgo * 60 * 1000);
|
||||
|
||||
@@ -166,23 +167,54 @@ export function buildFriends(
|
||||
drivers: Driver[],
|
||||
memberships: RacingMembership[],
|
||||
): FriendDTO[] {
|
||||
return drivers.map((driver) => ({
|
||||
driverId: driver.id,
|
||||
displayName: driver.name,
|
||||
avatarUrl: getDriverAvatar(driver.id),
|
||||
isOnline: true,
|
||||
lastSeen: new Date(),
|
||||
primaryLeagueId: memberships.find((m) => m.driverId === driver.id)?.leagueId,
|
||||
primaryTeamId: memberships.find((m) => m.driverId === driver.id)?.teamId,
|
||||
}));
|
||||
return drivers.map((driver) => {
|
||||
const membership = memberships.find((m) => m.driverId === driver.id);
|
||||
|
||||
const base: FriendDTO = {
|
||||
driverId: driver.id,
|
||||
displayName: driver.name,
|
||||
avatarUrl: getDriverAvatar(driver.id),
|
||||
isOnline: true,
|
||||
lastSeen: new Date(),
|
||||
};
|
||||
|
||||
const withLeague =
|
||||
membership?.leagueId !== undefined
|
||||
? { ...base, primaryLeagueId: membership.leagueId }
|
||||
: base;
|
||||
|
||||
const withTeam =
|
||||
membership?.teamId !== undefined
|
||||
? { ...withLeague, primaryTeamId: membership.teamId }
|
||||
: withLeague;
|
||||
|
||||
return withTeam;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build top leagues with banner URLs for UI.
|
||||
*/
|
||||
export function buildTopLeagues(leagues: League[]): Array<League & { bannerUrl: string }> {
|
||||
export type LeagueWithBannerDTO = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
ownerId: string;
|
||||
settings: League['settings'];
|
||||
createdAt: Date;
|
||||
socialLinks: League['socialLinks'];
|
||||
bannerUrl: string;
|
||||
};
|
||||
|
||||
export function buildTopLeagues(leagues: League[]): LeagueWithBannerDTO[] {
|
||||
return leagues.map((league) => ({
|
||||
...league,
|
||||
id: league.id,
|
||||
name: league.name,
|
||||
description: league.description,
|
||||
ownerId: league.ownerId,
|
||||
settings: league.settings,
|
||||
createdAt: league.createdAt,
|
||||
socialLinks: league.socialLinks,
|
||||
bannerUrl: getLeagueBanner(league.id),
|
||||
}));
|
||||
}
|
||||
@@ -252,5 +284,9 @@ export function buildLatestResults(
|
||||
* Kept here to avoid importing from core in callers that only care about feed.
|
||||
*/
|
||||
function pickOne<T>(items: readonly T[]): T {
|
||||
return items[Math.floor(faker.number.int({ min: 0, max: items.length - 1 }))];
|
||||
if (items.length === 0) {
|
||||
throw new Error('pickOne: empty items array');
|
||||
}
|
||||
const index = faker.number.int({ min: 0, max: items.length - 1 });
|
||||
return items[index]!;
|
||||
}
|
||||
@@ -47,7 +47,11 @@ export const POINTS_TABLE: Record<number, number> = {
|
||||
};
|
||||
|
||||
export function pickOne<T>(items: readonly T[]): T {
|
||||
return items[Math.floor(faker.number.int({ min: 0, max: items.length - 1 }))];
|
||||
if (items.length === 0) {
|
||||
throw new Error('pickOne: empty items array');
|
||||
}
|
||||
const index = faker.number.int({ min: 0, max: items.length - 1 });
|
||||
return items[index]!;
|
||||
}
|
||||
|
||||
export function createDrivers(count: number): Driver[] {
|
||||
@@ -136,18 +140,31 @@ export function createLeagues(ownerIds: string[]): League[] {
|
||||
websiteUrl: 'https://virtual-touring.example.com',
|
||||
}
|
||||
: undefined;
|
||||
|
||||
leagues.push(
|
||||
League.create({
|
||||
id,
|
||||
name,
|
||||
description: faker.lorem.sentence(),
|
||||
ownerId,
|
||||
settings,
|
||||
createdAt: faker.date.past(),
|
||||
socialLinks,
|
||||
}),
|
||||
);
|
||||
|
||||
if (socialLinks) {
|
||||
leagues.push(
|
||||
League.create({
|
||||
id,
|
||||
name,
|
||||
description: faker.lorem.sentence(),
|
||||
ownerId,
|
||||
settings,
|
||||
createdAt: faker.date.past(),
|
||||
socialLinks,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
leagues.push(
|
||||
League.create({
|
||||
id,
|
||||
name,
|
||||
description: faker.lorem.sentence(),
|
||||
ownerId,
|
||||
settings,
|
||||
createdAt: faker.date.past(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return leagues;
|
||||
@@ -204,11 +221,16 @@ export function createMemberships(
|
||||
? pickOne(leagueTeams)
|
||||
: undefined;
|
||||
|
||||
memberships.push({
|
||||
const membership: RacingMembership = {
|
||||
driverId: driver.id,
|
||||
leagueId: league.id,
|
||||
teamId: team?.id,
|
||||
});
|
||||
};
|
||||
|
||||
if (team) {
|
||||
membership.teamId = team.id;
|
||||
}
|
||||
|
||||
memberships.push(membership);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -354,6 +376,7 @@ export function createFriendships(drivers: Driver[]): Friendship[] {
|
||||
for (let offset = 1; offset <= friendCount; offset++) {
|
||||
const friendIndex = (index + offset) % drivers.length;
|
||||
const friend = drivers[friendIndex];
|
||||
if (!friend) continue;
|
||||
if (friend.id === driver.id) continue;
|
||||
|
||||
friendships.push({
|
||||
|
||||
@@ -334,12 +334,14 @@ export function createSponsorshipRequests(
|
||||
|
||||
// Pending request: Simucube wants to sponsor a driver
|
||||
if (drivers.length > 6) {
|
||||
requests.push(
|
||||
SponsorshipRequest.create({
|
||||
id: 'req-simucube-driver-1',
|
||||
sponsorId: SIMUCUBE_ID,
|
||||
entityType: 'driver',
|
||||
entityId: drivers[5].id,
|
||||
const targetDriver = drivers[5];
|
||||
if (targetDriver) {
|
||||
requests.push(
|
||||
SponsorshipRequest.create({
|
||||
id: 'req-simucube-driver-1',
|
||||
sponsorId: SIMUCUBE_ID,
|
||||
entityType: 'driver',
|
||||
entityId: targetDriver.id,
|
||||
tier: 'main',
|
||||
offeredAmount: Money.create(250, 'USD'),
|
||||
message:
|
||||
@@ -347,23 +349,27 @@ export function createSponsorshipRequests(
|
||||
createdAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000), // 2 days ago
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Pending request: Heusinkveld wants to sponsor a team
|
||||
if (teams.length > 3) {
|
||||
requests.push(
|
||||
SponsorshipRequest.create({
|
||||
id: 'req-heusinkveld-team-1',
|
||||
sponsorId: HEUSINKVELD_ID,
|
||||
entityType: 'team',
|
||||
entityId: teams[2].id,
|
||||
tier: 'main',
|
||||
offeredAmount: Money.create(550, 'USD'),
|
||||
message:
|
||||
'Heusinkveld pedals are known for their precision. We believe your team embodies the same values.',
|
||||
createdAt: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000), // 3 days ago
|
||||
}),
|
||||
);
|
||||
const targetTeam = teams[2];
|
||||
if (targetTeam) {
|
||||
requests.push(
|
||||
SponsorshipRequest.create({
|
||||
id: 'req-heusinkveld-team-1',
|
||||
sponsorId: HEUSINKVELD_ID,
|
||||
entityType: 'team',
|
||||
entityId: targetTeam.id,
|
||||
tier: 'main',
|
||||
offeredAmount: Money.create(550, 'USD'),
|
||||
message:
|
||||
'Heusinkveld pedals are known for their precision. We believe your team embodies the same values.',
|
||||
createdAt: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000), // 3 days ago
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Pending request: Trak Racer wants to sponsor a race
|
||||
@@ -403,12 +409,14 @@ export function createSponsorshipRequests(
|
||||
|
||||
// Already accepted request (for history)
|
||||
if (teams.length > 0) {
|
||||
requests.push(
|
||||
SponsorshipRequest.create({
|
||||
id: 'req-simlab-team-accepted',
|
||||
sponsorId: SIMLAB_ID,
|
||||
entityType: 'team',
|
||||
entityId: teams[0].id,
|
||||
const acceptedTeam = teams[0];
|
||||
if (acceptedTeam) {
|
||||
requests.push(
|
||||
SponsorshipRequest.create({
|
||||
id: 'req-simlab-team-accepted',
|
||||
sponsorId: SIMLAB_ID,
|
||||
entityType: 'team',
|
||||
entityId: acceptedTeam.id,
|
||||
tier: 'secondary',
|
||||
offeredAmount: Money.create(300, 'USD'),
|
||||
message: 'Sim-Lab rigs are the foundation of any competitive setup.',
|
||||
@@ -416,16 +424,19 @@ export function createSponsorshipRequests(
|
||||
createdAt: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000), // 30 days ago
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Already rejected request (for history)
|
||||
if (drivers.length > 10) {
|
||||
requests.push(
|
||||
SponsorshipRequest.create({
|
||||
id: 'req-motionrig-driver-rejected',
|
||||
sponsorId: MOTIONRIG_ID,
|
||||
entityType: 'driver',
|
||||
entityId: drivers[10].id,
|
||||
const rejectedDriver = drivers[10];
|
||||
if (rejectedDriver) {
|
||||
requests.push(
|
||||
SponsorshipRequest.create({
|
||||
id: 'req-motionrig-driver-rejected',
|
||||
sponsorId: MOTIONRIG_ID,
|
||||
entityType: 'driver',
|
||||
entityId: rejectedDriver.id,
|
||||
tier: 'main',
|
||||
offeredAmount: Money.create(150, 'USD'),
|
||||
message: 'Would you like to represent MotionRig Pro?',
|
||||
@@ -433,6 +444,7 @@ export function createSponsorshipRequests(
|
||||
createdAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000), // 20 days ago
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return requests;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
|
||||
Reference in New Issue
Block a user