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 / GetNoteCardsForUrlUseCase.ts
3.9 kB 134 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 { NoteCardDTO, PaginationDTO, CardSortingDTO } from '@semble/types'; 11import { ProfileEnricher } from '../../services/ProfileEnricher'; 12 13export interface GetNoteCardsForUrlQuery { 14 url: string; 15 callingUserId?: string; 16 page?: number; 17 limit?: number; 18 sortBy?: CardSortField; 19 sortOrder?: SortOrder; 20} 21 22export interface GetNoteCardsForUrlResult { 23 notes: NoteCardDTO[]; 24 pagination: PaginationDTO; 25 sorting: CardSortingDTO; 26} 27 28export class ValidationError extends Error { 29 constructor(message: string) { 30 super(message); 31 this.name = 'ValidationError'; 32 } 33} 34 35export class GetNoteCardsForUrlUseCase 36 implements UseCase<GetNoteCardsForUrlQuery, Result<GetNoteCardsForUrlResult>> 37{ 38 constructor( 39 private cardQueryRepo: ICardQueryRepository, 40 private profileService: IProfileService, 41 ) {} 42 43 async execute( 44 query: GetNoteCardsForUrlQuery, 45 ): Promise<Result<GetNoteCardsForUrlResult>> { 46 // Validate URL 47 const urlResult = URL.create(query.url); 48 if (urlResult.isErr()) { 49 return err( 50 new ValidationError(`Invalid URL: ${urlResult.error.message}`), 51 ); 52 } 53 54 // Set defaults 55 const page = query.page || 1; 56 const limit = Math.min(query.limit || 20, 100); // Cap at 100 57 const sortBy = query.sortBy || CardSortField.UPDATED_AT; 58 const sortOrder = query.sortOrder || SortOrder.DESC; 59 60 try { 61 // Execute query to get note cards for the URL (raw data with authorId) 62 const result = await this.cardQueryRepo.getNoteCardsForUrl( 63 urlResult.value.value, 64 { 65 page, 66 limit, 67 sortBy, 68 sortOrder, 69 }, 70 ); 71 72 // Enrich with author profiles using ProfileEnricher 73 const uniqueAuthorIds = Array.from( 74 new Set(result.items.map((item) => item.authorId)), 75 ); 76 77 const profileEnricher = new ProfileEnricher(this.profileService); 78 const profileMapResult = await profileEnricher.buildProfileMap( 79 uniqueAuthorIds, 80 query.callingUserId, 81 { 82 skipFailures: false, // Fail if any profile fetch fails (preserving original behavior) 83 mapToUser: false, // Use inline profile (without isFollowing) 84 }, 85 ); 86 87 if (profileMapResult.isErr()) { 88 return err( 89 new Error( 90 `Failed to fetch author profiles: ${profileMapResult.error.message}`, 91 ), 92 ); 93 } 94 95 const profileMap = profileMapResult.value; 96 97 // Map items with enriched author data 98 const enrichedNotes: NoteCardDTO[] = result.items.map((item) => { 99 const author = profileMap.get(item.authorId); 100 if (!author) { 101 throw new Error(`Profile not found for author ${item.authorId}`); 102 } 103 return { 104 id: item.id, 105 note: item.note, 106 author, 107 createdAt: item.createdAt.toISOString(), 108 updatedAt: item.updatedAt.toISOString(), 109 }; 110 }); 111 112 return ok({ 113 notes: enrichedNotes, 114 pagination: { 115 currentPage: page, 116 totalPages: Math.ceil(result.totalCount / limit), 117 totalCount: result.totalCount, 118 hasMore: page * limit < result.totalCount, 119 limit, 120 }, 121 sorting: { 122 sortBy, 123 sortOrder, 124 }, 125 }); 126 } catch (error) { 127 return err( 128 new Error( 129 `Failed to retrieve note cards for URL: ${error instanceof Error ? error.message : 'Unknown error'}`, 130 ), 131 ); 132 } 133 } 134}