This repository has no description
9.7 kB
294 lines
1import { Result, ok, err } from 'src/shared/core/Result';
2import { UseCase } from 'src/shared/core/UseCase';
3import { UseCaseError } from 'src/shared/core/UseCaseError';
4import { AppError } from 'src/shared/core/AppError';
5import { IAtUriResolutionService } from '../../../cards/domain/services/IAtUriResolutionService';
6import { PublishedRecordId } from '../../../cards/domain/value-objects/PublishedRecordId';
7import { ATUri } from '../../domain/ATUri';
8import {
9 Record as CardRecord,
10 NoteContent,
11 UrlContent,
12} from '../../infrastructure/lexicon/types/network/cosmik/card';
13import { AddUrlToLibraryUseCase } from '../../../cards/application/useCases/commands/AddUrlToLibraryUseCase';
14import { UpdateUrlCardAssociationsUseCase, OperationContext } from '../../../cards/application/useCases/commands/UpdateUrlCardAssociationsUseCase';
15import { RemoveCardFromLibraryUseCase } from '../../../cards/application/useCases/commands/RemoveCardFromLibraryUseCase';
16
17export interface ProcessCardFirehoseEventDTO {
18 atUri: string;
19 cid: string | null;
20 eventType: 'create' | 'update' | 'delete';
21 record?: CardRecord;
22}
23
24export class ProcessCardFirehoseEventUseCase
25 implements UseCase<ProcessCardFirehoseEventDTO, Result<void>>
26{
27 constructor(
28 private atUriResolutionService: IAtUriResolutionService,
29 private addUrlToLibraryUseCase: AddUrlToLibraryUseCase,
30 private updateUrlCardAssociationsUseCase: UpdateUrlCardAssociationsUseCase,
31 private removeCardFromLibraryUseCase: RemoveCardFromLibraryUseCase,
32 ) {}
33
34 async execute(request: ProcessCardFirehoseEventDTO): Promise<Result<void>> {
35 try {
36 console.log(
37 `Processing card firehose event: ${request.atUri} (${request.eventType})`,
38 );
39
40 switch (request.eventType) {
41 case 'create':
42 return await this.handleCardCreate(request);
43 case 'update':
44 return await this.handleCardUpdate(request);
45 case 'delete':
46 return await this.handleCardDelete(request);
47 }
48
49 return ok(undefined);
50 } catch (error) {
51 return err(AppError.UnexpectedError.create(error));
52 }
53 }
54
55 private async handleCardCreate(
56 request: ProcessCardFirehoseEventDTO,
57 ): Promise<Result<void>> {
58 if (!request.record || !request.cid) {
59 console.warn('Card create event missing record or cid, skipping');
60 return ok(undefined);
61 }
62
63 try {
64 // Parse AT URI to extract curator DID
65 const atUriResult = ATUri.create(request.atUri);
66 if (atUriResult.isErr()) {
67 console.warn(
68 `Invalid AT URI format: ${request.atUri} - ${atUriResult.error.message}`,
69 );
70 return ok(undefined);
71 }
72 const atUri = atUriResult.value;
73 const curatorDid = atUri.did.value;
74
75 const publishedRecordId = PublishedRecordId.create({
76 uri: request.atUri,
77 cid: request.cid,
78 });
79
80 if (request.record.type === 'URL') {
81 // Handle URL card creation
82 const urlContent = request.record.content as UrlContent;
83 if (!urlContent.url) {
84 console.warn(`URL card missing URL: ${request.atUri}`);
85 return ok(undefined);
86 }
87
88 const result = await this.addUrlToLibraryUseCase.execute({
89 url: urlContent.url,
90 curatorId: curatorDid,
91 publishedRecordId: publishedRecordId,
92 });
93
94 if (result.isErr()) {
95 console.warn(`Failed to add URL to library: ${result.error.message}`);
96 return ok(undefined);
97 }
98
99 console.log(
100 `Successfully created URL card from firehose event: ${result.value.urlCardId}`,
101 );
102 } else if (request.record.type === 'NOTE') {
103 // Handle note card creation
104 const noteContent = request.record.content as NoteContent;
105 if (!noteContent.text) {
106 console.warn(`Note card missing text: ${request.atUri}`);
107 return ok(undefined);
108 }
109
110 // Get parent card from parentCard reference
111 if (!request.record.parentCard) {
112 console.warn(
113 `Note card missing parent card reference: ${request.atUri}`,
114 );
115 return ok(undefined);
116 }
117
118 // Resolve parent card ID from AT URI
119 const parentCardId = await this.atUriResolutionService.resolveCardId(
120 request.record.parentCard.uri,
121 );
122 if (parentCardId.isErr() || !parentCardId.value) {
123 console.warn(
124 `Failed to resolve parent card: ${request.record.parentCard.uri}`,
125 );
126 return ok(undefined);
127 }
128
129 const result = await this.updateUrlCardAssociationsUseCase.execute({
130 cardId: parentCardId.value.getStringValue(),
131 curatorId: curatorDid,
132 note: noteContent.text,
133 context: OperationContext.FIREHOSE_EVENT,
134 publishedRecordIds: {
135 noteCard: publishedRecordId,
136 },
137 });
138
139 if (result.isErr()) {
140 console.warn(`Failed to create note card: ${result.error.message}`);
141 return ok(undefined);
142 }
143
144 console.log(
145 `Successfully created note card from firehose event: ${result.value.noteCardId}`,
146 );
147 }
148
149 return ok(undefined);
150 } catch (error) {
151 console.error(`Error processing card create event: ${error}`);
152 return ok(undefined); // Don't fail the firehose processing
153 }
154 }
155
156 private async handleCardUpdate(
157 request: ProcessCardFirehoseEventDTO,
158 ): Promise<Result<void>> {
159 if (!request.record || !request.cid) {
160 console.warn('Card update event missing record or cid, skipping');
161 return ok(undefined);
162 }
163
164 // Only handle NOTE card updates for now
165 if (request.record.type !== 'NOTE') {
166 console.log(`Ignoring update for card type: ${request.record.type}`);
167 return ok(undefined);
168 }
169
170 try {
171 // Parse AT URI to extract curator DID
172 const atUriResult = ATUri.create(request.atUri);
173 if (atUriResult.isErr()) {
174 console.warn(
175 `Invalid AT URI format: ${request.atUri} - ${atUriResult.error.message}`,
176 );
177 return ok(undefined);
178 }
179 const curatorDid = atUriResult.value.did.value;
180
181 const noteContent = request.record.content as NoteContent;
182 if (!noteContent.text) {
183 console.warn(`Note card missing text: ${request.atUri}`);
184 return ok(undefined);
185 }
186
187 // Get parent card from parentCard reference
188 if (!request.record.parentCard) {
189 console.warn(
190 `Note card missing parent card reference: ${request.atUri}`,
191 );
192 return ok(undefined);
193 }
194
195 // Resolve parent card ID from AT URI
196 const parentCardId = await this.atUriResolutionService.resolveCardId(
197 request.record.parentCard.uri,
198 );
199 if (parentCardId.isErr() || !parentCardId.value) {
200 console.warn(
201 `Failed to resolve parent card: ${request.record.parentCard.uri}`,
202 );
203 return ok(undefined);
204 }
205
206 const publishedRecordId = PublishedRecordId.create({
207 uri: request.atUri,
208 cid: request.cid,
209 });
210
211 const result = await this.updateUrlCardAssociationsUseCase.execute({
212 cardId: parentCardId.value.getStringValue(),
213 curatorId: curatorDid,
214 note: noteContent.text,
215 context: OperationContext.FIREHOSE_EVENT,
216 publishedRecordIds: {
217 noteCard: publishedRecordId,
218 },
219 });
220
221 if (result.isErr()) {
222 console.warn(`Failed to update note card: ${result.error.message}`);
223 return ok(undefined);
224 }
225
226 console.log(
227 `Successfully updated note card from firehose event: ${result.value.noteCardId}`,
228 );
229 return ok(undefined);
230 } catch (error) {
231 console.error(`Error processing card update event: ${error}`);
232 return ok(undefined); // Don't fail the firehose processing
233 }
234 }
235
236 private async handleCardDelete(
237 request: ProcessCardFirehoseEventDTO,
238 ): Promise<Result<void>> {
239 try {
240 // Parse AT URI to extract curator DID
241 const atUriResult = ATUri.create(request.atUri);
242 if (atUriResult.isErr()) {
243 console.warn(
244 `Invalid AT URI format: ${request.atUri} - ${atUriResult.error.message}`,
245 );
246 return ok(undefined);
247 }
248 const curatorDid = atUriResult.value.did.value;
249
250 const cardIdResult = await this.atUriResolutionService.resolveCardId(
251 request.atUri,
252 );
253 if (cardIdResult.isErr()) {
254 console.warn(
255 `Failed to resolve card ID: ${cardIdResult.error.message}`,
256 );
257 return ok(undefined);
258 }
259
260 if (cardIdResult.value) {
261 console.log(
262 `Card deleted externally: ${request.atUri}, removing from library`,
263 );
264
265 const publishedRecordId = PublishedRecordId.create({
266 uri: request.atUri,
267 cid: request.cid || 'deleted',
268 });
269
270 const result = await this.removeCardFromLibraryUseCase.execute({
271 cardId: cardIdResult.value.getStringValue(),
272 curatorId: curatorDid,
273 publishedRecordId: publishedRecordId,
274 });
275
276 if (result.isErr()) {
277 console.warn(
278 `Failed to remove card from library: ${result.error.message}`,
279 );
280 return ok(undefined);
281 }
282
283 console.log(
284 `Successfully removed card from library: ${result.value.cardId}`,
285 );
286 }
287
288 return ok(undefined);
289 } catch (error) {
290 console.error(`Error processing card delete event: ${error}`);
291 return ok(undefined);
292 }
293 }
294}