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 / GetUrlCardsUseCase.ts
4.0 kB 139 lines
1import { err, ok, Result } from 'src/shared/core/Result'; 2import { UseCase } from 'src/shared/core/UseCase'; 3import { 4 ICardQueryRepository, 5 CardSortField, 6 SortOrder, 7 WithCollections, 8 UrlCardView, 9} from '../../../domain/ICardQueryRepository'; 10import { UrlType } from '../../../domain/value-objects/UrlType'; 11import { DIDOrHandle } from 'src/modules/atproto/domain/DIDOrHandle'; 12import { IIdentityResolutionService } from 'src/modules/atproto/domain/services/IIdentityResolutionService'; 13import { IProfileService } from '../../../domain/services/IProfileService'; 14import { User } from '@semble/types'; 15import { ProfileEnricher } from '../../services/ProfileEnricher'; 16 17export interface GetUrlCardsQuery { 18 userId: string; 19 callingUserId?: string; 20 page?: number; 21 limit?: number; 22 sortBy?: CardSortField; 23 sortOrder?: SortOrder; 24 urlType?: UrlType; 25} 26 27// Enriched data for the final use case result 28export type UrlCardListItemDTO = Omit<UrlCardView, 'authorId'> & { 29 author: User; 30}; 31export interface GetUrlCardsResult { 32 cards: UrlCardListItemDTO[]; 33 pagination: { 34 currentPage: number; 35 totalPages: number; 36 totalCount: number; 37 hasMore: boolean; 38 limit: number; 39 }; 40 sorting: { 41 sortBy: CardSortField; 42 sortOrder: SortOrder; 43 }; 44} 45 46export class ValidationError extends Error { 47 constructor(message: string) { 48 super(message); 49 this.name = 'ValidationError'; 50 } 51} 52 53export class GetUrlCardsUseCase 54 implements UseCase<GetUrlCardsQuery, Result<GetUrlCardsResult>> 55{ 56 constructor( 57 private cardQueryRepo: ICardQueryRepository, 58 private identityResolver: IIdentityResolutionService, 59 private profileService: IProfileService, 60 ) {} 61 62 async execute(query: GetUrlCardsQuery): Promise<Result<GetUrlCardsResult>> { 63 // Set defaults 64 const page = query.page || 1; 65 const limit = Math.min(query.limit || 20, 100); // Cap at 100 66 const sortBy = query.sortBy || CardSortField.UPDATED_AT; 67 const sortOrder = query.sortOrder || SortOrder.DESC; 68 const urlType = query.urlType; 69 70 // Parse and validate user identifier 71 const identifierResult = DIDOrHandle.create(query.userId); 72 if (identifierResult.isErr()) { 73 return err(new ValidationError('Invalid user identifier')); 74 } 75 76 // Resolve to DID 77 const didResult = await this.identityResolver.resolveToDID( 78 identifierResult.value, 79 ); 80 if (didResult.isErr()) { 81 return err( 82 new ValidationError( 83 `Could not resolve user identifier: ${didResult.error.message}`, 84 ), 85 ); 86 } 87 88 try { 89 // Execute query to get raw card data using the resolved DID 90 const result = await this.cardQueryRepo.getUrlCardsOfUser( 91 didResult.value.value, 92 { 93 page, 94 limit, 95 sortBy, 96 sortOrder, 97 urlType, 98 }, 99 query.callingUserId, 100 ); 101 102 // Enrich cards with author profiles using ProfileEnricher utility 103 const profileEnricher = new ProfileEnricher(this.profileService); 104 const enrichResult = await profileEnricher.enrichWithAuthors( 105 result.items, 106 (item) => item.authorId, 107 query.callingUserId, 108 { mapToUser: false }, // Use inline profile (without isFollowing) 109 ); 110 111 if (enrichResult.isErr()) { 112 return err(enrichResult.error); 113 } 114 115 const enrichedCards = enrichResult.value; 116 117 return ok({ 118 cards: enrichedCards, 119 pagination: { 120 currentPage: page, 121 totalPages: Math.ceil(result.totalCount / limit), 122 totalCount: result.totalCount, 123 hasMore: page * limit < result.totalCount, 124 limit, 125 }, 126 sorting: { 127 sortBy, 128 sortOrder, 129 }, 130 }); 131 } catch (error) { 132 return err( 133 new Error( 134 `Failed to retrieve URL cards: ${error instanceof Error ? error.message : 'Unknown error'}`, 135 ), 136 ); 137 } 138 } 139}