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