This repository has no description
3.4 kB
111 lines
1import {
2 IProfileService,
3 UserProfile,
4} from 'src/modules/cards/domain/services/IProfileService';
5import { Result, ok, err } from 'src/shared/core/Result';
6import { IAgentService } from '../../application/IAgentService';
7import { DID } from '../../domain/DID';
8import { AuthenticationError } from 'src/shared/core/AuthenticationError';
9import { IFollowsRepository } from 'src/modules/user/domain/repositories/IFollowsRepository';
10import { FollowTargetType } from 'src/modules/user/domain/value-objects/FollowTargetType';
11
12export class BlueskyProfileService implements IProfileService {
13 constructor(
14 private readonly agentService: IAgentService,
15 private readonly followsRepository: IFollowsRepository,
16 ) {}
17
18 async getProfile(
19 userId: string,
20 callerDid?: string,
21 ): Promise<Result<UserProfile>> {
22 try {
23 let agent;
24
25 if (callerDid) {
26 // Use caller's authenticated agent
27 const didResult = DID.create(callerDid);
28 if (didResult.isErr()) {
29 return err(
30 new Error(`Invalid caller DID: ${didResult.error.message}`),
31 );
32 }
33
34 const agentResult = await this.agentService.getAuthenticatedAgent(
35 didResult.value,
36 );
37 if (agentResult.isErr()) {
38 return err(
39 new AuthenticationError(
40 `Failed to get authenticated agent for BlueskyProfileService: ${agentResult.error.message}`,
41 ),
42 );
43 }
44 agent = agentResult.value;
45 } else {
46 // Fall back to unauthenticated agent for public profiles
47 const agentResult = this.agentService.getUnauthenticatedAgent();
48 if (agentResult.isErr()) {
49 return err(
50 new Error(
51 `Failed to get unauthenticated agent: ${agentResult.error.message}`,
52 ),
53 );
54 }
55 agent = agentResult.value;
56 }
57
58 if (!agent) {
59 return err(new Error('No authenticated agent available'));
60 }
61
62 // Fetch the profile using the ATProto API
63 const profileResult = await agent.getProfile({ actor: userId });
64
65 if (!profileResult.success) {
66 return err(
67 new Error(
68 `Failed to fetch profile ${userId}: ${JSON.stringify(profileResult)}`,
69 ),
70 );
71 }
72
73 const profile = profileResult.data;
74
75 // Map ATProto profile data to our UserProfile interface
76 const userProfile: UserProfile = {
77 id: userId,
78 name: profile.displayName || profile.handle,
79 handle: profile.handle,
80 avatarUrl: profile.avatar,
81 bio: profile.description,
82 };
83
84 // Add follow status if callerId is provided
85 let isFollowing: boolean | undefined = undefined;
86 if (callerDid && callerDid !== userId) {
87 const followResult =
88 await this.followsRepository.findByFollowerAndTarget(
89 callerDid,
90 userId,
91 FollowTargetType.USER,
92 );
93
94 if (followResult.isOk()) {
95 isFollowing = followResult.value !== null;
96 }
97 }
98
99 return ok({
100 ...userProfile,
101 isFollowing,
102 });
103 } catch (error) {
104 return err(
105 new Error(
106 `Error fetching profile: ${error instanceof Error ? error.message : String(error)}`,
107 ),
108 );
109 }
110 }
111}