Files
gridpilot.gg/adapters/social/persistence/inmemory/InMemorySocialAndFeed.ts
Marc Mintel 597bb48248
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 4m51s
Contract Testing / contract-snapshot (pull_request) Has been skipped
integration tests
2026-01-22 17:29:06 +01:00

162 lines
5.6 KiB
TypeScript

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<FeedItem[]> {
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<FeedItem[]> {
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<string, Driver>;
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<string[]> {
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<Driver[]> {
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<Driver[]> {
this.logger.debug(`Getting suggested friends for driver: ${driverId}, limit: ${limit}`);
try {
const directFriendIds = new Set(await this.getFriendIds(driverId));
const suggestions = new Map<string, number>();
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<void> {
this.logger.info('[InMemorySocialGraphRepository] Clearing all friendships and drivers');
this.friendships = [];
this.driversById.clear();
}
}