This repository has no description
1import { ICollectionPublisher } from '../../application/ports/ICollectionPublisher';
2import { Collection } from '../../domain/Collection';
3import { Card } from '../../domain/Card';
4import { PublishedRecordId } from '../../domain/value-objects/PublishedRecordId';
5import { ok, err, Result } from '../../../../shared/core/Result';
6import { UseCaseError } from '../../../../shared/core/UseCaseError';
7import { AppError } from '../../../../shared/core/AppError';
8import { CuratorId } from '../../domain/value-objects/CuratorId';
9import { EnvironmentConfigService } from 'src/shared/infrastructure/config/EnvironmentConfigService';
10
11export class FakeCollectionPublisher implements ICollectionPublisher {
12 private publishedCollections: Map<string, Collection> = new Map();
13 private publishedLinks: Map<
14 string,
15 { cardId: string; linkRecord: PublishedRecordId }[]
16 > = new Map();
17 private unpublishedCollections: Array<{ uri: string; cid: string }> = [];
18 private removedLinks: Array<{ cardId: string; collectionId: string }> = [];
19 private publishedRemovals: Map<
20 string,
21 {
22 cardId: string;
23 collectionId: string;
24 removedLink: PublishedRecordId;
25 removalRecord: PublishedRecordId;
26 }[]
27 > = new Map();
28 private shouldFail: boolean = false;
29 private shouldFailUnpublish: boolean = false;
30 private collectionType =
31 new EnvironmentConfigService().getAtProtoCollections().collection;
32
33 private cardCollectionLinkType =
34 new EnvironmentConfigService().getAtProtoCollections().collectionLink;
35
36 private collectionLinkRemovalType =
37 new EnvironmentConfigService().getAtProtoCollections()
38 .collectionLinkRemoval;
39
40 async publish(
41 collection: Collection,
42 ): Promise<Result<PublishedRecordId, UseCaseError>> {
43 if (this.shouldFail) {
44 return err(
45 AppError.UnexpectedError.create(
46 new Error('Simulated collection publish failure'),
47 ),
48 );
49 }
50
51 const collectionId = collection.collectionId.getStringValue();
52
53 // Use the collection's author DID directly
54 const fakeDid = collection.authorId.value;
55
56 // Simulate publishing the collection record itself
57 const fakeCollectionUri = `at://${fakeDid}/${this.collectionType}/${collectionId}`;
58 const fakeCollectionCid = `fake-collection-cid-${collectionId}`;
59
60 const collectionRecord = PublishedRecordId.create({
61 uri: fakeCollectionUri,
62 cid: fakeCollectionCid,
63 });
64
65 // Store the published collection for inspection
66 this.publishedCollections.set(collectionId, collection);
67
68 console.log(
69 `[FakeCollectionPublisher] Published collection ${collectionId}`,
70 );
71
72 return ok(collectionRecord);
73 }
74
75 async publishCardAddedToCollection(
76 card: Card,
77 collection: Collection,
78 curatorId: CuratorId,
79 ): Promise<Result<PublishedRecordId, UseCaseError>> {
80 if (this.shouldFail) {
81 return err(
82 AppError.UnexpectedError.create(
83 new Error('Simulated card-collection link publish failure'),
84 ),
85 );
86 }
87
88 const collectionId = collection.collectionId.getStringValue();
89 const cardId = card.cardId.getStringValue();
90
91 // Simulate publishing a card-collection link
92 const fakeLinkUri = `at://${curatorId.value}/${this.cardCollectionLinkType}/${collectionId}-${cardId}`;
93 const fakeLinkCid = `fake-link-cid-${collectionId}-${cardId}`;
94
95 const linkRecord = PublishedRecordId.create({
96 uri: fakeLinkUri,
97 cid: fakeLinkCid,
98 });
99
100 // Store published link for inspection
101 const existingLinks = this.publishedLinks.get(collectionId) || [];
102 existingLinks.push({
103 cardId,
104 linkRecord,
105 });
106 this.publishedLinks.set(collectionId, existingLinks);
107
108 console.log(
109 `[FakeCollectionPublisher] Published card ${cardId} added to collection ${collectionId} link at ${fakeLinkUri}`,
110 );
111
112 return ok(linkRecord);
113 }
114
115 async unpublishCardAddedToCollection(
116 recordId: PublishedRecordId,
117 ): Promise<Result<void, UseCaseError>> {
118 if (this.shouldFailUnpublish) {
119 return err(
120 AppError.UnexpectedError.create(
121 new Error('Simulated card-collection link unpublish failure'),
122 ),
123 );
124 }
125
126 // Find and remove the link by its published record ID
127 for (const [collectionId, links] of this.publishedLinks.entries()) {
128 const linkIndex = links.findIndex(
129 (link) => link.linkRecord.uri === recordId.uri,
130 );
131 if (linkIndex !== -1) {
132 const removedLink = links.splice(linkIndex, 1)[0];
133 this.removedLinks.push({
134 cardId: removedLink!.cardId,
135 collectionId,
136 });
137 console.log(
138 `[FakeCollectionPublisher] Unpublished card-collection link ${recordId.uri}`,
139 );
140 return ok(undefined);
141 }
142 }
143
144 console.warn(
145 `[FakeCollectionPublisher] Card-collection link not found for unpublishing: ${recordId.uri}`,
146 );
147 return ok(undefined);
148 }
149
150 async publishCollectionLinkRemoval(
151 card: Card,
152 collection: Collection,
153 curatorId: CuratorId,
154 removedLinkRef: PublishedRecordId,
155 ): Promise<Result<PublishedRecordId, UseCaseError>> {
156 if (this.shouldFail) {
157 return err(
158 AppError.UnexpectedError.create(
159 new Error('Simulated collection link removal publish failure'),
160 ),
161 );
162 }
163
164 const collectionId = collection.collectionId.getStringValue();
165 const cardId = card.cardId.getStringValue();
166
167 // Simulate publishing a collection link removal record
168 const fakeRemovalUri = `at://${curatorId.value}/${this.collectionLinkRemovalType}/${collectionId}-${cardId}-removal`;
169 const fakeRemovalCid = `fake-removal-cid-${collectionId}-${cardId}`;
170
171 const removalRecord = PublishedRecordId.create({
172 uri: fakeRemovalUri,
173 cid: fakeRemovalCid,
174 });
175
176 // Store published removal for inspection
177 const existingRemovals = this.publishedRemovals.get(collectionId) || [];
178 existingRemovals.push({
179 cardId,
180 collectionId,
181 removedLink: removedLinkRef,
182 removalRecord,
183 });
184 this.publishedRemovals.set(collectionId, existingRemovals);
185
186 console.log(
187 `[FakeCollectionPublisher] Published removal of card ${cardId} from collection ${collectionId} at ${fakeRemovalUri}`,
188 );
189
190 return ok(removalRecord);
191 }
192
193 async unpublish(
194 recordId: PublishedRecordId,
195 ): Promise<Result<void, UseCaseError>> {
196 if (this.shouldFailUnpublish) {
197 return err(
198 AppError.UnexpectedError.create(
199 new Error('Simulated collection unpublish failure'),
200 ),
201 );
202 }
203
204 // Find and remove the collection by its published record ID
205 for (const [
206 collectionId,
207 collection,
208 ] of this.publishedCollections.entries()) {
209 if (collection.publishedRecordId?.uri === recordId.uri) {
210 this.publishedCollections.delete(collectionId);
211 this.publishedLinks.delete(collectionId);
212 this.unpublishedCollections.push({
213 uri: recordId.uri,
214 cid: recordId.cid,
215 });
216 console.log(
217 `[FakeCollectionPublisher] Unpublished collection ${recordId.uri}`,
218 );
219 return ok(undefined);
220 }
221 }
222 if (this.publishedCollections.size === 0) {
223 this.unpublishedCollections.push({
224 uri: recordId.uri,
225 cid: recordId.cid,
226 });
227 console.log(
228 `[FakeCollectionPublisher] Unpublished collection ${recordId.uri} (not found in published collections)`,
229 );
230 return ok(undefined);
231 }
232
233 console.warn(
234 `[FakeCollectionPublisher] Collection not found for unpublishing: ${recordId.uri}`,
235 );
236 return ok(undefined);
237 }
238
239 setShouldFail(shouldFail: boolean): void {
240 this.shouldFail = shouldFail;
241 }
242
243 setShouldFailUnpublish(shouldFailUnpublish: boolean): void {
244 this.shouldFailUnpublish = shouldFailUnpublish;
245 }
246
247 clear(): void {
248 this.publishedCollections.clear();
249 this.publishedLinks.clear();
250 this.unpublishedCollections = [];
251 this.removedLinks = [];
252 this.publishedRemovals.clear();
253 this.shouldFail = false;
254 this.shouldFailUnpublish = false;
255 }
256
257 getPublishedCollections(): Collection[] {
258 return Array.from(this.publishedCollections.values());
259 }
260
261 getPublishedLinksForCollection(
262 collectionId: string,
263 ): Array<{ cardId: string; linkRecord: PublishedRecordId }> {
264 return this.publishedLinks.get(collectionId) || [];
265 }
266
267 getUnpublishedCollections(): Array<{ uri: string; cid: string }> {
268 return this.unpublishedCollections;
269 }
270
271 getRemovedLinksForCollection(
272 collectionId: string,
273 ): Array<{ cardId: string; collectionId: string }> {
274 return this.removedLinks.filter(
275 (link) => link.collectionId === collectionId,
276 );
277 }
278
279 getAllRemovedLinks(): Array<{ cardId: string; collectionId: string }> {
280 return this.removedLinks;
281 }
282 getAllPublishedLinks() {
283 const allLinks: Array<{ cardId: string; linkRecord: PublishedRecordId }> =
284 [];
285 for (const links of this.publishedLinks.values()) {
286 allLinks.push(...links);
287 }
288 return allLinks;
289 }
290
291 getPublishedRemovalsForCollection(collectionId: string) {
292 return this.publishedRemovals.get(collectionId) || [];
293 }
294
295 getAllPublishedRemovals() {
296 const allRemovals: Array<{
297 cardId: string;
298 collectionId: string;
299 removedLink: PublishedRecordId;
300 removalRecord: PublishedRecordId;
301 }> = [];
302 for (const removals of this.publishedRemovals.values()) {
303 allRemovals.push(...removals);
304 }
305 return allRemovals;
306 }
307}