This repository has no description
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';
17
18export interface UpdateUrlCardAssociationsDTO {
19 cardId: string;
20 curatorId: string;
21 note?: string;
22 addToCollections?: string[];
23 removeFromCollections?: string[];
24}
25
26export interface UpdateUrlCardAssociationsResponseDTO {
27 urlCardId: string;
28 noteCardId?: string;
29 addedToCollections: string[];
30 removedFromCollections: string[];
31}
32
33export class ValidationError extends UseCaseError {
34 constructor(message: string) {
35 super(message);
36 }
37}
38
39export class UpdateUrlCardAssociationsUseCase extends BaseUseCase<
40 UpdateUrlCardAssociationsDTO,
41 Result<
42 UpdateUrlCardAssociationsResponseDTO,
43 ValidationError | AppError.UnexpectedError
44 >
45> {
46 constructor(
47 private cardRepository: ICardRepository,
48 private cardLibraryService: CardLibraryService,
49 private cardCollectionService: CardCollectionService,
50 eventPublisher: IEventPublisher,
51 ) {
52 super(eventPublisher);
53 }
54
55 async execute(
56 request: UpdateUrlCardAssociationsDTO,
57 ): Promise<
58 Result<
59 UpdateUrlCardAssociationsResponseDTO,
60 ValidationError | AppError.UnexpectedError
61 >
62 > {
63 try {
64 // Validate and create CuratorId
65 const curatorIdResult = CuratorId.create(request.curatorId);
66 if (curatorIdResult.isErr()) {
67 return err(
68 new ValidationError(
69 `Invalid curator ID: ${curatorIdResult.error.message}`,
70 ),
71 );
72 }
73 const curatorId = curatorIdResult.value;
74
75 // Validate and create CardId
76 const cardIdResult = CardId.createFromString(request.cardId);
77 if (cardIdResult.isErr()) {
78 return err(
79 new ValidationError(
80 `Invalid card ID: ${cardIdResult.error.message}`,
81 ),
82 );
83 }
84 const cardId = cardIdResult.value;
85
86 // Find the URL card - it must already exist
87 const existingUrlCardResult = await this.cardRepository.findById(cardId);
88 if (existingUrlCardResult.isErr()) {
89 return err(
90 AppError.UnexpectedError.create(existingUrlCardResult.error),
91 );
92 }
93
94 const urlCard = existingUrlCardResult.value;
95 if (!urlCard) {
96 return err(
97 new ValidationError(
98 'URL card not found. Please add the URL to your library first.',
99 ),
100 );
101 }
102
103 // Verify it's a URL card
104 if (!urlCard.isUrlCard) {
105 return err(
106 new ValidationError('Card must be a URL card to update associations.'),
107 );
108 }
109
110 // Verify ownership
111 if (!urlCard.curatorId.equals(curatorId)) {
112 return err(
113 new ValidationError('You do not have permission to update this card.'),
114 );
115 }
116
117 // Get the URL from the card for note operations
118 if (!urlCard.url) {
119 return err(
120 new ValidationError('URL card must have a URL property.'),
121 );
122 }
123 const url = urlCard.url;
124
125 let noteCard;
126
127 // Handle note updates/creation
128 if (request.note !== undefined) {
129 // Check if note card already exists
130 const existingNoteCardResult =
131 await this.cardRepository.findUsersNoteCardByUrl(url, curatorId);
132 if (existingNoteCardResult.isErr()) {
133 return err(
134 AppError.UnexpectedError.create(existingNoteCardResult.error),
135 );
136 }
137
138 noteCard = existingNoteCardResult.value;
139
140 if (noteCard) {
141 // Update existing note card
142 const newContentResult = CardContent.createNoteContent(request.note);
143 if (newContentResult.isErr()) {
144 return err(new ValidationError(newContentResult.error.message));
145 }
146
147 const updateContentResult = noteCard.updateContent(
148 newContentResult.value,
149 );
150 if (updateContentResult.isErr()) {
151 return err(new ValidationError(updateContentResult.error.message));
152 }
153
154 // Save updated note card
155 const saveNoteCardResult =
156 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 if (
195 addNoteCardToLibraryResult.error instanceof
196 AppError.UnexpectedError
197 ) {
198 return err(addNoteCardToLibraryResult.error);
199 }
200 return err(
201 new ValidationError(addNoteCardToLibraryResult.error.message),
202 );
203 }
204
205 // Update noteCard reference to the one returned by the service
206 noteCard = addNoteCardToLibraryResult.value;
207 }
208 }
209
210 const addedToCollections: string[] = [];
211 const removedFromCollections: string[] = [];
212
213 // Handle adding to collections
214 if (request.addToCollections && request.addToCollections.length > 0) {
215 const collectionIds: CollectionId[] = [];
216 for (const collectionIdStr of request.addToCollections) {
217 const collectionIdResult =
218 CollectionId.createFromString(collectionIdStr);
219 if (collectionIdResult.isErr()) {
220 return err(
221 new ValidationError(
222 `Invalid collection ID: ${collectionIdResult.error.message}`,
223 ),
224 );
225 }
226 collectionIds.push(collectionIdResult.value);
227 }
228
229 const addToCollectionsResult =
230 await this.cardCollectionService.addCardToCollections(
231 urlCard,
232 collectionIds,
233 curatorId,
234 );
235 if (addToCollectionsResult.isErr()) {
236 if (
237 addToCollectionsResult.error instanceof AppError.UnexpectedError
238 ) {
239 return err(addToCollectionsResult.error);
240 }
241 return err(new ValidationError(addToCollectionsResult.error.message));
242 }
243
244 // Publish events for all affected collections
245 const updatedCollections = addToCollectionsResult.value;
246 for (const collection of updatedCollections) {
247 const publishResult =
248 await this.publishEventsForAggregate(collection);
249 if (publishResult.isErr()) {
250 console.error(
251 'Failed to publish events for collection:',
252 publishResult.error,
253 );
254 // Don't fail the operation if event publishing fails
255 }
256 addedToCollections.push(collection.collectionId.getStringValue());
257 }
258 }
259
260 // Handle removing from collections
261 if (
262 request.removeFromCollections &&
263 request.removeFromCollections.length > 0
264 ) {
265 const collectionIds: CollectionId[] = [];
266 for (const collectionIdStr of request.removeFromCollections) {
267 const collectionIdResult =
268 CollectionId.createFromString(collectionIdStr);
269 if (collectionIdResult.isErr()) {
270 return err(
271 new ValidationError(
272 `Invalid collection ID: ${collectionIdResult.error.message}`,
273 ),
274 );
275 }
276 collectionIds.push(collectionIdResult.value);
277 }
278
279 const removeFromCollectionsResult =
280 await this.cardCollectionService.removeCardFromCollections(
281 urlCard,
282 collectionIds,
283 curatorId,
284 );
285 if (removeFromCollectionsResult.isErr()) {
286 if (
287 removeFromCollectionsResult.error instanceof AppError.UnexpectedError
288 ) {
289 return err(removeFromCollectionsResult.error);
290 }
291 return err(
292 new ValidationError(removeFromCollectionsResult.error.message),
293 );
294 }
295
296 // Publish events for all affected collections
297 const updatedCollections = removeFromCollectionsResult.value;
298 for (const collection of updatedCollections) {
299 const publishResult =
300 await this.publishEventsForAggregate(collection);
301 if (publishResult.isErr()) {
302 console.error(
303 'Failed to publish events for collection:',
304 publishResult.error,
305 );
306 // Don't fail the operation if event publishing fails
307 }
308 removedFromCollections.push(collection.collectionId.getStringValue());
309 }
310 }
311
312 return ok({
313 urlCardId: urlCard.cardId.getStringValue(),
314 noteCardId: noteCard?.cardId.getStringValue(),
315 addedToCollections,
316 removedFromCollections,
317 });
318 } catch (error) {
319 return err(AppError.UnexpectedError.create(error));
320 }
321 }
322}