This repository has no description
0

Configure Feed

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

semble / src / modules / cards / application / services / ProfileEnricher.ts
4.2 kB 138 lines
1import { Result, ok, err } from '../../../../shared/core/Result'; 2import { IProfileService } from '../../domain/services/IProfileService'; 3import { User } from '@semble/types'; 4import { BatchProfileFetcher } from './BatchProfileFetcher'; 5import { ProfileMapper } from '../mappers/ProfileMapper'; 6 7/** 8 * ProfileEnricher - High-level service for enriching items with author profiles 9 * 10 * Provides a simple API to replace the extract → fetch → map pattern 11 * used across all query use cases 12 */ 13export class ProfileEnricher { 14 private batchFetcher: BatchProfileFetcher; 15 16 constructor(private profileService: IProfileService) { 17 this.batchFetcher = new BatchProfileFetcher(profileService); 18 } 19 20 /** 21 * Enrich a collection of items with author profiles 22 * 23 * @param items - Array of items to enrich 24 * @param getAuthorId - Function to extract author ID from each item 25 * @param callingUserId - Optional calling user ID for isFollowing status 26 * @param options - Fetch options (skipFailures, includeFallback) 27 * @returns Result with items enriched with 'author' field 28 * 29 * @example 30 * const enriched = await enricher.enrichWithAuthors( 31 * cards, 32 * (card) => card.authorId, 33 * callingUserId 34 * ); 35 */ 36 async enrichWithAuthors<T extends Record<string, any>>( 37 items: T[], 38 getAuthorId: (item: T) => string, 39 callingUserId?: string, 40 options?: { 41 skipFailures?: boolean; 42 includeFallback?: boolean; 43 mapToUser?: boolean; // If true, map to User (with isFollowing), else use inline profile 44 }, 45 ): Promise<Result<(T & { author: User })[]>> { 46 // Extract unique author IDs 47 const authorIds = Array.from(new Set(items.map(getAuthorId))); 48 49 // Fetch all profiles 50 const profileMapResult = await this.batchFetcher.fetchProfileMap( 51 authorIds, 52 callingUserId, 53 { 54 skipFailures: options?.skipFailures ?? false, 55 includeFallback: options?.includeFallback ?? false, 56 }, 57 ); 58 59 if (profileMapResult.isErr()) { 60 return err(profileMapResult.error); 61 } 62 63 const profileMap = profileMapResult.value; 64 const mapToUser = options?.mapToUser ?? true; 65 66 // Enrich items with author profiles 67 const enrichedItems: (T & { author: User })[] = []; 68 69 for (const item of items) { 70 const authorId = getAuthorId(item); 71 const profile = profileMap.get(authorId); 72 73 if (!profile) { 74 if (options?.skipFailures) { 75 // Skip this item if we're allowing failures 76 continue; 77 } 78 return err(new Error(`Profile not found for author ${authorId}`)); 79 } 80 81 const author = mapToUser 82 ? ProfileMapper.toUser(profile) 83 : (ProfileMapper.toInlineProfile(profile) as User); 84 85 enrichedItems.push({ 86 ...item, 87 author, 88 }); 89 } 90 91 return ok(enrichedItems); 92 } 93 94 /** 95 * Build a profile map for later enrichment 96 * Useful when you need to enrich multiple different collections with the same profiles 97 * 98 * @param userIds - Array of user IDs to fetch 99 * @param callingUserId - Optional calling user ID 100 * @param options - Fetch options 101 * @returns Map of userId -> User (API DTO) 102 */ 103 async buildProfileMap( 104 userIds: string[], 105 callingUserId?: string, 106 options?: { 107 skipFailures?: boolean; 108 includeFallback?: boolean; 109 mapToUser?: boolean; 110 }, 111 ): Promise<Result<Map<string, User>>> { 112 const profileMapResult = await this.batchFetcher.fetchProfileMap( 113 userIds, 114 callingUserId, 115 { 116 skipFailures: options?.skipFailures ?? false, 117 includeFallback: options?.includeFallback ?? false, 118 }, 119 ); 120 121 if (profileMapResult.isErr()) { 122 return err(profileMapResult.error); 123 } 124 125 const profileMap = profileMapResult.value; 126 const mapToUser = options?.mapToUser ?? true; 127 const userMap = new Map<string, User>(); 128 129 for (const [userId, profile] of profileMap.entries()) { 130 const user = mapToUser 131 ? ProfileMapper.toUser(profile) 132 : (ProfileMapper.toInlineProfile(profile) as User); 133 userMap.set(userId, user); 134 } 135 136 return ok(userMap); 137 } 138}