A social knowledge tool for researchers built on ATProto
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}