A social knowledge tool for researchers built on ATProto
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 { ICollectionRepository } from '../../../domain/ICollectionRepository';
8import { CardId } from '../../../domain/value-objects/CardId';
9import { CollectionId } from '../../../domain/value-objects/CollectionId';
10import { CuratorId } from '../../../domain/value-objects/CuratorId';
11import { CardCollectionService } from '../../../domain/services/CardCollectionService';
12import { AuthenticationError } from '../../../../../shared/core/AuthenticationError';
13
14export interface AddCardToCollectionDTO {
15 cardId: string;
16 collectionIds: string[];
17 curatorId: string;
18}
19
20export interface AddCardToCollectionResponseDTO {
21 cardId: string;
22}
23
24export class ValidationError extends UseCaseError {
25 constructor(message: string) {
26 super(message);
27 }
28}
29
30export class AddCardToCollectionUseCase extends BaseUseCase<
31 AddCardToCollectionDTO,
32 Result<
33 AddCardToCollectionResponseDTO,
34 ValidationError | AuthenticationError | AppError.UnexpectedError
35 >
36> {
37 constructor(
38 private cardRepository: ICardRepository,
39 private cardCollectionService: CardCollectionService,
40 eventPublisher: IEventPublisher,
41 ) {
42 super(eventPublisher);
43 }
44
45 async execute(
46 request: AddCardToCollectionDTO,
47 ): Promise<
48 Result<
49 AddCardToCollectionResponseDTO,
50 ValidationError | AuthenticationError | AppError.UnexpectedError
51 >
52 > {
53 try {
54 // Validate and create CuratorId
55 const curatorIdResult = CuratorId.create(request.curatorId);
56 if (curatorIdResult.isErr()) {
57 return err(
58 new ValidationError(
59 `Invalid curator ID: ${curatorIdResult.error.message}`,
60 ),
61 );
62 }
63 const curatorId = curatorIdResult.value;
64
65 // Validate and create CardId
66 const cardIdResult = CardId.createFromString(request.cardId);
67 if (cardIdResult.isErr()) {
68 return err(
69 new ValidationError(`Invalid card ID: ${cardIdResult.error.message}`),
70 );
71 }
72 const cardId = cardIdResult.value;
73
74 // Validate and create CollectionIds
75 const collectionIds: CollectionId[] = [];
76 for (const collectionIdStr of request.collectionIds) {
77 const collectionIdResult =
78 CollectionId.createFromString(collectionIdStr);
79 if (collectionIdResult.isErr()) {
80 return err(
81 new ValidationError(
82 `Invalid collection ID: ${collectionIdResult.error.message}`,
83 ),
84 );
85 }
86 collectionIds.push(collectionIdResult.value);
87 }
88
89 // Find the card
90 const cardResult = await this.cardRepository.findById(cardId);
91 if (cardResult.isErr()) {
92 return err(AppError.UnexpectedError.create(cardResult.error));
93 }
94
95 const card = cardResult.value;
96 if (!card) {
97 return err(new ValidationError(`Card not found: ${request.cardId}`));
98 }
99
100 // Add card to collections using domain service
101 const addToCollectionsResult =
102 await this.cardCollectionService.addCardToCollections(
103 card,
104 collectionIds,
105 curatorId,
106 );
107 if (addToCollectionsResult.isErr()) {
108 // Propagate authentication errors
109 if (addToCollectionsResult.error instanceof AuthenticationError) {
110 return err(addToCollectionsResult.error);
111 }
112 if (addToCollectionsResult.error instanceof AppError.UnexpectedError) {
113 return err(addToCollectionsResult.error);
114 }
115 return err(new ValidationError(addToCollectionsResult.error.message));
116 }
117
118 // Publish events for all affected collections
119 const updatedCollections = addToCollectionsResult.value;
120 for (const collection of updatedCollections) {
121 const publishResult = await this.publishEventsForAggregate(collection);
122 if (publishResult.isErr()) {
123 console.error(
124 'Failed to publish events for collection:',
125 publishResult.error,
126 );
127 // Don't fail the operation if event publishing fails
128 }
129 }
130
131 return ok({
132 cardId: card.cardId.getStringValue(),
133 });
134 } catch (error) {
135 return err(AppError.UnexpectedError.create(error));
136 }
137 }
138}