A social knowledge tool for researchers built on ATProto
at development 216 lines 6.4 kB view raw
1import { Result, ok, err } from '../../../../shared/core/Result'; 2import { Card } from '../Card'; 3import { Collection } from '../Collection'; 4import { CuratorId } from '../value-objects/CuratorId'; 5import { CollectionId } from '../value-objects/CollectionId'; 6import { ICollectionRepository } from '../ICollectionRepository'; 7import { ICollectionPublisher } from '../../application/ports/ICollectionPublisher'; 8import { AppError } from '../../../../shared/core/AppError'; 9import { DomainService } from '../../../../shared/domain/DomainService'; 10 11export class CardCollectionValidationError extends Error { 12 constructor(message: string) { 13 super(message); 14 this.name = 'CardCollectionValidationError'; 15 } 16} 17 18export class CardCollectionService implements DomainService { 19 constructor( 20 private collectionRepository: ICollectionRepository, 21 private collectionPublisher: ICollectionPublisher, 22 ) {} 23 24 async addCardToCollection( 25 card: Card, 26 collectionId: CollectionId, 27 curatorId: CuratorId, 28 ): Promise< 29 Result<Collection, CardCollectionValidationError | AppError.UnexpectedError> 30 > { 31 try { 32 // Find the collection 33 const collectionResult = 34 await this.collectionRepository.findById(collectionId); 35 if (collectionResult.isErr()) { 36 return err(AppError.UnexpectedError.create(collectionResult.error)); 37 } 38 39 const collection = collectionResult.value; 40 if (!collection) { 41 return err( 42 new CardCollectionValidationError( 43 `Collection not found: ${collectionId.getStringValue()}`, 44 ), 45 ); 46 } 47 48 // Add card to collection 49 const addCardResult = collection.addCard(card.cardId, curatorId); 50 if (addCardResult.isErr()) { 51 return err( 52 new CardCollectionValidationError( 53 `Failed to add card to collection: ${addCardResult.error.message}`, 54 ), 55 ); 56 } 57 58 // Publish the collection link 59 const publishLinkResult = 60 await this.collectionPublisher.publishCardAddedToCollection( 61 card, 62 collection, 63 curatorId, 64 ); 65 if (publishLinkResult.isErr()) { 66 return err( 67 new CardCollectionValidationError( 68 `Failed to publish collection link: ${publishLinkResult.error.message}`, 69 ), 70 ); 71 } 72 73 // Mark the card link as published in the collection 74 collection.markCardLinkAsPublished(card.cardId, publishLinkResult.value); 75 76 // Save the updated collection 77 const saveCollectionResult = 78 await this.collectionRepository.save(collection); 79 if (saveCollectionResult.isErr()) { 80 return err(AppError.UnexpectedError.create(saveCollectionResult.error)); 81 } 82 83 return ok(collection); 84 } catch (error) { 85 return err(AppError.UnexpectedError.create(error)); 86 } 87 } 88 89 async addCardToCollections( 90 card: Card, 91 collectionIds: CollectionId[], 92 curatorId: CuratorId, 93 ): Promise< 94 Result< 95 Collection[], 96 CardCollectionValidationError | AppError.UnexpectedError 97 > 98 > { 99 const updatedCollections: Collection[] = []; 100 101 for (const collectionId of collectionIds) { 102 const result = await this.addCardToCollection( 103 card, 104 collectionId, 105 curatorId, 106 ); 107 if (result.isErr()) { 108 return err(result.error); 109 } 110 updatedCollections.push(result.value); 111 } 112 return ok(updatedCollections); 113 } 114 115 async removeCardFromCollection( 116 card: Card, 117 collectionId: CollectionId, 118 curatorId: CuratorId, 119 ): Promise< 120 Result< 121 Collection | null, 122 CardCollectionValidationError | AppError.UnexpectedError 123 > 124 > { 125 try { 126 // Find the collection 127 const collectionResult = 128 await this.collectionRepository.findById(collectionId); 129 if (collectionResult.isErr()) { 130 return err(AppError.UnexpectedError.create(collectionResult.error)); 131 } 132 133 const collection = collectionResult.value; 134 if (!collection) { 135 return err( 136 new CardCollectionValidationError( 137 `Collection not found: ${collectionId.getStringValue()}`, 138 ), 139 ); 140 } 141 142 // Check if card is in collection 143 const cardLink = collection.cardLinks.find((link) => 144 link.cardId.equals(card.cardId), 145 ); 146 if (!cardLink) { 147 // Card is not in collection, nothing to do 148 return ok(null); 149 } 150 151 // If the card link was published, unpublish it 152 if (cardLink.publishedRecordId) { 153 const unpublishLinkResult = 154 await this.collectionPublisher.unpublishCardAddedToCollection( 155 cardLink.publishedRecordId, 156 ); 157 if (unpublishLinkResult.isErr()) { 158 return err( 159 new CardCollectionValidationError( 160 `Failed to unpublish collection link: ${unpublishLinkResult.error.message}`, 161 ), 162 ); 163 } 164 } 165 166 // Remove card from collection 167 const removeCardResult = collection.removeCard(card.cardId, curatorId); 168 if (removeCardResult.isErr()) { 169 return err( 170 new CardCollectionValidationError( 171 `Failed to remove card from collection: ${removeCardResult.error.message}`, 172 ), 173 ); 174 } 175 176 // Save the updated collection 177 const saveCollectionResult = 178 await this.collectionRepository.save(collection); 179 if (saveCollectionResult.isErr()) { 180 return err(AppError.UnexpectedError.create(saveCollectionResult.error)); 181 } 182 183 return ok(collection); 184 } catch (error) { 185 return err(AppError.UnexpectedError.create(error)); 186 } 187 } 188 189 async removeCardFromCollections( 190 card: Card, 191 collectionIds: CollectionId[], 192 curatorId: CuratorId, 193 ): Promise< 194 Result< 195 Collection[], 196 CardCollectionValidationError | AppError.UnexpectedError 197 > 198 > { 199 const updatedCollections: Collection[] = []; 200 201 for (const collectionId of collectionIds) { 202 const result = await this.removeCardFromCollection( 203 card, 204 collectionId, 205 curatorId, 206 ); 207 if (result.isErr()) { 208 return err(result.error); 209 } 210 if (result.value !== null) { 211 updatedCollections.push(result.value); 212 } 213 } 214 return ok(updatedCollections); 215 } 216}