This repository has no description
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}