A social knowledge tool for researchers built on ATProto
1import { CardId } from 'src/modules/cards/domain/value-objects/CardId';
2import { err, ok, Result } from 'src/shared/core/Result';
3import { UseCase } from 'src/shared/core/UseCase';
4import {
5 ICardQueryRepository,
6 UrlCardView,
7 WithCollections,
8} from '../../../domain/ICardQueryRepository';
9import { IProfileService } from '../../../domain/services/IProfileService';
10
11export interface GetUrlCardViewQuery {
12 cardId: string;
13 callerDid?: string;
14}
15
16// Enriched data for the final use case result
17export type UrlCardViewResult = UrlCardView &
18 WithCollections & {
19 libraries: {
20 userId: string;
21 name: string;
22 handle: string;
23 avatarUrl?: string;
24 }[];
25 };
26
27export class ValidationError extends Error {
28 constructor(message: string) {
29 super(message);
30 this.name = 'ValidationError';
31 }
32}
33
34export class CardNotFoundError extends Error {
35 constructor(message: string) {
36 super(message);
37 this.name = 'CardNotFoundError';
38 }
39}
40
41export class GetUrlCardViewUseCase
42 implements UseCase<GetUrlCardViewQuery, Result<UrlCardViewResult>>
43{
44 constructor(
45 private cardQueryRepo: ICardQueryRepository,
46 private profileService: IProfileService,
47 ) {}
48
49 async execute(
50 query: GetUrlCardViewQuery,
51 ): Promise<Result<UrlCardViewResult>> {
52 // Validate card ID
53 const cardIdResult = CardId.createFromString(query.cardId);
54 if (cardIdResult.isErr()) {
55 return err(new ValidationError('Invalid card ID'));
56 }
57
58 try {
59 // Get the URL card view data
60 const cardView = await this.cardQueryRepo.getUrlCardView(query.cardId);
61
62 if (!cardView) {
63 return err(new CardNotFoundError('URL card not found'));
64 }
65
66 // Get profiles for all users in libraries
67 const userIds = cardView.libraries.map((lib) => lib.userId);
68 const profilePromises = userIds.map((userId) =>
69 this.profileService.getProfile(userId, query.callerDid),
70 );
71
72 const profileResults = await Promise.all(profilePromises);
73
74 // Check if any profile fetches failed
75 const failedProfiles = profileResults.filter((result) => result.isErr());
76 if (failedProfiles.length > 0) {
77 const firstError = failedProfiles[0]!.error;
78 return err(
79 new Error(
80 `Failed to fetch user profiles: ${firstError instanceof Error ? firstError.message : 'Unknown error'}`,
81 ),
82 );
83 }
84
85 // Transform to result format with enriched profile data
86 const enrichedLibraries = cardView.libraries.map((lib, index) => {
87 const profileResult = profileResults[index]!;
88 if (profileResult.isErr()) {
89 throw new Error(
90 `Failed to fetch profile for user ${lib.userId}: ${profileResult.error instanceof Error ? profileResult.error.message : 'Unknown error'}`,
91 );
92 }
93 const profile = profileResult.value;
94
95 return {
96 userId: lib.userId,
97 name: profile.name,
98 handle: profile.handle,
99 avatarUrl: profile.avatarUrl,
100 };
101 });
102
103 const result: UrlCardViewResult = {
104 ...cardView,
105 libraries: enrichedLibraries,
106 };
107
108 return ok(result);
109 } catch (error) {
110 return err(
111 new Error(
112 `Failed to retrieve URL card view: ${error instanceof Error ? error.message : 'Unknown error'}`,
113 ),
114 );
115 }
116 }
117}