A social knowledge tool for researchers built on ATProto
at main 145 lines 4.8 kB view raw
1import { Result, ok, err } from '../../../../../shared/core/Result'; 2import { UseCase } from '../../../../../shared/core/UseCase'; 3import { UseCaseError } from '../../../../../shared/core/UseCaseError'; 4import { AppError } from '../../../../../shared/core/AppError'; 5import { ICardRepository } from '../../../domain/ICardRepository'; 6import { CardId } from '../../../domain/value-objects/CardId'; 7import { CuratorId } from '../../../domain/value-objects/CuratorId'; 8import { CardLibraryService } from '../../../domain/services/CardLibraryService'; 9import { AuthenticationError } from '../../../../../shared/core/AuthenticationError'; 10 11export interface RemoveCardFromLibraryDTO { 12 cardId: string; 13 curatorId: string; 14} 15 16export interface RemoveCardFromLibraryResponseDTO { 17 cardId: string; 18} 19 20export class ValidationError extends UseCaseError { 21 constructor(message: string) { 22 super(message); 23 } 24} 25 26export class RemoveCardFromLibraryUseCase 27 implements 28 UseCase< 29 RemoveCardFromLibraryDTO, 30 Result< 31 RemoveCardFromLibraryResponseDTO, 32 ValidationError | AuthenticationError | AppError.UnexpectedError 33 > 34 > 35{ 36 constructor( 37 private cardRepository: ICardRepository, 38 private cardLibraryService: CardLibraryService, 39 ) {} 40 41 async execute( 42 request: RemoveCardFromLibraryDTO, 43 ): Promise< 44 Result< 45 RemoveCardFromLibraryResponseDTO, 46 ValidationError | AuthenticationError | AppError.UnexpectedError 47 > 48 > { 49 try { 50 // Validate and create CuratorId 51 const curatorIdResult = CuratorId.create(request.curatorId); 52 if (curatorIdResult.isErr()) { 53 return err( 54 new ValidationError( 55 `Invalid curator ID: ${curatorIdResult.error.message}`, 56 ), 57 ); 58 } 59 const curatorId = curatorIdResult.value; 60 61 // Validate and create CardId 62 const cardIdResult = CardId.createFromString(request.cardId); 63 if (cardIdResult.isErr()) { 64 return err( 65 new ValidationError(`Invalid card ID: ${cardIdResult.error.message}`), 66 ); 67 } 68 const cardId = cardIdResult.value; 69 70 // Find the card 71 const cardResult = await this.cardRepository.findById(cardId); 72 if (cardResult.isErr()) { 73 return err(AppError.UnexpectedError.create(cardResult.error)); 74 } 75 76 const card = cardResult.value; 77 if (!card) { 78 return err(new ValidationError(`Card not found: ${request.cardId}`)); 79 } 80 81 // Remove card from library using domain service (this handles cascading for URL cards) 82 const removeFromLibraryResult = 83 await this.cardLibraryService.removeCardFromLibrary(card, curatorId); 84 if (removeFromLibraryResult.isErr()) { 85 // Propagate authentication errors 86 if (removeFromLibraryResult.error instanceof AuthenticationError) { 87 return err(removeFromLibraryResult.error); 88 } 89 if (removeFromLibraryResult.error instanceof AppError.UnexpectedError) { 90 return err(removeFromLibraryResult.error); 91 } 92 return err(new ValidationError(removeFromLibraryResult.error.message)); 93 } 94 95 const updatedCard = removeFromLibraryResult.value; 96 97 // Handle deletion with proper ordering for URL cards 98 if ( 99 updatedCard.libraryCount === 0 && 100 updatedCard.curatorId.equals(curatorId) 101 ) { 102 if (updatedCard.isUrlCard && updatedCard.url) { 103 // First, delete any associated note card that also has no library memberships 104 const noteCardResult = 105 await this.cardRepository.findUsersNoteCardByUrl( 106 updatedCard.url, 107 curatorId, 108 ); 109 110 if (noteCardResult.isOk() && noteCardResult.value) { 111 const noteCard = noteCardResult.value; 112 if ( 113 noteCard.libraryCount === 0 && 114 noteCard.curatorId.equals(curatorId) 115 ) { 116 // Delete note card first (child before parent) 117 const deleteNoteResult = await this.cardRepository.delete( 118 noteCard.cardId, 119 ); 120 if (deleteNoteResult.isErr()) { 121 return err( 122 AppError.UnexpectedError.create(deleteNoteResult.error), 123 ); 124 } 125 } 126 } 127 } 128 129 // Then delete the main card (URL card or any other card type) 130 const deleteResult = await this.cardRepository.delete( 131 updatedCard.cardId, 132 ); 133 if (deleteResult.isErr()) { 134 return err(AppError.UnexpectedError.create(deleteResult.error)); 135 } 136 } 137 138 return ok({ 139 cardId: card.cardId.getStringValue(), 140 }); 141 } catch (error) { 142 return err(AppError.UnexpectedError.create(error)); 143 } 144 } 145}