A social knowledge tool for researchers built on ATProto
at development 162 lines 4.5 kB view raw
1import { err, ok, Result } from 'src/shared/core/Result'; 2import { UseCase } from 'src/shared/core/UseCase'; 3import { 4 ICollectionQueryRepository, 5 CollectionSortField, 6 SortOrder, 7} from '../../../domain/ICollectionQueryRepository'; 8import { IProfileService } from 'src/modules/cards/domain/services/IProfileService'; 9import { DIDOrHandle } from 'src/modules/atproto/domain/DIDOrHandle'; 10import { IIdentityResolutionService } from 'src/modules/atproto/domain/services/IIdentityResolutionService'; 11 12export interface GetCollectionsQuery { 13 curatorId: string; 14 page?: number; 15 limit?: number; 16 sortBy?: CollectionSortField; 17 sortOrder?: SortOrder; 18 searchText?: string; 19} 20 21// Enriched data for the final use case result 22export interface CollectionListItemDTO { 23 id: string; 24 uri?: string; 25 name: string; 26 description?: string; 27 updatedAt: Date; 28 createdAt: Date; 29 cardCount: number; 30 createdBy: { 31 id: string; 32 name: string; 33 handle: string; 34 avatarUrl?: string; 35 }; 36} 37export interface GetCollectionsResult { 38 collections: CollectionListItemDTO[]; 39 pagination: { 40 currentPage: number; 41 totalPages: number; 42 totalCount: number; 43 hasMore: boolean; 44 limit: number; 45 }; 46 sorting: { 47 sortBy: CollectionSortField; 48 sortOrder: SortOrder; 49 }; 50} 51 52export class ValidationError extends Error { 53 constructor(message: string) { 54 super(message); 55 this.name = 'ValidationError'; 56 } 57} 58 59export class GetCollectionsUseCase 60 implements UseCase<GetCollectionsQuery, Result<GetCollectionsResult>> 61{ 62 constructor( 63 private collectionQueryRepo: ICollectionQueryRepository, 64 private profileService: IProfileService, 65 private identityResolver: IIdentityResolutionService, 66 ) {} 67 68 async execute( 69 query: GetCollectionsQuery, 70 ): Promise<Result<GetCollectionsResult>> { 71 // Set defaults 72 const page = query.page || 1; 73 const limit = Math.min(query.limit || 20, 100); // Cap at 100 74 const sortBy = query.sortBy || CollectionSortField.UPDATED_AT; 75 const sortOrder = query.sortOrder || SortOrder.DESC; 76 77 // Parse and validate curator identifier 78 const identifierResult = DIDOrHandle.create(query.curatorId); 79 if (identifierResult.isErr()) { 80 return err(new ValidationError('Invalid curator identifier')); 81 } 82 83 // Resolve to DID 84 const didResult = await this.identityResolver.resolveToDID( 85 identifierResult.value, 86 ); 87 if (didResult.isErr()) { 88 return err( 89 new ValidationError( 90 `Could not resolve curator identifier: ${didResult.error.message}`, 91 ), 92 ); 93 } 94 95 const resolvedDid = didResult.value.value; 96 97 try { 98 // Execute query to get raw collection data using the resolved DID 99 const result = await this.collectionQueryRepo.findByCreator(resolvedDid, { 100 page, 101 limit, 102 sortBy, 103 sortOrder, 104 searchText: query.searchText, 105 }); 106 107 // Get user profile for the curator using the resolved DID 108 const profileResult = await this.profileService.getProfile(resolvedDid); 109 110 if (profileResult.isErr()) { 111 return err( 112 new Error( 113 `Failed to fetch user profile: ${profileResult.error instanceof Error ? profileResult.error.message : 'Unknown error'}`, 114 ), 115 ); 116 } 117 const profile = profileResult.value; 118 119 // Transform raw data to enriched DTOs 120 const enrichedCollections: CollectionListItemDTO[] = result.items.map( 121 (item) => { 122 return { 123 id: item.id, 124 uri: item.uri, 125 name: item.name, 126 description: item.description, 127 updatedAt: item.updatedAt, 128 createdAt: item.createdAt, 129 cardCount: item.cardCount, 130 createdBy: { 131 id: profile.id, 132 name: profile.name, 133 handle: profile.handle, 134 avatarUrl: profile.avatarUrl, 135 }, 136 }; 137 }, 138 ); 139 140 return ok({ 141 collections: enrichedCollections, 142 pagination: { 143 currentPage: page, 144 totalPages: Math.ceil(result.totalCount / limit), 145 totalCount: result.totalCount, 146 hasMore: page * limit < result.totalCount, 147 limit, 148 }, 149 sorting: { 150 sortBy, 151 sortOrder, 152 }, 153 }); 154 } catch (error) { 155 return err( 156 new Error( 157 `Failed to retrieve collections: ${error instanceof Error ? error.message : 'Unknown error'}`, 158 ), 159 ); 160 } 161 } 162}