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 / GetCollectionContributorsUseCase.ts
5.0 kB 168 lines
1import { err, ok, Result } from 'src/shared/core/Result'; 2import { UseCase } from 'src/shared/core/UseCase'; 3import { ICollectionQueryRepository } from '../../../domain/ICollectionQueryRepository'; 4import { IProfileService } from 'src/modules/cards/domain/services/IProfileService'; 5import { ICollectionRepository } from 'src/modules/cards/domain/ICollectionRepository'; 6import { CollectionId } from 'src/modules/cards/domain/value-objects/CollectionId'; 7import { ContributorUser } from '@semble/types'; 8import { ProfileEnricher } from 'src/modules/cards/application/services/ProfileEnricher'; 9 10export interface GetCollectionContributorsQuery { 11 collectionId: string; // Collection UUID 12 callingUserId?: string; 13 page?: number; 14 limit?: number; 15} 16 17export interface GetCollectionContributorsResult { 18 users: ContributorUser[]; 19 pagination: { 20 currentPage: number; 21 totalPages: number; 22 totalCount: number; 23 hasMore: boolean; 24 limit: number; 25 }; 26} 27 28export class ValidationError extends Error { 29 constructor(message: string) { 30 super(message); 31 this.name = 'ValidationError'; 32 } 33} 34 35export class GetCollectionContributorsUseCase 36 implements 37 UseCase< 38 GetCollectionContributorsQuery, 39 Result<GetCollectionContributorsResult> 40 > 41{ 42 constructor( 43 private collectionQueryRepository: ICollectionQueryRepository, 44 private profileService: IProfileService, 45 private collectionRepository: ICollectionRepository, 46 ) {} 47 48 async execute( 49 query: GetCollectionContributorsQuery, 50 ): Promise<Result<GetCollectionContributorsResult>> { 51 // Set defaults 52 const page = query.page || 1; 53 const limit = Math.min(query.limit || 20, 100); // Cap at 100 54 55 // Validate collection ID 56 const collectionIdResult = CollectionId.createFromString( 57 query.collectionId, 58 ); 59 if (collectionIdResult.isErr()) { 60 return err( 61 new ValidationError( 62 `Invalid collection ID: ${collectionIdResult.error.message}`, 63 ), 64 ); 65 } 66 67 try { 68 // Verify collection exists 69 const collectionResult = await this.collectionRepository.findById( 70 collectionIdResult.value, 71 ); 72 if (collectionResult.isErr()) { 73 return err( 74 new Error( 75 `Failed to fetch collection: ${collectionResult.error instanceof Error ? collectionResult.error.message : 'Unknown error'}`, 76 ), 77 ); 78 } 79 if (!collectionResult.value) { 80 return err(new ValidationError('Collection not found')); 81 } 82 83 const collection = collectionResult.value; 84 const authorId = collection.authorId.value; 85 86 // Get contributors from repository (excluding author) 87 const contributorsResult = 88 await this.collectionQueryRepository.getCollectionContributors( 89 query.collectionId, 90 authorId, 91 { page, limit }, 92 ); 93 94 const totalCount = contributorsResult.totalCount; 95 96 // Extract unique contributor IDs 97 const uniqueContributorIds = contributorsResult.items.map( 98 (item) => item.userId, 99 ); 100 101 if (uniqueContributorIds.length === 0) { 102 return ok({ 103 users: [], 104 pagination: { 105 currentPage: page, 106 totalPages: 0, 107 totalCount: 0, 108 hasMore: false, 109 limit, 110 }, 111 }); 112 } 113 114 // Fetch profiles for all contributors using ProfileEnricher 115 const profileEnricher = new ProfileEnricher(this.profileService); 116 const profileMapResult = await profileEnricher.buildProfileMap( 117 uniqueContributorIds, 118 query.callingUserId, 119 { 120 skipFailures: false, // Fail if any profile fetch fails 121 mapToUser: true, // Use full User DTO with isFollowing 122 }, 123 ); 124 125 if (profileMapResult.isErr()) { 126 return err( 127 new Error( 128 `Failed to fetch user profiles: ${profileMapResult.error.message}`, 129 ), 130 ); 131 } 132 133 const profileMap = profileMapResult.value; 134 135 // Build users array in the order of contributors (chronological by most recent contribution) 136 // Add contributionCount to each user 137 const users: ContributorUser[] = contributorsResult.items 138 .map((item) => { 139 const user = profileMap.get(item.userId); 140 if (!user) { 141 return undefined; 142 } 143 return { 144 ...user, 145 contributionCount: item.contributionCount, 146 }; 147 }) 148 .filter((user): user is ContributorUser => user !== undefined); 149 150 return ok({ 151 users, 152 pagination: { 153 currentPage: page, 154 totalPages: Math.ceil(totalCount / limit), 155 totalCount, 156 hasMore: page * limit < totalCount, 157 limit, 158 }, 159 }); 160 } catch (error) { 161 return err( 162 new Error( 163 `Failed to retrieve collection contributors: ${error instanceof Error ? error.message : 'Unknown error'}`, 164 ), 165 ); 166 } 167 } 168}