import type { Driver } from '@core/racing/domain/entities/Driver'; import type { Logger } from '@core/shared/domain/Logger'; import type { FeedRepository } from '@core/social/domain/repositories/FeedRepository'; import type { SocialGraphRepository } from '@core/social/domain/repositories/SocialGraphRepository'; import type { FeedItem } from '@core/social/domain/types/FeedItem'; export type Friendship = { driverId: string; friendId: string; }; export type RacingSeedData = { drivers: Driver[]; friendships: Friendship[]; feedEvents: FeedItem[]; }; export class InMemoryFeedRepository implements FeedRepository { private feedEvents: FeedItem[]; private friendships: Friendship[]; private readonly logger: Logger; constructor(logger: Logger, seed?: RacingSeedData) { this.logger = logger; this.logger.info('InMemoryFeedRepository initialized.'); this.feedEvents = seed?.feedEvents ?? []; this.friendships = seed?.friendships ?? []; } seed(seed: RacingSeedData): void { this.feedEvents = seed.feedEvents; this.friendships = seed.friendships; } async getFeedForDriver(driverId: string, limit?: number): Promise { this.logger.debug(`Getting feed for driver: ${driverId}, limit: ${limit}`); try { 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()); this.logger.info(`Found ${sorted.length} feed items for driver: ${driverId}.`); return typeof limit === 'number' ? sorted.slice(0, limit) : sorted; } catch (error) { this.logger.error(`Error getting feed for driver ${driverId}:`, error as Error); throw error; } } async getGlobalFeed(limit?: number): Promise { this.logger.debug(`Getting global feed, limit: ${limit}`); try { const sorted = this.feedEvents .slice() .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); this.logger.info(`Found ${sorted.length} global feed items.`); return typeof limit === 'number' ? sorted.slice(0, limit) : sorted; } catch (error) { this.logger.error(`Error getting global feed:`, error as Error); throw error; } } } export class InMemorySocialGraphRepository implements SocialGraphRepository { private friendships: Friendship[]; private driversById: Map; private readonly logger: Logger; constructor(logger: Logger, seed?: RacingSeedData) { this.logger = logger; this.logger.info('InMemorySocialGraphRepository initialized.'); this.friendships = seed?.friendships ?? []; this.driversById = new Map((seed?.drivers ?? []).map((d) => [d.id, d])); } seed(seed: RacingSeedData): void { this.friendships = seed.friendships; this.driversById = new Map(seed.drivers.map((d) => [d.id, d])); } async getFriendIds(driverId: string): Promise { this.logger.debug(`Getting friend IDs for driver: ${driverId}`); try { const friendIds = this.friendships .filter((f) => f.driverId === driverId) .map((f) => f.friendId); this.logger.info(`Found ${friendIds.length} friend IDs for driver: ${driverId}.`); return friendIds; } catch (error) { this.logger.error(`Error getting friend IDs for driver ${driverId}:`, error as Error); throw error; } } async getFriends(driverId: string): Promise { this.logger.debug(`Getting friends for driver: ${driverId}`); try { const ids = await this.getFriendIds(driverId); const friends = ids .map((id) => this.driversById.get(id)) .filter((d): d is Driver => Boolean(d)); this.logger.info(`Found ${friends.length} friends for driver: ${driverId}.`); return friends; } catch (error) { this.logger.error(`Error getting friends for driver ${driverId}:`, error as Error); throw error; } } async getSuggestedFriends(driverId: string, limit?: number): Promise { this.logger.debug(`Getting suggested friends for driver: ${driverId}, limit: ${limit}`); try { 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)); const result = typeof limit === 'number' ? drivers.slice(0, limit) : drivers; this.logger.info(`Found ${result.length} suggested friends for driver: ${driverId}.`); return result; } catch (error) { this.logger.error(`Error getting suggested friends for driver ${driverId}:`, error as Error); throw error; } } async clear(): Promise { this.logger.info('[InMemorySocialGraphRepository] Clearing all friendships and drivers'); this.friendships = []; this.driversById.clear(); } }