This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / modules / cards / domain / services / CardCollectionService.ts
7.8 kB 256 lines
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'; 10import { AuthenticationError } from '../../../../shared/core/AuthenticationError'; 11 12export interface CardCollectionServiceOptions { 13 skipPublishing?: boolean; 14 publishedRecordIds?: Map<string, PublishedRecordId>; // collectionId -> publishedRecordId 15} 16 17export class CardCollectionValidationError extends Error { 18 constructor(message: string) { 19 super(message); 20 this.name = 'CardCollectionValidationError'; 21 } 22} 23 24export class CardCollectionService implements DomainService { 25 constructor( 26 private collectionRepository: ICollectionRepository, 27 private collectionPublisher: ICollectionPublisher, 28 ) {} 29 30 async addCardToCollection( 31 card: Card, 32 collectionId: CollectionId, 33 curatorId: CuratorId, 34 options?: CardCollectionServiceOptions, 35 ): Promise< 36 Result< 37 Collection, 38 | CardCollectionValidationError 39 | AuthenticationError 40 | AppError.UnexpectedError 41 > 42 > { 43 try { 44 // Find the collection 45 const collectionResult = 46 await this.collectionRepository.findById(collectionId); 47 if (collectionResult.isErr()) { 48 return err(AppError.UnexpectedError.create(collectionResult.error)); 49 } 50 51 const collection = collectionResult.value; 52 if (!collection) { 53 return err( 54 new CardCollectionValidationError( 55 `Collection not found: ${collectionId.getStringValue()}`, 56 ), 57 ); 58 } 59 60 // Add card to collection 61 const addCardResult = collection.addCard(card.cardId, curatorId); 62 if (addCardResult.isErr()) { 63 return err( 64 new CardCollectionValidationError( 65 `Failed to add card to collection: ${addCardResult.error.message}`, 66 ), 67 ); 68 } 69 70 // Handle publishing based on options 71 if (options?.skipPublishing && options?.publishedRecordIds) { 72 const publishedRecordId = options.publishedRecordIds.get(collectionId.getStringValue()); 73 if (publishedRecordId) { 74 // Skip publishing and use provided record ID 75 collection.markCardLinkAsPublished(card.cardId, publishedRecordId); 76 } 77 } else { 78 // Publish the collection link normally 79 const publishLinkResult = 80 await this.collectionPublisher.publishCardAddedToCollection( 81 card, 82 collection, 83 curatorId, 84 ); 85 if (publishLinkResult.isErr()) { 86 // Propagate authentication errors 87 if (publishLinkResult.error instanceof AuthenticationError) { 88 return err(publishLinkResult.error); 89 } 90 return err( 91 new CardCollectionValidationError( 92 `Failed to publish collection link: ${publishLinkResult.error.message}`, 93 ), 94 ); 95 } 96 97 // Mark the card link as published in the collection 98 collection.markCardLinkAsPublished(card.cardId, publishLinkResult.value); 99 } 100 101 // Save the updated collection 102 const saveCollectionResult = 103 await this.collectionRepository.save(collection); 104 if (saveCollectionResult.isErr()) { 105 return err(AppError.UnexpectedError.create(saveCollectionResult.error)); 106 } 107 108 return ok(collection); 109 } catch (error) { 110 return err(AppError.UnexpectedError.create(error)); 111 } 112 } 113 114 async addCardToCollections( 115 card: Card, 116 collectionIds: CollectionId[], 117 curatorId: CuratorId, 118 options?: CardCollectionServiceOptions, 119 ): Promise< 120 Result< 121 Collection[], 122 | CardCollectionValidationError 123 | AuthenticationError 124 | AppError.UnexpectedError 125 > 126 > { 127 const updatedCollections: Collection[] = []; 128 129 for (const collectionId of collectionIds) { 130 const result = await this.addCardToCollection( 131 card, 132 collectionId, 133 curatorId, 134 options, 135 ); 136 if (result.isErr()) { 137 return err(result.error); 138 } 139 updatedCollections.push(result.value); 140 } 141 return ok(updatedCollections); 142 } 143 144 async removeCardFromCollection( 145 card: Card, 146 collectionId: CollectionId, 147 curatorId: CuratorId, 148 options?: CardCollectionServiceOptions, 149 ): Promise< 150 Result< 151 Collection | null, 152 | CardCollectionValidationError 153 | AuthenticationError 154 | AppError.UnexpectedError 155 > 156 > { 157 try { 158 // Find the collection 159 const collectionResult = 160 await this.collectionRepository.findById(collectionId); 161 if (collectionResult.isErr()) { 162 return err(AppError.UnexpectedError.create(collectionResult.error)); 163 } 164 165 const collection = collectionResult.value; 166 if (!collection) { 167 return err( 168 new CardCollectionValidationError( 169 `Collection not found: ${collectionId.getStringValue()}`, 170 ), 171 ); 172 } 173 174 // Check if card is in collection 175 const cardLink = collection.cardLinks.find((link) => 176 link.cardId.equals(card.cardId), 177 ); 178 if (!cardLink) { 179 // Card is not in collection, nothing to do 180 return ok(null); 181 } 182 183 // Handle unpublishing based on options 184 if (!options?.skipPublishing && cardLink.publishedRecordId) { 185 const unpublishLinkResult = 186 await this.collectionPublisher.unpublishCardAddedToCollection( 187 cardLink.publishedRecordId, 188 ); 189 if (unpublishLinkResult.isErr()) { 190 // Propagate authentication errors 191 if (unpublishLinkResult.error instanceof AuthenticationError) { 192 return err(unpublishLinkResult.error); 193 } 194 return err( 195 new CardCollectionValidationError( 196 `Failed to unpublish collection link: ${unpublishLinkResult.error.message}`, 197 ), 198 ); 199 } 200 } 201 202 // Remove card from collection 203 const removeCardResult = collection.removeCard(card.cardId, curatorId); 204 if (removeCardResult.isErr()) { 205 return err( 206 new CardCollectionValidationError( 207 `Failed to remove card from collection: ${removeCardResult.error.message}`, 208 ), 209 ); 210 } 211 212 // Save the updated collection 213 const saveCollectionResult = 214 await this.collectionRepository.save(collection); 215 if (saveCollectionResult.isErr()) { 216 return err(AppError.UnexpectedError.create(saveCollectionResult.error)); 217 } 218 219 return ok(collection); 220 } catch (error) { 221 return err(AppError.UnexpectedError.create(error)); 222 } 223 } 224 225 async removeCardFromCollections( 226 card: Card, 227 collectionIds: CollectionId[], 228 curatorId: CuratorId, 229 options?: CardCollectionServiceOptions, 230 ): Promise< 231 Result< 232 Collection[], 233 | CardCollectionValidationError 234 | AuthenticationError 235 | AppError.UnexpectedError 236 > 237 > { 238 const updatedCollections: Collection[] = []; 239 240 for (const collectionId of collectionIds) { 241 const result = await this.removeCardFromCollection( 242 card, 243 collectionId, 244 curatorId, 245 options, 246 ); 247 if (result.isErr()) { 248 return err(result.error); 249 } 250 if (result.value !== null) { 251 updatedCollections.push(result.value); 252 } 253 } 254 return ok(updatedCollections); 255 } 256}