A social knowledge tool for researchers built on ATProto
at development 2.9 kB view raw
1import { Result, ok, err } from '../../../../shared/core/Result'; 2import { 3 IFeedRepository, 4 FeedQueryOptions, 5 PaginatedFeedResult, 6} from '../../domain/IFeedRepository'; 7import { FeedActivity } from '../../domain/FeedActivity'; 8import { ActivityId } from '../../domain/value-objects/ActivityId'; 9 10export class InMemoryFeedRepository implements IFeedRepository { 11 private static instance: InMemoryFeedRepository | null = null; 12 private activities: FeedActivity[] = []; 13 14 private constructor() {} 15 16 public static getInstance(): InMemoryFeedRepository { 17 if (!InMemoryFeedRepository.instance) { 18 InMemoryFeedRepository.instance = new InMemoryFeedRepository(); 19 } 20 return InMemoryFeedRepository.instance; 21 } 22 23 public static resetInstance(): void { 24 InMemoryFeedRepository.instance = null; 25 } 26 27 async addActivity(activity: FeedActivity): Promise<Result<void>> { 28 try { 29 this.activities.push(activity); 30 // Sort by creation time descending 31 this.activities.sort( 32 (a, b) => b.createdAt.getTime() - a.createdAt.getTime(), 33 ); 34 return ok(undefined); 35 } catch (error) { 36 return err(error as Error); 37 } 38 } 39 40 async getGlobalFeed( 41 options: FeedQueryOptions, 42 ): Promise<Result<PaginatedFeedResult>> { 43 try { 44 const { page, limit, beforeActivityId } = options; 45 let filteredActivities = [...this.activities]; 46 47 // Filter by cursor if provided 48 if (beforeActivityId) { 49 const beforeIndex = filteredActivities.findIndex((activity) => 50 activity.activityId.equals(beforeActivityId), 51 ); 52 if (beforeIndex >= 0) { 53 filteredActivities = filteredActivities.slice(beforeIndex + 1); 54 } 55 } 56 57 // Paginate 58 const offset = (page - 1) * limit; 59 const paginatedActivities = filteredActivities.slice( 60 offset, 61 offset + limit, 62 ); 63 64 const totalCount = this.activities.length; 65 const hasMore = offset + paginatedActivities.length < totalCount; 66 67 let nextCursor: ActivityId | undefined; 68 if (hasMore && paginatedActivities.length > 0) { 69 nextCursor = 70 paginatedActivities[paginatedActivities.length - 1]!.activityId; 71 } 72 73 return ok({ 74 activities: paginatedActivities, 75 totalCount, 76 hasMore, 77 nextCursor, 78 }); 79 } catch (error) { 80 return err(error as Error); 81 } 82 } 83 84 async findById(activityId: ActivityId): Promise<Result<FeedActivity | null>> { 85 try { 86 const activity = this.activities.find((a) => 87 a.activityId.equals(activityId), 88 ); 89 return ok(activity || null); 90 } catch (error) { 91 return err(error as Error); 92 } 93 } 94 95 // Test helper methods 96 clear(): void { 97 this.activities = []; 98 } 99 100 getAll(): FeedActivity[] { 101 return [...this.activities]; 102 } 103}