A social knowledge tool for researchers built on ATProto
1import { Result, ok, err } from '../../../../shared/core/Result';
2import { ICollectionRepository } from '../../domain/ICollectionRepository';
3import { Collection } from '../../domain/Collection';
4import { CollectionId } from '../../domain/value-objects/CollectionId';
5import { CardId } from '../../domain/value-objects/CardId';
6import { CuratorId } from '../../domain/value-objects/CuratorId';
7
8export class InMemoryCollectionRepository implements ICollectionRepository {
9 private collections: Map<string, Collection> = new Map();
10
11 private clone(collection: Collection): Collection {
12 // Simple clone - in a real implementation you'd want proper deep cloning
13 const collectionResult = Collection.create(
14 {
15 authorId: collection.authorId,
16 name: collection.name.value,
17 description: collection.description?.value,
18 accessType: collection.accessType,
19 collaboratorIds: collection.collaboratorIds,
20 cardLinks: collection.cardLinks,
21 cardCount: collection.cardCount,
22 publishedRecordId: collection.publishedRecordId,
23 createdAt: collection.createdAt,
24 updatedAt: collection.updatedAt,
25 },
26 collection.id,
27 );
28
29 if (collectionResult.isErr()) {
30 throw new Error(
31 `Failed to clone collection: ${collectionResult.error.message}`,
32 );
33 }
34
35 return collectionResult.value;
36 }
37
38 async findById(id: CollectionId): Promise<Result<Collection | null>> {
39 try {
40 const collection = this.collections.get(id.getStringValue());
41 return ok(collection ? this.clone(collection) : null);
42 } catch (error) {
43 return err(error as Error);
44 }
45 }
46
47 async findByCuratorId(curatorId: CuratorId): Promise<Result<Collection[]>> {
48 try {
49 const collections = Array.from(this.collections.values()).filter(
50 (collection) =>
51 collection.authorId.value === curatorId.value ||
52 collection.collaboratorIds.some((id) => id.value === curatorId.value),
53 );
54 return ok(collections.map((collection) => this.clone(collection)));
55 } catch (error) {
56 return err(error as Error);
57 }
58 }
59
60 async findByCardId(cardId: CardId): Promise<Result<Collection[]>> {
61 try {
62 const collections = Array.from(this.collections.values()).filter(
63 (collection) =>
64 collection.cardLinks.some(
65 (link) => link.cardId.getStringValue() === cardId.getStringValue(),
66 ),
67 );
68 return ok(collections.map((collection) => this.clone(collection)));
69 } catch (error) {
70 return err(error as Error);
71 }
72 }
73
74 async findByCuratorIdContainingCard(
75 authorId: CuratorId,
76 cardId: CardId,
77 ): Promise<Result<Collection[]>> {
78 try {
79 const collections = Array.from(this.collections.values()).filter(
80 (collection) =>
81 collection.authorId.value === authorId.value &&
82 collection.cardLinks.some(
83 (link) => link.cardId.getStringValue() === cardId.getStringValue(),
84 ),
85 );
86 return ok(collections.map((collection) => this.clone(collection)));
87 } catch (error) {
88 return err(error as Error);
89 }
90 }
91
92 async save(collection: Collection): Promise<Result<void>> {
93 try {
94 this.collections.set(
95 collection.collectionId.getStringValue(),
96 this.clone(collection),
97 );
98 return ok(undefined);
99 } catch (error) {
100 return err(error as Error);
101 }
102 }
103
104 async delete(collectionId: CollectionId): Promise<Result<void>> {
105 try {
106 this.collections.delete(collectionId.getStringValue());
107 return ok(undefined);
108 } catch (error) {
109 return err(error as Error);
110 }
111 }
112
113 // Helper methods for testing
114 public clear(): void {
115 this.collections.clear();
116 }
117
118 public getStoredCollection(id: CollectionId): Collection | undefined {
119 return this.collections.get(id.getStringValue());
120 }
121
122 public getAllCollections(): Collection[] {
123 return Array.from(this.collections.values()).map((collection) =>
124 this.clone(collection),
125 );
126 }
127}