A social knowledge tool for researchers built on ATProto
at development 7.4 kB view raw
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 shouldFail: boolean = false; 20 private shouldFailUnpublish: boolean = false; 21 private collectionType = 22 new EnvironmentConfigService().getAtProtoCollections().collection; 23 24 private cardCollectionLinkType = 25 new EnvironmentConfigService().getAtProtoCollections().collectionLink; 26 27 async publish( 28 collection: Collection, 29 ): Promise<Result<PublishedRecordId, UseCaseError>> { 30 if (this.shouldFail) { 31 return err( 32 AppError.UnexpectedError.create( 33 new Error('Simulated collection publish failure'), 34 ), 35 ); 36 } 37 38 const collectionId = collection.collectionId.getStringValue(); 39 const fakeDid = process.env.BSKY_DID || 'did:plc:rlknsba2qldjkicxsmni3vyn'; 40 41 // Simulate publishing the collection record itself 42 const fakeCollectionUri = `at://${fakeDid}/${this.collectionType}/${collectionId}`; 43 const fakeCollectionCid = `fake-collection-cid-${collectionId}`; 44 45 const collectionRecord = PublishedRecordId.create({ 46 uri: fakeCollectionUri, 47 cid: fakeCollectionCid, 48 }); 49 50 // Store the published collection for inspection 51 this.publishedCollections.set(collectionId, collection); 52 53 console.log( 54 `[FakeCollectionPublisher] Published collection ${collectionId}`, 55 ); 56 57 return ok(collectionRecord); 58 } 59 60 async publishCardAddedToCollection( 61 card: Card, 62 collection: Collection, 63 curatorId: CuratorId, 64 ): Promise<Result<PublishedRecordId, UseCaseError>> { 65 if (this.shouldFail) { 66 return err( 67 AppError.UnexpectedError.create( 68 new Error('Simulated card-collection link publish failure'), 69 ), 70 ); 71 } 72 73 const collectionId = collection.collectionId.getStringValue(); 74 const cardId = card.cardId.getStringValue(); 75 76 // Simulate publishing a card-collection link 77 const fakeLinkUri = `at://${curatorId.value}/${this.cardCollectionLinkType}/${collectionId}-${cardId}`; 78 const fakeLinkCid = `fake-link-cid-${collectionId}-${cardId}`; 79 80 const linkRecord = PublishedRecordId.create({ 81 uri: fakeLinkUri, 82 cid: fakeLinkCid, 83 }); 84 85 // Store published link for inspection 86 const existingLinks = this.publishedLinks.get(collectionId) || []; 87 existingLinks.push({ 88 cardId, 89 linkRecord, 90 }); 91 this.publishedLinks.set(collectionId, existingLinks); 92 93 console.log( 94 `[FakeCollectionPublisher] Published card ${cardId} added to collection ${collectionId} link at ${fakeLinkUri}`, 95 ); 96 97 return ok(linkRecord); 98 } 99 100 async unpublishCardAddedToCollection( 101 recordId: PublishedRecordId, 102 ): Promise<Result<void, UseCaseError>> { 103 if (this.shouldFailUnpublish) { 104 return err( 105 AppError.UnexpectedError.create( 106 new Error('Simulated card-collection link unpublish failure'), 107 ), 108 ); 109 } 110 111 // Find and remove the link by its published record ID 112 for (const [collectionId, links] of this.publishedLinks.entries()) { 113 const linkIndex = links.findIndex( 114 (link) => link.linkRecord.uri === recordId.uri, 115 ); 116 if (linkIndex !== -1) { 117 const removedLink = links.splice(linkIndex, 1)[0]; 118 this.removedLinks.push({ 119 cardId: removedLink!.cardId, 120 collectionId, 121 }); 122 console.log( 123 `[FakeCollectionPublisher] Unpublished card-collection link ${recordId.uri}`, 124 ); 125 return ok(undefined); 126 } 127 } 128 129 console.warn( 130 `[FakeCollectionPublisher] Card-collection link not found for unpublishing: ${recordId.uri}`, 131 ); 132 return ok(undefined); 133 } 134 135 async unpublish( 136 recordId: PublishedRecordId, 137 ): Promise<Result<void, UseCaseError>> { 138 if (this.shouldFailUnpublish) { 139 return err( 140 AppError.UnexpectedError.create( 141 new Error('Simulated collection unpublish failure'), 142 ), 143 ); 144 } 145 146 // Find and remove the collection by its published record ID 147 for (const [ 148 collectionId, 149 collection, 150 ] of this.publishedCollections.entries()) { 151 if (collection.publishedRecordId?.uri === recordId.uri) { 152 this.publishedCollections.delete(collectionId); 153 this.publishedLinks.delete(collectionId); 154 this.unpublishedCollections.push({ 155 uri: recordId.uri, 156 cid: recordId.cid, 157 }); 158 console.log( 159 `[FakeCollectionPublisher] Unpublished collection ${recordId.uri}`, 160 ); 161 return ok(undefined); 162 } 163 } 164 if (this.publishedCollections.size === 0) { 165 this.unpublishedCollections.push({ 166 uri: recordId.uri, 167 cid: recordId.cid, 168 }); 169 console.log( 170 `[FakeCollectionPublisher] Unpublished collection ${recordId.uri} (not found in published collections)`, 171 ); 172 return ok(undefined); 173 } 174 175 console.warn( 176 `[FakeCollectionPublisher] Collection not found for unpublishing: ${recordId.uri}`, 177 ); 178 return ok(undefined); 179 } 180 181 setShouldFail(shouldFail: boolean): void { 182 this.shouldFail = shouldFail; 183 } 184 185 setShouldFailUnpublish(shouldFailUnpublish: boolean): void { 186 this.shouldFailUnpublish = shouldFailUnpublish; 187 } 188 189 clear(): void { 190 this.publishedCollections.clear(); 191 this.publishedLinks.clear(); 192 this.unpublishedCollections = []; 193 this.removedLinks = []; 194 this.shouldFail = false; 195 this.shouldFailUnpublish = false; 196 } 197 198 getPublishedCollections(): Collection[] { 199 return Array.from(this.publishedCollections.values()); 200 } 201 202 getPublishedLinksForCollection( 203 collectionId: string, 204 ): Array<{ cardId: string; linkRecord: PublishedRecordId }> { 205 return this.publishedLinks.get(collectionId) || []; 206 } 207 208 getUnpublishedCollections(): Array<{ uri: string; cid: string }> { 209 return this.unpublishedCollections; 210 } 211 212 getRemovedLinksForCollection( 213 collectionId: string, 214 ): Array<{ cardId: string; collectionId: string }> { 215 return this.removedLinks.filter( 216 (link) => link.collectionId === collectionId, 217 ); 218 } 219 220 getAllRemovedLinks(): Array<{ cardId: string; collectionId: string }> { 221 return this.removedLinks; 222 } 223 getAllPublishedLinks() { 224 const allLinks: Array<{ cardId: string; linkRecord: PublishedRecordId }> = 225 []; 226 for (const links of this.publishedLinks.values()) { 227 allLinks.push(...links); 228 } 229 return allLinks; 230 } 231}