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 / AddUrlToLibraryUseCase.ts
10 kB 299 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 { 8 CardFactory, 9 IUrlCardInput, 10 INoteCardInput, 11} from '../../../domain/CardFactory'; 12import { CollectionId } from '../../../domain/value-objects/CollectionId'; 13import { CuratorId } from '../../../domain/value-objects/CuratorId'; 14import { IMetadataService } from '../../../domain/services/IMetadataService'; 15import { CardTypeEnum } from '../../../domain/value-objects/CardType'; 16import { URL } from '../../../domain/value-objects/URL'; 17import { CardLibraryService } from '../../../domain/services/CardLibraryService'; 18import { CardCollectionService } from '../../../domain/services/CardCollectionService'; 19import { CardContent } from '../../../domain/value-objects/CardContent'; 20 21export interface AddUrlToLibraryDTO { 22 url: string; 23 note?: string; 24 collectionIds?: string[]; 25 curatorId: string; 26} 27 28export interface AddUrlToLibraryResponseDTO { 29 urlCardId: string; 30 noteCardId?: string; 31} 32 33export class ValidationError extends UseCaseError { 34 constructor(message: string) { 35 super(message); 36 } 37} 38 39export class AddUrlToLibraryUseCase extends BaseUseCase< 40 AddUrlToLibraryDTO, 41 Result<AddUrlToLibraryResponseDTO, ValidationError | AppError.UnexpectedError> 42> { 43 constructor( 44 private cardRepository: ICardRepository, 45 private metadataService: IMetadataService, 46 private cardLibraryService: CardLibraryService, 47 private cardCollectionService: CardCollectionService, 48 eventPublisher: IEventPublisher, 49 ) { 50 super(eventPublisher); 51 } 52 53 async execute( 54 request: AddUrlToLibraryDTO, 55 ): Promise< 56 Result< 57 AddUrlToLibraryResponseDTO, 58 ValidationError | AppError.UnexpectedError 59 > 60 > { 61 try { 62 // Validate and create CuratorId 63 const curatorIdResult = CuratorId.create(request.curatorId); 64 if (curatorIdResult.isErr()) { 65 return err( 66 new ValidationError( 67 `Invalid curator ID: ${curatorIdResult.error.message}`, 68 ), 69 ); 70 } 71 const curatorId = curatorIdResult.value; 72 73 // Validate URL 74 const urlResult = URL.create(request.url); 75 if (urlResult.isErr()) { 76 return err( 77 new ValidationError(`Invalid URL: ${urlResult.error.message}`), 78 ); 79 } 80 const url = urlResult.value; 81 82 // Check if URL card already exists 83 const existingUrlCardResult = 84 await this.cardRepository.findUsersUrlCardByUrl(url, curatorId); 85 if (existingUrlCardResult.isErr()) { 86 return err( 87 AppError.UnexpectedError.create(existingUrlCardResult.error), 88 ); 89 } 90 91 let urlCard = existingUrlCardResult.value; 92 if (!urlCard) { 93 // Fetch metadata for URL 94 const metadataResult = await this.metadataService.fetchMetadata(url); 95 if (metadataResult.isErr()) { 96 return err( 97 new ValidationError( 98 `Failed to fetch metadata: ${metadataResult.error.message}`, 99 ), 100 ); 101 } 102 103 // Create URL card 104 const urlCardInput: IUrlCardInput = { 105 type: CardTypeEnum.URL, 106 url: request.url, 107 metadata: metadataResult.value, 108 }; 109 110 const urlCardResult = CardFactory.create({ 111 curatorId: request.curatorId, 112 cardInput: urlCardInput, 113 }); 114 115 if (urlCardResult.isErr()) { 116 return err(new ValidationError(urlCardResult.error.message)); 117 } 118 119 urlCard = urlCardResult.value; 120 121 // Save URL card 122 const saveUrlCardResult = await this.cardRepository.save(urlCard); 123 if (saveUrlCardResult.isErr()) { 124 return err(AppError.UnexpectedError.create(saveUrlCardResult.error)); 125 } 126 } 127 128 // Add URL card to library using domain service 129 const addUrlCardToLibraryResult = 130 await this.cardLibraryService.addCardToLibrary(urlCard, curatorId); 131 if (addUrlCardToLibraryResult.isErr()) { 132 if ( 133 addUrlCardToLibraryResult.error instanceof AppError.UnexpectedError 134 ) { 135 return err(addUrlCardToLibraryResult.error); 136 } 137 return err( 138 new ValidationError(addUrlCardToLibraryResult.error.message), 139 ); 140 } 141 142 // Update urlCard reference to the one returned by the service 143 urlCard = addUrlCardToLibraryResult.value; 144 145 let noteCard; 146 147 // Handle note card creation or update if note is provided 148 if (request.note) { 149 // Check if note card already exists for this URL and curator 150 const existingNoteCardResult = 151 await this.cardRepository.findUsersNoteCardByUrl(url, curatorId); 152 if (existingNoteCardResult.isErr()) { 153 return err( 154 AppError.UnexpectedError.create(existingNoteCardResult.error), 155 ); 156 } 157 158 noteCard = existingNoteCardResult.value; 159 160 if (noteCard) { 161 // Update existing note card 162 const newContentResult = CardContent.createNoteContent(request.note); 163 if (newContentResult.isErr()) { 164 return err(new ValidationError(newContentResult.error.message)); 165 } 166 167 const updateContentResult = noteCard.updateContent( 168 newContentResult.value, 169 ); 170 if (updateContentResult.isErr()) { 171 return err(new ValidationError(updateContentResult.error.message)); 172 } 173 174 // Save updated note card 175 const saveNoteCardResult = await this.cardRepository.save(noteCard); 176 if (saveNoteCardResult.isErr()) { 177 return err( 178 AppError.UnexpectedError.create(saveNoteCardResult.error), 179 ); 180 } 181 } else { 182 // Create new note card 183 const noteCardInput: INoteCardInput = { 184 type: CardTypeEnum.NOTE, 185 text: request.note, 186 parentCardId: urlCard.cardId.getStringValue(), 187 url: request.url, 188 }; 189 190 const noteCardResult = CardFactory.create({ 191 curatorId: request.curatorId, 192 cardInput: noteCardInput, 193 }); 194 195 if (noteCardResult.isErr()) { 196 return err(new ValidationError(noteCardResult.error.message)); 197 } 198 199 noteCard = noteCardResult.value; 200 201 // Save note card 202 const saveNoteCardResult = await this.cardRepository.save(noteCard); 203 if (saveNoteCardResult.isErr()) { 204 return err( 205 AppError.UnexpectedError.create(saveNoteCardResult.error), 206 ); 207 } 208 209 // Add note card to library using domain service 210 const addNoteCardToLibraryResult = 211 await this.cardLibraryService.addCardToLibrary(noteCard, curatorId); 212 if (addNoteCardToLibraryResult.isErr()) { 213 if ( 214 addNoteCardToLibraryResult.error instanceof 215 AppError.UnexpectedError 216 ) { 217 return err(addNoteCardToLibraryResult.error); 218 } 219 return err( 220 new ValidationError(addNoteCardToLibraryResult.error.message), 221 ); 222 } 223 224 // Update noteCard reference to the one returned by the service 225 noteCard = addNoteCardToLibraryResult.value; 226 } 227 } 228 229 // Handle collection additions if specified 230 if (request.collectionIds && request.collectionIds.length > 0) { 231 // Always add the URL card to collections 232 const cardToAdd = urlCard; 233 234 // Validate and create CollectionIds 235 const collectionIds: CollectionId[] = []; 236 for (const collectionIdStr of request.collectionIds) { 237 const collectionIdResult = 238 CollectionId.createFromString(collectionIdStr); 239 if (collectionIdResult.isErr()) { 240 return err( 241 new ValidationError( 242 `Invalid collection ID: ${collectionIdResult.error.message}`, 243 ), 244 ); 245 } 246 collectionIds.push(collectionIdResult.value); 247 } 248 249 // Add card to collections using domain service 250 const addToCollectionsResult = 251 await this.cardCollectionService.addCardToCollections( 252 cardToAdd, 253 collectionIds, 254 curatorId, 255 ); 256 if (addToCollectionsResult.isErr()) { 257 if ( 258 addToCollectionsResult.error instanceof AppError.UnexpectedError 259 ) { 260 return err(addToCollectionsResult.error); 261 } 262 return err(new ValidationError(addToCollectionsResult.error.message)); 263 } 264 265 // Publish events for all affected collections 266 const updatedCollections = addToCollectionsResult.value; 267 for (const collection of updatedCollections) { 268 const publishResult = 269 await this.publishEventsForAggregate(collection); 270 if (publishResult.isErr()) { 271 console.error( 272 'Failed to publish events for collection:', 273 publishResult.error, 274 ); 275 // Don't fail the operation if event publishing fails 276 } 277 } 278 } 279 280 // Publish events for URL card (events are raised in addToLibrary method) 281 const publishUrlCardResult = 282 await this.publishEventsForAggregate(urlCard); 283 if (publishUrlCardResult.isErr()) { 284 console.error( 285 'Failed to publish events for URL card:', 286 publishUrlCardResult.error, 287 ); 288 // Don't fail the operation if event publishing fails 289 } 290 291 return ok({ 292 urlCardId: urlCard.cardId.getStringValue(), 293 noteCardId: noteCard?.cardId.getStringValue(), 294 }); 295 } catch (error) { 296 return err(AppError.UnexpectedError.create(error)); 297 } 298 } 299}