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 / GetUrlStatusForMyLibraryUseCase.ts
5.8 kB 175 lines
1import { Result, ok, err } from '../../../../../shared/core/Result'; 2import { BaseUseCase } from '../../../../../shared/core/UseCase'; 3import { UseCaseError } from '../../../../../shared/core/UseCaseError'; 4import { AppError } from '../../../../../shared/core/AppError'; 5import { IEventPublisher } from '../../../../../shared/application/events/IEventPublisher'; 6import { ICardRepository } from '../../../domain/ICardRepository'; 7import { ICollectionQueryRepository } from '../../../domain/ICollectionQueryRepository'; 8import { ICollectionRepository } from '../../../domain/ICollectionRepository'; 9import { IProfileService } from '../../../domain/services/IProfileService'; 10import { CuratorId } from '../../../domain/value-objects/CuratorId'; 11import { URL } from '../../../domain/value-objects/URL'; 12import { CollectionId } from '../../../domain/value-objects/CollectionId'; 13 14export interface GetUrlStatusForMyLibraryQuery { 15 url: string; 16 curatorId: string; 17} 18 19export interface CollectionInfo { 20 id: string; 21 uri?: string; 22 name: string; 23 description?: string; 24 author: { 25 id: string; 26 name: string; 27 handle: string; 28 avatarUrl?: string; 29 description?: string; 30 }; 31 cardCount: number; 32 createdAt: string; 33 updatedAt: string; 34} 35 36export interface GetUrlStatusForMyLibraryResult { 37 cardId?: string; 38 collections?: CollectionInfo[]; 39} 40 41export class ValidationError extends UseCaseError { 42 constructor(message: string) { 43 super(message); 44 } 45} 46 47export class GetUrlStatusForMyLibraryUseCase extends BaseUseCase< 48 GetUrlStatusForMyLibraryQuery, 49 Result< 50 GetUrlStatusForMyLibraryResult, 51 ValidationError | AppError.UnexpectedError 52 > 53> { 54 constructor( 55 private cardRepository: ICardRepository, 56 private collectionQueryRepository: ICollectionQueryRepository, 57 private collectionRepo: ICollectionRepository, 58 private profileService: IProfileService, 59 eventPublisher: IEventPublisher, 60 ) { 61 super(eventPublisher); 62 } 63 64 async execute( 65 query: GetUrlStatusForMyLibraryQuery, 66 ): Promise< 67 Result< 68 GetUrlStatusForMyLibraryResult, 69 ValidationError | AppError.UnexpectedError 70 > 71 > { 72 try { 73 // Validate and create CuratorId 74 const curatorIdResult = CuratorId.create(query.curatorId); 75 if (curatorIdResult.isErr()) { 76 return err( 77 new ValidationError( 78 `Invalid curator ID: ${curatorIdResult.error.message}`, 79 ), 80 ); 81 } 82 const curatorId = curatorIdResult.value; 83 84 // Validate URL 85 const urlResult = URL.create(query.url); 86 if (urlResult.isErr()) { 87 return err( 88 new ValidationError(`Invalid URL: ${urlResult.error.message}`), 89 ); 90 } 91 const url = urlResult.value; 92 93 // Check if user has a URL card with this URL 94 const existingCardResult = 95 await this.cardRepository.findUsersUrlCardByUrl(url, curatorId); 96 if (existingCardResult.isErr()) { 97 return err(AppError.UnexpectedError.create(existingCardResult.error)); 98 } 99 100 const card = existingCardResult.value; 101 const result: GetUrlStatusForMyLibraryResult = {}; 102 103 if (card) { 104 result.cardId = card.cardId.getStringValue(); 105 106 // Get collections containing this card for the user 107 try { 108 const collections = 109 await this.collectionQueryRepository.getCollectionsContainingCardForUser( 110 card.cardId.getStringValue(), 111 curatorId.value, 112 ); 113 114 // Enrich collections with full data 115 result.collections = await Promise.all( 116 collections.map(async (collection) => { 117 // Fetch full collection to get dates and cardCount 118 const collectionIdResult = CollectionId.createFromString( 119 collection.id, 120 ); 121 if (collectionIdResult.isErr()) { 122 throw new Error( 123 `Invalid collection ID: ${collection.id}`, 124 ); 125 } 126 const collectionResult = await this.collectionRepo.findById( 127 collectionIdResult.value, 128 ); 129 if (collectionResult.isErr() || !collectionResult.value) { 130 throw new Error( 131 `Collection not found: ${collection.id}`, 132 ); 133 } 134 const fullCollection = collectionResult.value; 135 136 // Fetch author profile 137 const authorProfileResult = await this.profileService.getProfile( 138 fullCollection.authorId.value, 139 ); 140 if (authorProfileResult.isErr()) { 141 throw new Error( 142 `Failed to fetch author profile: ${authorProfileResult.error.message}`, 143 ); 144 } 145 const authorProfile = authorProfileResult.value; 146 147 return { 148 id: collection.id, 149 uri: collection.uri, 150 name: collection.name, 151 description: collection.description, 152 author: { 153 id: authorProfile.id, 154 name: authorProfile.name, 155 handle: authorProfile.handle, 156 avatarUrl: authorProfile.avatarUrl, 157 description: authorProfile.bio, 158 }, 159 cardCount: fullCollection.cardCount, 160 createdAt: fullCollection.createdAt.toISOString(), 161 updatedAt: fullCollection.updatedAt.toISOString(), 162 }; 163 }), 164 ); 165 } catch (error) { 166 return err(AppError.UnexpectedError.create(error)); 167 } 168 } 169 170 return ok(result); 171 } catch (error) { 172 return err(AppError.UnexpectedError.create(error)); 173 } 174 } 175}