A social knowledge tool for researchers built on ATProto

feat: implement library context enrichment for search results

Co-authored-by: aider (anthropic/claude-sonnet-4-20250514) <aider@aider.chat>

+49 -16
+44 -15
src/modules/search/domain/services/SearchService.ts
··· 9 9 constructor( 10 10 private vectorDatabase: IVectorDatabase, 11 11 private metadataService: IMetadataService, 12 + private cardQueryRepository: ICardQueryRepository, 12 13 ) {} 13 14 14 15 async indexUrl(url: URL): Promise<Result<void>> { ··· 59 60 60 61 async findSimilarUrls( 61 62 url: URL, 62 - options: { limit: number; threshold?: number }, 63 + options: { limit: number; threshold?: number; callingUserId?: string }, 63 64 ): Promise<Result<UrlView[]>> { 64 65 try { 65 66 // 1. Find similar URLs from vector database ··· 80 81 // 2. Enrich results with library counts and context 81 82 const enrichedUrls = await this.enrichUrlsWithContext( 82 83 similarResult.value, 84 + options.callingUserId, 83 85 ); 84 86 85 87 return ok(enrichedUrls); ··· 116 118 author?: string; 117 119 }; 118 120 }>, 121 + callingUserId?: string, 119 122 ): Promise<UrlView[]> { 120 - // For now, return basic enriched results 121 - // In a full implementation, you'd query the card repository for library counts 122 - return searchResults.map((result) => ({ 123 - url: result.url, 124 - metadata: { 125 - url: result.url, 126 - title: result.metadata.title, 127 - description: result.metadata.description, 128 - author: result.metadata.author, 129 - thumbnailUrl: undefined, // Would be enriched from metadata service 130 - }, 131 - urlLibraryCount: 0, // Would be queried from card repository 132 - urlInLibrary: false, // Would be determined based on calling user context 133 - })); 123 + // Enrich each URL with library context 124 + const enrichedResults = await Promise.all( 125 + searchResults.map(async (result) => { 126 + // Get library information for this URL 127 + const librariesResult = await this.cardQueryRepository.getLibrariesForUrl( 128 + result.url, 129 + { 130 + page: 1, 131 + limit: 1000, // Get all libraries to count them 132 + sortBy: 'createdAt' as any, // Type assertion needed due to enum mismatch 133 + sortOrder: 'desc' as any, 134 + }, 135 + ); 136 + 137 + const urlLibraryCount = librariesResult.totalCount; 138 + 139 + // Check if calling user has this URL in their library 140 + let urlInLibrary = false; 141 + if (callingUserId) { 142 + urlInLibrary = librariesResult.items.some( 143 + (library) => library.userId === callingUserId, 144 + ); 145 + } 146 + 147 + return { 148 + url: result.url, 149 + metadata: { 150 + url: result.url, 151 + title: result.metadata.title, 152 + description: result.metadata.description, 153 + author: result.metadata.author, 154 + thumbnailUrl: undefined, // Could be enriched from metadata service if needed 155 + }, 156 + urlLibraryCount, 157 + urlInLibrary, 158 + }; 159 + }), 160 + ); 161 + 162 + return enrichedResults; 134 163 } 135 164 }
+5 -1
src/shared/infrastructure/http/factories/ServiceFactory.ts
··· 314 314 ? InMemoryVectorDatabase.getInstance() 315 315 : InMemoryVectorDatabase.getInstance(); // TODO: Replace with real vector DB implementation 316 316 317 - const searchService = new SearchService(vectorDatabase, metadataService); 317 + const searchService = new SearchService( 318 + vectorDatabase, 319 + metadataService, 320 + repositories.cardQueryRepository, 321 + ); 318 322 319 323 return { 320 324 tokenService,