This repository has no description
4.3 kB
137 lines
1import { err, ok, Result } from 'src/shared/core/Result';
2import { UseCase } from 'src/shared/core/UseCase';
3import { IFollowsRepository } from '../../../domain/repositories/IFollowsRepository';
4import { IIdentityResolutionService } from 'src/modules/atproto/domain/services/IIdentityResolutionService';
5import { IProfileService } from 'src/modules/cards/domain/services/IProfileService';
6import { DIDOrHandle } from 'src/modules/atproto/domain/DIDOrHandle';
7import { FollowTargetType } from '../../../domain/value-objects/FollowTargetType';
8import { User } from '@semble/types';
9import { ProfileEnricher } from 'src/modules/cards/application/services/ProfileEnricher';
10
11export interface GetFollowingUsersQuery {
12 userId: string; // DID or handle
13 callingUserId?: string;
14 page?: number;
15 limit?: number;
16}
17
18export interface GetFollowingUsersResult {
19 users: User[];
20 pagination: {
21 currentPage: number;
22 totalPages: number;
23 totalCount: number;
24 hasMore: boolean;
25 limit: number;
26 };
27}
28
29export class ValidationError extends Error {
30 constructor(message: string) {
31 super(message);
32 this.name = 'ValidationError';
33 }
34}
35
36export class GetFollowingUsersUseCase
37 implements UseCase<GetFollowingUsersQuery, Result<GetFollowingUsersResult>>
38{
39 constructor(
40 private followsRepository: IFollowsRepository,
41 private identityResolver: IIdentityResolutionService,
42 private profileService: IProfileService,
43 ) {}
44
45 async execute(
46 query: GetFollowingUsersQuery,
47 ): Promise<Result<GetFollowingUsersResult>> {
48 // Set defaults
49 const page = query.page || 1;
50 const limit = Math.min(query.limit || 20, 100); // Cap at 100
51
52 // Parse and validate user identifier
53 const identifierResult = DIDOrHandle.create(query.userId);
54 if (identifierResult.isErr()) {
55 return err(new ValidationError('Invalid user identifier'));
56 }
57
58 // Resolve to DID
59 const didResult = await this.identityResolver.resolveToDID(
60 identifierResult.value,
61 );
62 if (didResult.isErr()) {
63 return err(
64 new ValidationError(
65 `Could not resolve user identifier: ${didResult.error.message}`,
66 ),
67 );
68 }
69
70 try {
71 // Get paginated list of users that this user follows
72 const followsResult = await this.followsRepository.getFollowing(
73 didResult.value.value,
74 FollowTargetType.USER,
75 { page, limit },
76 );
77
78 if (followsResult.isErr()) {
79 return err(
80 new Error(
81 `Failed to retrieve following users: ${followsResult.error instanceof Error ? followsResult.error.message : 'Unknown error'}`,
82 ),
83 );
84 }
85
86 const { follows, totalCount } = followsResult.value;
87
88 // Extract unique target IDs (the users being followed)
89 const uniqueTargetIds = Array.from(
90 new Set(follows.map((follow) => follow.targetId)),
91 );
92
93 // Fetch profiles for all followed users using ProfileEnricher
94 const profileEnricher = new ProfileEnricher(this.profileService);
95 const profileMapResult = await profileEnricher.buildProfileMap(
96 uniqueTargetIds,
97 query.callingUserId,
98 {
99 skipFailures: false, // Fail if any profile fetch fails (preserving original behavior)
100 mapToUser: true, // Use full User DTO with isFollowing
101 },
102 );
103
104 if (profileMapResult.isErr()) {
105 return err(
106 new Error(
107 `Failed to fetch user profiles: ${profileMapResult.error.message}`,
108 ),
109 );
110 }
111
112 const profileMap = profileMapResult.value;
113
114 // Build users array in the order of follows (chronological)
115 const users: User[] = follows
116 .map((follow) => profileMap.get(follow.targetId))
117 .filter((user): user is User => user !== undefined);
118
119 return ok({
120 users,
121 pagination: {
122 currentPage: page,
123 totalPages: Math.ceil(totalCount / limit),
124 totalCount,
125 hasMore: page * limit < totalCount,
126 limit,
127 },
128 });
129 } catch (error) {
130 return err(
131 new Error(
132 `Failed to retrieve following users: ${error instanceof Error ? error.message : 'Unknown error'}`,
133 ),
134 );
135 }
136 }
137}