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 / useCases / queries / GetLibrariesForUrlUseCase.ts
5.1 kB 171 lines
1import { Result, ok, err } from '../../../../../shared/core/Result'; 2import { UseCase } from '../../../../../shared/core/UseCase'; 3import { 4 ICardQueryRepository, 5 CardSortField, 6 SortOrder, 7} from '../../../domain/ICardQueryRepository'; 8import { URL } from '../../../domain/value-objects/URL'; 9import { IProfileService } from '../../../domain/services/IProfileService'; 10import { 11 UserProfileDTO, 12 UrlCardDTO, 13 PaginationDTO, 14 CardSortingDTO, 15} from '@semble/types'; 16import { ProfileEnricher } from '../../services/ProfileEnricher'; 17 18export interface GetLibrariesForUrlQuery { 19 url: string; 20 callingUserId?: string; 21 page?: number; 22 limit?: number; 23 sortBy?: CardSortField; 24 sortOrder?: SortOrder; 25} 26 27export interface LibraryForUrlDTO { 28 user: UserProfileDTO; 29 card: UrlCardDTO; 30} 31 32export interface GetLibrariesForUrlResult { 33 libraries: LibraryForUrlDTO[]; 34 pagination: PaginationDTO; 35 sorting: CardSortingDTO; 36} 37 38export class ValidationError extends Error { 39 constructor(message: string) { 40 super(message); 41 this.name = 'ValidationError'; 42 } 43} 44 45export class GetLibrariesForUrlUseCase 46 implements UseCase<GetLibrariesForUrlQuery, Result<GetLibrariesForUrlResult>> 47{ 48 constructor( 49 private cardQueryRepo: ICardQueryRepository, 50 private profileService: IProfileService, 51 ) {} 52 53 async execute( 54 query: GetLibrariesForUrlQuery, 55 ): Promise<Result<GetLibrariesForUrlResult>> { 56 // Validate URL 57 const urlResult = URL.create(query.url); 58 if (urlResult.isErr()) { 59 return err( 60 new ValidationError(`Invalid URL: ${urlResult.error.message}`), 61 ); 62 } 63 64 // Set defaults 65 const page = query.page || 1; 66 const limit = Math.min(query.limit || 20, 100); // Cap at 100 67 const sortBy = query.sortBy || CardSortField.CREATED_AT; 68 const sortOrder = query.sortOrder || SortOrder.DESC; 69 70 try { 71 // Execute query to get libraries with full card data 72 const result = await this.cardQueryRepo.getLibrariesForUrl( 73 urlResult.value.value, 74 { 75 page, 76 limit, 77 sortBy, 78 sortOrder, 79 }, 80 ); 81 82 // Enrich with user profiles using ProfileEnricher 83 const uniqueUserIds = Array.from( 84 new Set(result.items.map((item) => item.userId)), 85 ); 86 87 const profileEnricher = new ProfileEnricher(this.profileService); 88 const profileMapResult = await profileEnricher.buildProfileMap( 89 uniqueUserIds, 90 query.callingUserId, 91 { 92 skipFailures: false, // Fail if any profile fetch fails (preserving original behavior) 93 mapToUser: false, // Use inline profile (without isFollowing) 94 }, 95 ); 96 97 if (profileMapResult.isErr()) { 98 return err( 99 new Error( 100 `Failed to fetch user profiles: ${profileMapResult.error.message}`, 101 ), 102 ); 103 } 104 105 const profileMap = profileMapResult.value; 106 107 // Map items with enriched user data and card data 108 const enrichedLibraries: LibraryForUrlDTO[] = result.items.map((item) => { 109 const user = profileMap.get(item.userId); 110 if (!user) { 111 throw new Error(`Profile not found for user ${item.userId}`); 112 } 113 114 // Build card object 115 // Note: userId is the card author (it's their card in their library) 116 const card: UrlCardDTO = { 117 id: item.card.id, 118 type: 'URL', 119 url: item.card.url, 120 uri: item.card.uri, 121 cardContent: { 122 url: item.card.cardContent.url, 123 title: item.card.cardContent.title, 124 description: item.card.cardContent.description, 125 author: item.card.cardContent.author, 126 publishedDate: item.card.cardContent.publishedDate?.toISOString(), 127 siteName: item.card.cardContent.siteName, 128 imageUrl: item.card.cardContent.imageUrl, 129 type: item.card.cardContent.type, 130 retrievedAt: item.card.cardContent.retrievedAt?.toISOString(), 131 doi: item.card.cardContent.doi, 132 isbn: item.card.cardContent.isbn, 133 }, 134 libraryCount: item.card.libraryCount, 135 urlLibraryCount: item.card.urlLibraryCount, 136 urlInLibrary: item.card.urlInLibrary, 137 createdAt: item.card.createdAt.toISOString(), 138 updatedAt: item.card.updatedAt.toISOString(), 139 author: user, // Card author is same as library user 140 note: item.card.note, 141 }; 142 143 return { 144 user, 145 card, 146 }; 147 }); 148 149 return ok({ 150 libraries: enrichedLibraries, 151 pagination: { 152 currentPage: page, 153 totalPages: Math.ceil(result.totalCount / limit), 154 totalCount: result.totalCount, 155 hasMore: page * limit < result.totalCount, 156 limit, 157 }, 158 sorting: { 159 sortBy, 160 sortOrder, 161 }, 162 }); 163 } catch (error) { 164 return err( 165 new Error( 166 `Failed to retrieve libraries for URL: ${error instanceof Error ? error.message : 'Unknown error'}`, 167 ), 168 ); 169 } 170 } 171}