A social knowledge tool for researchers built on ATProto
at development 6.3 kB view raw
1import { Result, ok, err } from '../../../../shared/core/Result'; 2import { Card } from '../Card'; 3import { CuratorId } from '../value-objects/CuratorId'; 4import { ICardPublisher } from '../../application/ports/ICardPublisher'; 5import { ICardRepository } from '../ICardRepository'; 6import { ICollectionRepository } from '../ICollectionRepository'; 7import { AppError } from '../../../../shared/core/AppError'; 8import { DomainService } from '../../../../shared/domain/DomainService'; 9import { CardCollectionService } from './CardCollectionService'; 10import { PublishedRecordId } from '../value-objects/PublishedRecordId'; 11 12export class CardLibraryValidationError extends Error { 13 constructor(message: string) { 14 super(message); 15 this.name = 'CardLibraryValidationError'; 16 } 17} 18 19export class CardLibraryService implements DomainService { 20 constructor( 21 private cardRepository: ICardRepository, 22 private cardPublisher: ICardPublisher, 23 private collectionRepository: ICollectionRepository, 24 private cardCollectionService: CardCollectionService, 25 ) {} 26 27 async addCardToLibrary( 28 card: Card, 29 curatorId: CuratorId, 30 ): Promise< 31 Result<Card, CardLibraryValidationError | AppError.UnexpectedError> 32 > { 33 try { 34 // Check if card is already in curator's library 35 const isInLibrary = card.isInLibrary(curatorId); 36 37 if (isInLibrary) { 38 // Card is already in library, nothing to do 39 return ok(card); 40 } 41 const addToLibResult = card.addToLibrary(curatorId); 42 if (addToLibResult.isErr()) { 43 return err( 44 new CardLibraryValidationError( 45 `Failed to add card to library: ${addToLibResult.error.message}`, 46 ), 47 ); 48 } 49 let parentCardPublishedRecordId: PublishedRecordId | undefined = 50 undefined; 51 52 if (card.parentCardId) { 53 // Ensure parent card is in the curator's library 54 const parentCardResult = await this.cardRepository.findById( 55 card.parentCardId, 56 ); 57 if (parentCardResult.isErr()) { 58 return err( 59 new CardLibraryValidationError( 60 `Failed to fetch parent card: ${parentCardResult.error.message}`, 61 ), 62 ); 63 } 64 const parentCardValue = parentCardResult.value; 65 66 if (!parentCardValue) { 67 return err(new CardLibraryValidationError(`Parent card not found`)); 68 } 69 parentCardPublishedRecordId = parentCardValue.publishedRecordId; 70 } 71 72 // Publish card to library 73 const publishResult = await this.cardPublisher.publishCardToLibrary( 74 card, 75 curatorId, 76 parentCardPublishedRecordId, 77 ); 78 if (publishResult.isErr()) { 79 return err( 80 new CardLibraryValidationError( 81 `Failed to publish card to library: ${publishResult.error.message}`, 82 ), 83 ); 84 } 85 86 // Mark card as published in library 87 const markCardAsPublishedResult = card.markCardInLibraryAsPublished( 88 curatorId, 89 publishResult.value, 90 ); 91 if (markCardAsPublishedResult.isErr()) { 92 return err( 93 new CardLibraryValidationError( 94 `Failed to mark card as published in library: ${markCardAsPublishedResult.error.message}`, 95 ), 96 ); 97 } 98 99 // Save updated card 100 const saveResult = await this.cardRepository.save(card); 101 if (saveResult.isErr()) { 102 return err(AppError.UnexpectedError.create(saveResult.error)); 103 } 104 105 return ok(card); 106 } catch (error) { 107 return err(AppError.UnexpectedError.create(error)); 108 } 109 } 110 111 async removeCardFromLibrary( 112 card: Card, 113 curatorId: CuratorId, 114 ): Promise< 115 Result<Card, CardLibraryValidationError | AppError.UnexpectedError> 116 > { 117 try { 118 // Check if card is in curator's library 119 const isInLibrary = card.isInLibrary(curatorId); 120 121 if (!isInLibrary) { 122 // Card is not in library, nothing to do 123 return ok(card); 124 } 125 126 // Get all collections owned by the curator that contain this card 127 const collectionsResult = 128 await this.collectionRepository.findByCuratorIdContainingCard( 129 curatorId, 130 card.cardId, 131 ); 132 if (collectionsResult.isErr()) { 133 return err(AppError.UnexpectedError.create(collectionsResult.error)); 134 } 135 136 // Remove card from all curator's collections 137 const collections = collectionsResult.value; 138 const collectionIds = collections.map( 139 (collection) => collection.collectionId, 140 ); 141 142 if (collectionIds.length > 0) { 143 const removeFromCollectionsResult = 144 await this.cardCollectionService.removeCardFromCollections( 145 card, 146 collectionIds, 147 curatorId, 148 ); 149 if (removeFromCollectionsResult.isErr()) { 150 return err( 151 new CardLibraryValidationError( 152 `Failed to remove card from collections: ${removeFromCollectionsResult.error.message}`, 153 ), 154 ); 155 } 156 } 157 158 // Get library info to check if it was published 159 const libraryInfo = card.getLibraryInfo(curatorId); 160 if (libraryInfo?.publishedRecordId) { 161 // Unpublish card from library 162 const unpublishResult = 163 await this.cardPublisher.unpublishCardFromLibrary( 164 libraryInfo.publishedRecordId, 165 libraryInfo.curatorId, 166 ); 167 if (unpublishResult.isErr()) { 168 return err( 169 new CardLibraryValidationError( 170 `Failed to unpublish card from library: ${unpublishResult.error.message}`, 171 ), 172 ); 173 } 174 } 175 176 // Remove card from library 177 const removeResult = card.removeFromLibrary(curatorId); 178 if (removeResult.isErr()) { 179 return err( 180 new CardLibraryValidationError( 181 `Failed to remove card from library: ${removeResult.error.message}`, 182 ), 183 ); 184 } 185 186 // Save updated card 187 const saveResult = await this.cardRepository.save(card); 188 if (saveResult.isErr()) { 189 return err(AppError.UnexpectedError.create(saveResult.error)); 190 } 191 192 return ok(card); 193 } catch (error) { 194 return err(AppError.UnexpectedError.create(error)); 195 } 196 } 197}