This repository has no description
1import { Result, ok, err } from '../../../../shared/core/Result';
2import { Card } from '../Card';
3import { Collection } from '../Collection';
4import { CuratorId } from '../value-objects/CuratorId';
5import { CollectionId } from '../value-objects/CollectionId';
6import { ICollectionRepository } from '../ICollectionRepository';
7import { ICollectionPublisher } from '../../application/ports/ICollectionPublisher';
8import { AppError } from '../../../../shared/core/AppError';
9import { DomainService } from '../../../../shared/domain/DomainService';
10import { AuthenticationError } from '../../../../shared/core/AuthenticationError';
11
12export class CardCollectionValidationError extends Error {
13 constructor(message: string) {
14 super(message);
15 this.name = 'CardCollectionValidationError';
16 }
17}
18
19export class CardCollectionService implements DomainService {
20 constructor(
21 private collectionRepository: ICollectionRepository,
22 private collectionPublisher: ICollectionPublisher,
23 ) {}
24
25 async addCardToCollection(
26 card: Card,
27 collectionId: CollectionId,
28 curatorId: CuratorId,
29 ): Promise<
30 Result<
31 Collection,
32 | CardCollectionValidationError
33 | AuthenticationError
34 | AppError.UnexpectedError
35 >
36 > {
37 try {
38 // Find the collection
39 const collectionResult =
40 await this.collectionRepository.findById(collectionId);
41 if (collectionResult.isErr()) {
42 return err(AppError.UnexpectedError.create(collectionResult.error));
43 }
44
45 const collection = collectionResult.value;
46 if (!collection) {
47 return err(
48 new CardCollectionValidationError(
49 `Collection not found: ${collectionId.getStringValue()}`,
50 ),
51 );
52 }
53
54 // Add card to collection
55 const addCardResult = collection.addCard(card.cardId, curatorId);
56 if (addCardResult.isErr()) {
57 return err(
58 new CardCollectionValidationError(
59 `Failed to add card to collection: ${addCardResult.error.message}`,
60 ),
61 );
62 }
63
64 // Publish the collection link
65 const publishLinkResult =
66 await this.collectionPublisher.publishCardAddedToCollection(
67 card,
68 collection,
69 curatorId,
70 );
71 if (publishLinkResult.isErr()) {
72 // Propagate authentication errors
73 if (publishLinkResult.error instanceof AuthenticationError) {
74 return err(publishLinkResult.error);
75 }
76 return err(
77 new CardCollectionValidationError(
78 `Failed to publish collection link: ${publishLinkResult.error.message}`,
79 ),
80 );
81 }
82
83 // Mark the card link as published in the collection
84 collection.markCardLinkAsPublished(card.cardId, publishLinkResult.value);
85
86 // Save the updated collection
87 const saveCollectionResult =
88 await this.collectionRepository.save(collection);
89 if (saveCollectionResult.isErr()) {
90 return err(AppError.UnexpectedError.create(saveCollectionResult.error));
91 }
92
93 return ok(collection);
94 } catch (error) {
95 return err(AppError.UnexpectedError.create(error));
96 }
97 }
98
99 async addCardToCollections(
100 card: Card,
101 collectionIds: CollectionId[],
102 curatorId: CuratorId,
103 ): Promise<
104 Result<
105 Collection[],
106 | CardCollectionValidationError
107 | AuthenticationError
108 | AppError.UnexpectedError
109 >
110 > {
111 const updatedCollections: Collection[] = [];
112
113 for (const collectionId of collectionIds) {
114 const result = await this.addCardToCollection(
115 card,
116 collectionId,
117 curatorId,
118 );
119 if (result.isErr()) {
120 return err(result.error);
121 }
122 updatedCollections.push(result.value);
123 }
124 return ok(updatedCollections);
125 }
126
127 async removeCardFromCollection(
128 card: Card,
129 collectionId: CollectionId,
130 curatorId: CuratorId,
131 ): Promise<
132 Result<
133 Collection | null,
134 | CardCollectionValidationError
135 | AuthenticationError
136 | AppError.UnexpectedError
137 >
138 > {
139 try {
140 // Find the collection
141 const collectionResult =
142 await this.collectionRepository.findById(collectionId);
143 if (collectionResult.isErr()) {
144 return err(AppError.UnexpectedError.create(collectionResult.error));
145 }
146
147 const collection = collectionResult.value;
148 if (!collection) {
149 return err(
150 new CardCollectionValidationError(
151 `Collection not found: ${collectionId.getStringValue()}`,
152 ),
153 );
154 }
155
156 // Check if card is in collection
157 const cardLink = collection.cardLinks.find((link) =>
158 link.cardId.equals(card.cardId),
159 );
160 if (!cardLink) {
161 // Card is not in collection, nothing to do
162 return ok(null);
163 }
164
165 // If the card link was published, unpublish it
166 if (cardLink.publishedRecordId) {
167 const unpublishLinkResult =
168 await this.collectionPublisher.unpublishCardAddedToCollection(
169 cardLink.publishedRecordId,
170 );
171 if (unpublishLinkResult.isErr()) {
172 // Propagate authentication errors
173 if (unpublishLinkResult.error instanceof AuthenticationError) {
174 return err(unpublishLinkResult.error);
175 }
176 return err(
177 new CardCollectionValidationError(
178 `Failed to unpublish collection link: ${unpublishLinkResult.error.message}`,
179 ),
180 );
181 }
182 }
183
184 // Remove card from collection
185 const removeCardResult = collection.removeCard(card.cardId, curatorId);
186 if (removeCardResult.isErr()) {
187 return err(
188 new CardCollectionValidationError(
189 `Failed to remove card from collection: ${removeCardResult.error.message}`,
190 ),
191 );
192 }
193
194 // Save the updated collection
195 const saveCollectionResult =
196 await this.collectionRepository.save(collection);
197 if (saveCollectionResult.isErr()) {
198 return err(AppError.UnexpectedError.create(saveCollectionResult.error));
199 }
200
201 return ok(collection);
202 } catch (error) {
203 return err(AppError.UnexpectedError.create(error));
204 }
205 }
206
207 async removeCardFromCollections(
208 card: Card,
209 collectionIds: CollectionId[],
210 curatorId: CuratorId,
211 ): Promise<
212 Result<
213 Collection[],
214 | CardCollectionValidationError
215 | AuthenticationError
216 | AppError.UnexpectedError
217 >
218 > {
219 const updatedCollections: Collection[] = [];
220
221 for (const collectionId of collectionIds) {
222 const result = await this.removeCardFromCollection(
223 card,
224 collectionId,
225 curatorId,
226 );
227 if (result.isErr()) {
228 return err(result.error);
229 }
230 if (result.value !== null) {
231 updatedCollections.push(result.value);
232 }
233 }
234 return ok(updatedCollections);
235 }
236}