import type { Driver } from '@gridpilot/racing/domain/entities/Driver'; import type { FeedItem } from '@gridpilot/social/domain/types/FeedItem'; import type { IFeedRepository } from '@gridpilot/social/domain/repositories/IFeedRepository'; import type { ISocialGraphRepository } from '@gridpilot/social/domain/repositories/ISocialGraphRepository'; export type Friendship = { driverId: string; friendId: string; }; export type RacingSeedData = { drivers: Driver[]; friendships: Friendship[]; feedEvents: FeedItem[]; }; export class InMemoryFeedRepository implements IFeedRepository { private readonly feedEvents: FeedItem[]; private readonly friendships: Friendship[]; private readonly driversById: Map; constructor(seed: RacingSeedData) { this.feedEvents = seed.feedEvents; this.friendships = seed.friendships; this.driversById = new Map(seed.drivers.map((d) => [d.id, d])); } async getFeedForDriver(driverId: string, limit?: number): Promise { const friendIds = new Set( this.friendships .filter((f) => f.driverId === driverId) .map((f) => f.friendId), ); const items = this.feedEvents.filter((item) => { if (item.actorDriverId && friendIds.has(item.actorDriverId)) { return true; } return false; }); const sorted = items .slice() .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); return typeof limit === 'number' ? sorted.slice(0, limit) : sorted; } async getGlobalFeed(limit?: number): Promise { const sorted = this.feedEvents .slice() .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); return typeof limit === 'number' ? sorted.slice(0, limit) : sorted; } } export class InMemorySocialGraphRepository implements ISocialGraphRepository { private readonly friendships: Friendship[]; private readonly driversById: Map; constructor(seed: RacingSeedData) { this.friendships = seed.friendships; this.driversById = new Map(seed.drivers.map((d) => [d.id, d])); } async getFriendIds(driverId: string): Promise { return this.friendships .filter((f) => f.driverId === driverId) .map((f) => f.friendId); } async getFriends(driverId: string): Promise { const ids = await this.getFriendIds(driverId); return ids .map((id) => this.driversById.get(id)) .filter((d): d is Driver => Boolean(d)); } async getSuggestedFriends(driverId: string, limit?: number): Promise { const directFriendIds = new Set(await this.getFriendIds(driverId)); const suggestions = new Map(); for (const friendship of this.friendships) { if (!directFriendIds.has(friendship.driverId)) continue; const friendOfFriendId = friendship.friendId; if (friendOfFriendId === driverId) continue; if (directFriendIds.has(friendOfFriendId)) continue; suggestions.set(friendOfFriendId, (suggestions.get(friendOfFriendId) ?? 0) + 1); } const rankedIds = Array.from(suggestions.entries()) .sort((a, b) => b[1] - a[1]) .map(([id]) => id); const drivers = rankedIds .map((id) => this.driversById.get(id)) .filter((d): d is Driver => Boolean(d)); if (typeof limit === 'number') { return drivers.slice(0, limit); } return drivers; } }