This repository has no description
0

Configure Feed

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

semble / src / modules / cards / application / useCases / commands / UpdateUrlCardAssociationsUseCase.ts
12 kB 339 lines
1import { Result, ok, err } from '../../../../../shared/core/Result'; 2import { BaseUseCase } from '../../../../../shared/core/UseCase'; 3import { UseCaseError } from '../../../../../shared/core/UseCaseError'; 4import { AppError } from '../../../../../shared/core/AppError'; 5import { IEventPublisher } from '../../../../../shared/application/events/IEventPublisher'; 6import { ICardRepository } from '../../../domain/ICardRepository'; 7import { INoteCardInput } from '../../../domain/CardFactory'; 8import { CardId } from '../../../domain/value-objects/CardId'; 9import { CollectionId } from '../../../domain/value-objects/CollectionId'; 10import { CuratorId } from '../../../domain/value-objects/CuratorId'; 11import { CardTypeEnum } from '../../../domain/value-objects/CardType'; 12import { URL } from '../../../domain/value-objects/URL'; 13import { CardCollectionService } from '../../../domain/services/CardCollectionService'; 14import { CardContent } from '../../../domain/value-objects/CardContent'; 15import { CardFactory } from '../../../domain/CardFactory'; 16import { CardLibraryService } from '../../../domain/services/CardLibraryService'; 17import { AuthenticationError } from '../../../../../shared/core/AuthenticationError'; 18 19export interface UpdateUrlCardAssociationsDTO { 20 cardId: string; 21 curatorId: string; 22 note?: string; 23 addToCollections?: string[]; 24 removeFromCollections?: string[]; 25} 26 27export interface UpdateUrlCardAssociationsResponseDTO { 28 urlCardId: string; 29 noteCardId?: string; 30 addedToCollections: string[]; 31 removedFromCollections: string[]; 32} 33 34export class ValidationError extends UseCaseError { 35 constructor(message: string) { 36 super(message); 37 } 38} 39 40export class UpdateUrlCardAssociationsUseCase extends BaseUseCase< 41 UpdateUrlCardAssociationsDTO, 42 Result< 43 UpdateUrlCardAssociationsResponseDTO, 44 ValidationError | AuthenticationError | AppError.UnexpectedError 45 > 46> { 47 constructor( 48 private cardRepository: ICardRepository, 49 private cardLibraryService: CardLibraryService, 50 private cardCollectionService: CardCollectionService, 51 eventPublisher: IEventPublisher, 52 ) { 53 super(eventPublisher); 54 } 55 56 async execute( 57 request: UpdateUrlCardAssociationsDTO, 58 ): Promise< 59 Result< 60 UpdateUrlCardAssociationsResponseDTO, 61 ValidationError | AuthenticationError | AppError.UnexpectedError 62 > 63 > { 64 try { 65 // Validate and create CuratorId 66 const curatorIdResult = CuratorId.create(request.curatorId); 67 if (curatorIdResult.isErr()) { 68 return err( 69 new ValidationError( 70 `Invalid curator ID: ${curatorIdResult.error.message}`, 71 ), 72 ); 73 } 74 const curatorId = curatorIdResult.value; 75 76 // Validate and create CardId 77 const cardIdResult = CardId.createFromString(request.cardId); 78 if (cardIdResult.isErr()) { 79 return err( 80 new ValidationError(`Invalid card ID: ${cardIdResult.error.message}`), 81 ); 82 } 83 const cardId = cardIdResult.value; 84 85 // Find the URL card - it must already exist 86 const existingUrlCardResult = await this.cardRepository.findById(cardId); 87 if (existingUrlCardResult.isErr()) { 88 return err( 89 AppError.UnexpectedError.create(existingUrlCardResult.error), 90 ); 91 } 92 93 const urlCard = existingUrlCardResult.value; 94 if (!urlCard) { 95 return err( 96 new ValidationError( 97 'URL card not found. Please add the URL to your library first.', 98 ), 99 ); 100 } 101 102 // Verify it's a URL card 103 if (!urlCard.isUrlCard) { 104 return err( 105 new ValidationError( 106 'Card must be a URL card to update associations.', 107 ), 108 ); 109 } 110 111 // Verify ownership 112 if (!urlCard.curatorId.equals(curatorId)) { 113 return err( 114 new ValidationError( 115 'You do not have permission to update this card.', 116 ), 117 ); 118 } 119 120 // Get the URL from the card for note operations 121 if (!urlCard.url) { 122 return err(new ValidationError('URL card must have a URL property.')); 123 } 124 const url = urlCard.url; 125 126 let noteCard; 127 128 // Handle note updates/creation 129 if (request.note !== undefined) { 130 // Check if note card already exists 131 const existingNoteCardResult = 132 await this.cardRepository.findUsersNoteCardByUrl(url, curatorId); 133 if (existingNoteCardResult.isErr()) { 134 return err( 135 AppError.UnexpectedError.create(existingNoteCardResult.error), 136 ); 137 } 138 139 noteCard = existingNoteCardResult.value; 140 141 if (noteCard) { 142 // Update existing note card 143 const newContentResult = CardContent.createNoteContent(request.note); 144 if (newContentResult.isErr()) { 145 return err(new ValidationError(newContentResult.error.message)); 146 } 147 148 const updateContentResult = noteCard.updateContent( 149 newContentResult.value, 150 ); 151 if (updateContentResult.isErr()) { 152 return err(new ValidationError(updateContentResult.error.message)); 153 } 154 155 // Save updated note card 156 const saveNoteCardResult = await this.cardRepository.save(noteCard); 157 if (saveNoteCardResult.isErr()) { 158 return err( 159 AppError.UnexpectedError.create(saveNoteCardResult.error), 160 ); 161 } 162 } else { 163 // Create new note card 164 const noteCardInput: INoteCardInput = { 165 type: CardTypeEnum.NOTE, 166 text: request.note, 167 parentCardId: urlCard.cardId.getStringValue(), 168 url: url.value, 169 }; 170 171 const noteCardResult = CardFactory.create({ 172 curatorId: request.curatorId, 173 cardInput: noteCardInput, 174 }); 175 176 if (noteCardResult.isErr()) { 177 return err(new ValidationError(noteCardResult.error.message)); 178 } 179 180 noteCard = noteCardResult.value; 181 182 // Save note card 183 const saveNoteCardResult = await this.cardRepository.save(noteCard); 184 if (saveNoteCardResult.isErr()) { 185 return err( 186 AppError.UnexpectedError.create(saveNoteCardResult.error), 187 ); 188 } 189 190 // Add note card to library using domain service 191 const addNoteCardToLibraryResult = 192 await this.cardLibraryService.addCardToLibrary(noteCard, curatorId); 193 if (addNoteCardToLibraryResult.isErr()) { 194 // Propagate authentication errors 195 if ( 196 addNoteCardToLibraryResult.error instanceof AuthenticationError 197 ) { 198 return err(addNoteCardToLibraryResult.error); 199 } 200 if ( 201 addNoteCardToLibraryResult.error instanceof 202 AppError.UnexpectedError 203 ) { 204 return err(addNoteCardToLibraryResult.error); 205 } 206 return err( 207 new ValidationError(addNoteCardToLibraryResult.error.message), 208 ); 209 } 210 211 // Update noteCard reference to the one returned by the service 212 noteCard = addNoteCardToLibraryResult.value; 213 } 214 } 215 216 const addedToCollections: string[] = []; 217 const removedFromCollections: string[] = []; 218 219 // Handle adding to collections 220 if (request.addToCollections && request.addToCollections.length > 0) { 221 const collectionIds: CollectionId[] = []; 222 for (const collectionIdStr of request.addToCollections) { 223 const collectionIdResult = 224 CollectionId.createFromString(collectionIdStr); 225 if (collectionIdResult.isErr()) { 226 return err( 227 new ValidationError( 228 `Invalid collection ID: ${collectionIdResult.error.message}`, 229 ), 230 ); 231 } 232 collectionIds.push(collectionIdResult.value); 233 } 234 235 const addToCollectionsResult = 236 await this.cardCollectionService.addCardToCollections( 237 urlCard, 238 collectionIds, 239 curatorId, 240 ); 241 if (addToCollectionsResult.isErr()) { 242 // Propagate authentication errors 243 if (addToCollectionsResult.error instanceof AuthenticationError) { 244 return err(addToCollectionsResult.error); 245 } 246 if ( 247 addToCollectionsResult.error instanceof AppError.UnexpectedError 248 ) { 249 return err(addToCollectionsResult.error); 250 } 251 return err(new ValidationError(addToCollectionsResult.error.message)); 252 } 253 254 // Publish events for all affected collections 255 const updatedCollections = addToCollectionsResult.value; 256 for (const collection of updatedCollections) { 257 const publishResult = 258 await this.publishEventsForAggregate(collection); 259 if (publishResult.isErr()) { 260 console.error( 261 'Failed to publish events for collection:', 262 publishResult.error, 263 ); 264 // Don't fail the operation if event publishing fails 265 } 266 addedToCollections.push(collection.collectionId.getStringValue()); 267 } 268 } 269 270 // Handle removing from collections 271 if ( 272 request.removeFromCollections && 273 request.removeFromCollections.length > 0 274 ) { 275 const collectionIds: CollectionId[] = []; 276 for (const collectionIdStr of request.removeFromCollections) { 277 const collectionIdResult = 278 CollectionId.createFromString(collectionIdStr); 279 if (collectionIdResult.isErr()) { 280 return err( 281 new ValidationError( 282 `Invalid collection ID: ${collectionIdResult.error.message}`, 283 ), 284 ); 285 } 286 collectionIds.push(collectionIdResult.value); 287 } 288 289 const removeFromCollectionsResult = 290 await this.cardCollectionService.removeCardFromCollections( 291 urlCard, 292 collectionIds, 293 curatorId, 294 ); 295 if (removeFromCollectionsResult.isErr()) { 296 // Propagate authentication errors 297 if ( 298 removeFromCollectionsResult.error instanceof AuthenticationError 299 ) { 300 return err(removeFromCollectionsResult.error); 301 } 302 if ( 303 removeFromCollectionsResult.error instanceof 304 AppError.UnexpectedError 305 ) { 306 return err(removeFromCollectionsResult.error); 307 } 308 return err( 309 new ValidationError(removeFromCollectionsResult.error.message), 310 ); 311 } 312 313 // Publish events for all affected collections 314 const updatedCollections = removeFromCollectionsResult.value; 315 for (const collection of updatedCollections) { 316 const publishResult = 317 await this.publishEventsForAggregate(collection); 318 if (publishResult.isErr()) { 319 console.error( 320 'Failed to publish events for collection:', 321 publishResult.error, 322 ); 323 // Don't fail the operation if event publishing fails 324 } 325 removedFromCollections.push(collection.collectionId.getStringValue()); 326 } 327 } 328 329 return ok({ 330 urlCardId: urlCard.cardId.getStringValue(), 331 noteCardId: noteCard?.cardId.getStringValue(), 332 addedToCollections, 333 removedFromCollections, 334 }); 335 } catch (error) { 336 return err(AppError.UnexpectedError.create(error)); 337 } 338 } 339}