A social knowledge tool for researchers built on ATProto
at main 137 lines 3.9 kB view raw
1import { Result, ok, err } from '../../../../shared/core/Result'; 2import { ICardRepository } from '../../domain/ICardRepository'; 3import { Card } from '../../domain/Card'; 4import { CardId } from '../../domain/value-objects/CardId'; 5import { CuratorId } from '../../domain/value-objects/CuratorId'; 6import { URL } from '../../domain/value-objects/URL'; 7 8export class InMemoryCardRepository implements ICardRepository { 9 private static instance: InMemoryCardRepository; 10 private cards: Map<string, Card> = new Map(); 11 private shouldFail: boolean = false; 12 private shouldFailSave: boolean = false; 13 14 private constructor() {} 15 16 public static getInstance(): InMemoryCardRepository { 17 if (!InMemoryCardRepository.instance) { 18 InMemoryCardRepository.instance = new InMemoryCardRepository(); 19 } 20 return InMemoryCardRepository.instance; 21 } 22 23 private clone(card: Card): Card { 24 // Simple clone - in a real implementation you'd want proper deep cloning 25 const cardResult = Card.create( 26 { 27 curatorId: card.props.curatorId, 28 type: card.type, 29 content: card.content, 30 parentCardId: card.parentCardId, 31 url: card.url, 32 publishedRecordId: card.publishedRecordId, 33 libraryMemberships: card.libraryMemberships, 34 libraryCount: card.libraryCount, 35 createdAt: card.createdAt, 36 updatedAt: card.updatedAt, 37 }, 38 card.id, 39 ); 40 41 if (cardResult.isErr()) { 42 throw new Error(`Failed to clone card: ${cardResult.error.message}`); 43 } 44 45 return cardResult.value; 46 } 47 48 async findById(id: CardId): Promise<Result<Card | null>> { 49 if (this.shouldFail) { 50 return err(new Error('Simulated find failure')); 51 } 52 try { 53 const card = this.cards.get(id.getStringValue()); 54 return ok(card ? this.clone(card) : null); 55 } catch (error) { 56 return err(error as Error); 57 } 58 } 59 60 async findUsersUrlCardByUrl( 61 url: URL, 62 curatorId: CuratorId, 63 ): Promise<Result<Card | null>> { 64 try { 65 const card = Array.from(this.cards.values()).find( 66 (card) => 67 card.type.value === 'URL' && 68 card.url?.value === url.value && 69 card.props.curatorId.equals(curatorId), 70 ); 71 return ok(card ? this.clone(card) : null); 72 } catch (error) { 73 return err(error as Error); 74 } 75 } 76 77 async findUsersNoteCardByUrl( 78 url: URL, 79 curatorId: CuratorId, 80 ): Promise<Result<Card | null>> { 81 try { 82 const card = Array.from(this.cards.values()).find( 83 (card) => 84 card.type.value === 'NOTE' && 85 card.url?.value === url.value && 86 card.props.curatorId.equals(curatorId), 87 ); 88 return ok(card ? this.clone(card) : null); 89 } catch (error) { 90 return err(error as Error); 91 } 92 } 93 94 async save(card: Card): Promise<Result<void>> { 95 if (this.shouldFailSave || this.shouldFail) { 96 return err(new Error('Simulated save failure')); 97 } 98 try { 99 this.cards.set(card.cardId.getStringValue(), this.clone(card)); 100 return ok(undefined); 101 } catch (error) { 102 return err(error as Error); 103 } 104 } 105 106 async delete(cardId: CardId): Promise<Result<void>> { 107 try { 108 this.cards.delete(cardId.getStringValue()); 109 return ok(undefined); 110 } catch (error) { 111 return err(error as Error); 112 } 113 } 114 115 setShouldFail(shouldFail: boolean): void { 116 this.shouldFail = shouldFail; 117 } 118 119 setShouldFailSave(shouldFailSave: boolean): void { 120 this.shouldFailSave = shouldFailSave; 121 } 122 123 // Helper methods for testing 124 public clear(): void { 125 this.cards.clear(); 126 this.shouldFail = false; 127 this.shouldFailSave = false; 128 } 129 130 public getStoredCard(id: CardId): Card | undefined { 131 return this.cards.get(id.getStringValue()); 132 } 133 134 public getAllCards(): Card[] { 135 return Array.from(this.cards.values()).map((card) => this.clone(card)); 136 } 137}