A social knowledge tool for researchers built on ATProto
at development 7.1 kB view raw
1import { 2 ICollectionQueryRepository, 3 CollectionQueryOptions, 4 CollectionQueryResultDTO, 5 CollectionContainingCardDTO, 6 CollectionForUrlDTO, 7 PaginatedQueryResult, 8 CollectionSortField, 9 SortOrder, 10 CollectionForUrlQueryOptions, 11} from '../../domain/ICollectionQueryRepository'; 12import { Collection } from '../../domain/Collection'; 13import { InMemoryCollectionRepository } from './InMemoryCollectionRepository'; 14import { InMemoryCardRepository } from './InMemoryCardRepository'; 15 16export class InMemoryCollectionQueryRepository 17 implements ICollectionQueryRepository 18{ 19 constructor( 20 private collectionRepository: InMemoryCollectionRepository, 21 private cardRepository?: InMemoryCardRepository, 22 ) {} 23 24 async findByCreator( 25 curatorId: string, 26 options: CollectionQueryOptions, 27 ): Promise<PaginatedQueryResult<CollectionQueryResultDTO>> { 28 try { 29 const allCollections = this.collectionRepository.getAllCollections(); 30 let creatorCollections = allCollections.filter( 31 (collection) => collection.authorId.value === curatorId, 32 ); 33 34 if (options.searchText && options.searchText.trim()) { 35 const searchTerm = options.searchText.trim().toLowerCase(); 36 creatorCollections = creatorCollections.filter((collection) => { 37 const nameMatch = collection.name.value 38 .toLowerCase() 39 .includes(searchTerm); 40 const descriptionMatch = 41 collection.description?.value.toLowerCase().includes(searchTerm) || 42 false; 43 return nameMatch || descriptionMatch; 44 }); 45 } 46 47 const sortedCollections = this.sortCollections( 48 creatorCollections, 49 options.sortBy, 50 options.sortOrder, 51 ); 52 53 const startIndex = (options.page - 1) * options.limit; 54 const endIndex = startIndex + options.limit; 55 const paginatedCollections = sortedCollections.slice( 56 startIndex, 57 endIndex, 58 ); 59 60 const items: CollectionQueryResultDTO[] = paginatedCollections.map( 61 (collection) => { 62 const collectionPublishedRecordId = collection.publishedRecordId; 63 return { 64 id: collection.collectionId.getStringValue(), 65 uri: collectionPublishedRecordId?.uri, 66 authorId: collection.authorId.value, 67 name: collection.name.value, 68 description: collection.description?.value, 69 accessType: collection.accessType, 70 cardCount: collection.cardCount, 71 createdAt: collection.createdAt, 72 updatedAt: collection.updatedAt, 73 }; 74 }, 75 ); 76 77 return { 78 items, 79 totalCount: creatorCollections.length, 80 hasMore: endIndex < creatorCollections.length, 81 }; 82 } catch (error) { 83 throw new Error( 84 `Failed to query collections: ${error instanceof Error ? error.message : String(error)}`, 85 ); 86 } 87 } 88 89 private sortCollections( 90 collections: Collection[], 91 sortBy: CollectionSortField, 92 sortOrder: SortOrder, 93 ): Collection[] { 94 const sorted = [...collections].sort((a, b) => { 95 let comparison = 0; 96 97 switch (sortBy) { 98 case CollectionSortField.NAME: 99 comparison = a.name.value.localeCompare(b.name.value); 100 break; 101 case CollectionSortField.CREATED_AT: 102 comparison = a.createdAt.getTime() - b.createdAt.getTime(); 103 break; 104 case CollectionSortField.UPDATED_AT: 105 comparison = a.updatedAt.getTime() - b.updatedAt.getTime(); 106 break; 107 case CollectionSortField.CARD_COUNT: 108 comparison = a.cardCount - b.cardCount; 109 break; 110 default: 111 comparison = 0; 112 } 113 114 return sortOrder === SortOrder.DESC ? -comparison : comparison; 115 }); 116 117 return sorted; 118 } 119 120 async getCollectionsContainingCardForUser( 121 cardId: string, 122 curatorId: string, 123 ): Promise<CollectionContainingCardDTO[]> { 124 try { 125 const allCollections = this.collectionRepository.getAllCollections(); 126 const creatorCollections = allCollections.filter( 127 (collection) => collection.authorId.value === curatorId, 128 ); 129 130 const collectionsWithCard = creatorCollections.filter((collection) => 131 collection.cardLinks.some( 132 (link) => link.cardId.getStringValue() === cardId, 133 ), 134 ); 135 136 const result: CollectionContainingCardDTO[] = collectionsWithCard.map( 137 (collection) => { 138 const collectionPublishedRecordId = collection.publishedRecordId; 139 return { 140 id: collection.collectionId.getStringValue(), 141 uri: collectionPublishedRecordId?.uri, 142 name: collection.name.value, 143 description: collection.description?.value, 144 }; 145 }, 146 ); 147 148 return result; 149 } catch (error) { 150 throw new Error( 151 `Failed to get collections containing card: ${error instanceof Error ? error.message : String(error)}`, 152 ); 153 } 154 } 155 156 async getCollectionsWithUrl( 157 url: string, 158 options: CollectionForUrlQueryOptions, 159 ): Promise<PaginatedQueryResult<CollectionForUrlDTO>> { 160 try { 161 if (!this.cardRepository) { 162 throw new Error( 163 'Card repository is required for getCollectionsWithUrl', 164 ); 165 } 166 167 const allCards = this.cardRepository.getAllCards(); 168 const cardsWithUrl = allCards.filter( 169 (card) => card.isUrlCard && card.url?.value === url, 170 ); 171 172 const cardIds = new Set( 173 cardsWithUrl.map((card) => card.cardId.getStringValue()), 174 ); 175 176 const allCollections = this.collectionRepository.getAllCollections(); 177 const collectionsWithUrl = allCollections.filter((collection) => 178 collection.cardLinks.some((link) => 179 cardIds.has(link.cardId.getStringValue()), 180 ), 181 ); 182 183 // Sort collections 184 const sortedCollections = this.sortCollections( 185 collectionsWithUrl, 186 options.sortBy, 187 options.sortOrder, 188 ); 189 190 // Apply pagination 191 const { page, limit } = options; 192 const startIndex = (page - 1) * limit; 193 const endIndex = startIndex + limit; 194 const paginatedCollections = sortedCollections.slice( 195 startIndex, 196 endIndex, 197 ); 198 199 const items: CollectionForUrlDTO[] = paginatedCollections.map( 200 (collection) => { 201 const collectionPublishedRecordId = collection.publishedRecordId; 202 return { 203 id: collection.collectionId.getStringValue(), 204 uri: collectionPublishedRecordId?.uri, 205 name: collection.name.value, 206 description: collection.description?.value, 207 authorId: collection.authorId.value, 208 }; 209 }, 210 ); 211 212 return { 213 items, 214 totalCount: sortedCollections.length, 215 hasMore: endIndex < sortedCollections.length, 216 }; 217 } catch (error) { 218 throw new Error( 219 `Failed to get collections with URL: ${error instanceof Error ? error.message : String(error)}`, 220 ); 221 } 222 } 223 224 clear(): void { 225 // No separate state to clear 226 } 227}