This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / modules / atproto / infrastructure / services / BlueskyProfileService.ts
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}