A social knowledge tool for researchers built on ATProto
at development 173 lines 4.8 kB view raw
1import { CollectionId } from 'src/modules/cards/domain/value-objects/CollectionId'; 2import { err, ok, Result } from 'src/shared/core/Result'; 3import { UseCase } from 'src/shared/core/UseCase'; 4import { 5 ICardQueryRepository, 6 CardSortField, 7 SortOrder, 8 UrlCardView, 9} from '../../../domain/ICardQueryRepository'; 10import { ICollectionRepository } from '../../../domain/ICollectionRepository'; 11import { IProfileService } from '../../../domain/services/IProfileService'; 12 13export interface GetCollectionPageQuery { 14 collectionId: string; 15 callerDid?: string; 16 page?: number; 17 limit?: number; 18 sortBy?: CardSortField; 19 sortOrder?: SortOrder; 20} 21 22export type CollectionPageUrlCardDTO = UrlCardView; 23export interface GetCollectionPageResult { 24 id: string; 25 uri?: string; 26 name: string; 27 description?: string; 28 author: { 29 id: string; 30 name: string; 31 handle: string; 32 avatarUrl?: string; 33 }; 34 urlCards: CollectionPageUrlCardDTO[]; 35 pagination: { 36 currentPage: number; 37 totalPages: number; 38 totalCount: number; 39 hasMore: boolean; 40 limit: number; 41 }; 42 sorting: { 43 sortBy: CardSortField; 44 sortOrder: SortOrder; 45 }; 46} 47 48export class ValidationError extends Error { 49 constructor(message: string) { 50 super(message); 51 this.name = 'ValidationError'; 52 } 53} 54 55export class CollectionNotFoundError extends Error { 56 constructor(message: string) { 57 super(message); 58 this.name = 'CollectionNotFoundError'; 59 } 60} 61 62export class GetCollectionPageUseCase 63 implements UseCase<GetCollectionPageQuery, Result<GetCollectionPageResult>> 64{ 65 constructor( 66 private collectionRepo: ICollectionRepository, 67 private cardQueryRepo: ICardQueryRepository, 68 private profileService: IProfileService, 69 ) {} 70 71 async execute( 72 query: GetCollectionPageQuery, 73 ): Promise<Result<GetCollectionPageResult>> { 74 // Set defaults 75 const page = query.page || 1; 76 const limit = Math.min(query.limit || 20, 100); // Cap at 100 77 const sortBy = query.sortBy || CardSortField.UPDATED_AT; 78 const sortOrder = query.sortOrder || SortOrder.DESC; 79 80 // Validate collection ID 81 const collectionIdResult = CollectionId.createFromString( 82 query.collectionId, 83 ); 84 if (collectionIdResult.isErr()) { 85 return err(new ValidationError('Invalid collection ID')); 86 } 87 88 try { 89 // Get the collection 90 const collectionResult = await this.collectionRepo.findById( 91 collectionIdResult.value, 92 ); 93 94 if (collectionResult.isErr()) { 95 return err( 96 new Error( 97 `Failed to fetch collection: ${collectionResult.error instanceof Error ? collectionResult.error.message : 'Unknown error'}`, 98 ), 99 ); 100 } 101 102 const collection = collectionResult.value; 103 if (!collection) { 104 return err(new CollectionNotFoundError('Collection not found')); 105 } 106 107 const collectionPublishedRecordId = collection.publishedRecordId; 108 109 const collectionUri = collectionPublishedRecordId?.uri; 110 111 // Get author profile 112 const profileResult = await this.profileService.getProfile( 113 collection.authorId.value, 114 query.callerDid, 115 ); 116 117 if (profileResult.isErr()) { 118 return err( 119 new Error( 120 `Failed to fetch author profile: ${profileResult.error instanceof Error ? profileResult.error.message : 'Unknown error'}`, 121 ), 122 ); 123 } 124 125 const authorProfile = profileResult.value; 126 127 // Get cards in the collection 128 const cardsResult = await this.cardQueryRepo.getCardsInCollection( 129 query.collectionId, 130 { 131 page, 132 limit, 133 sortBy, 134 sortOrder, 135 }, 136 ); 137 138 // Transform raw card data to enriched DTOs 139 const enrichedCards: CollectionPageUrlCardDTO[] = cardsResult.items; 140 141 return ok({ 142 id: collection.collectionId.getStringValue(), 143 uri: collectionUri, 144 name: collection.name.value, 145 description: collection.description?.value, 146 author: { 147 id: authorProfile.id, 148 name: authorProfile.name, 149 handle: authorProfile.handle, 150 avatarUrl: authorProfile.avatarUrl, 151 }, 152 urlCards: enrichedCards, 153 pagination: { 154 currentPage: page, 155 totalPages: Math.ceil(cardsResult.totalCount / limit), 156 totalCount: cardsResult.totalCount, 157 hasMore: page * limit < cardsResult.totalCount, 158 limit, 159 }, 160 sorting: { 161 sortBy, 162 sortOrder, 163 }, 164 }); 165 } catch (error) { 166 return err( 167 new Error( 168 `Failed to retrieve collection page: ${error instanceof Error ? error.message : 'Unknown error'}`, 169 ), 170 ); 171 } 172 } 173}