A social knowledge tool for researchers built on ATProto

formatting and linting

+159 -64
+118 -47
docs/plan/api_client_type_unification.md
··· 5 5 This document outlines the plan to unify API client response types for `User`, `UrlCard`, and `Collection` to enable shared UI components across different API endpoints. 6 6 7 7 ### Goals 8 + 8 9 - Unify duplicate type definitions into single, consistent interfaces 9 10 - Enable UI component reuse across different endpoints 10 11 - Minimize changes to backend (repositories and use cases) ··· 19 20 Currently, we have several user type definitions scattered across response types: 20 21 21 22 1. **`LibraryUser`** (responses.ts:96-101) 23 + 22 24 ```tsx 23 - { id, name, handle, avatarUrl } 25 + { 26 + (id, name, handle, avatarUrl); 27 + } 24 28 ``` 25 29 26 30 2. **`UserProfile`** (responses.ts:109-115) 31 + 27 32 ```tsx 28 - { id, name, handle, description, avatarUrl } 33 + { 34 + (id, name, handle, description, avatarUrl); 35 + } 29 36 ``` 30 37 31 38 3. **`FeedActivityActor`** (responses.ts:259-264) 39 + 32 40 ```tsx 33 - { id, name, handle, avatarUrl } 41 + { 42 + (id, name, handle, avatarUrl); 43 + } 34 44 ``` 35 45 36 46 4. **Inline author objects** in various responses with similar fields ··· 79 89 - Missing `cardCount`, `createdAt`, `updatedAt` 80 90 81 91 **Inconsistencies**: 92 + 82 93 - Sometimes `author`, sometimes `createdBy`, sometimes `authorId`, sometimes `authorHandle` 83 94 - Inconsistent inclusion of `cardCount`, `createdAt`, `updatedAt` 84 95 85 96 ### GetLibrariesForUrlResponse - Missing Card Data 86 97 87 98 **Current Structure** (responses.ts:331-340): 99 + 88 100 ```tsx 89 101 { 90 102 libraries: { ··· 98 110 ``` 99 111 100 112 **Issue**: When showing "who has this URL in their library", we only show user info but not their specific card. This means we can't display: 113 + 101 114 - The user's note on the URL 102 115 - When they saved it 103 116 - Their specific card metadata ··· 124 137 ``` 125 138 126 139 **Changes Required**: 140 + 127 141 - Remove `LibraryUser` interface (lines 96-101) 128 142 - Remove `UserProfile` interface (lines 109-115) 129 143 - Remove `FeedActivityActor` interface (lines 259-264) ··· 151 165 urlInLibrary?: boolean; 152 166 createdAt: string; 153 167 updatedAt: string; 154 - author: User; // NEW - currently missing! 168 + author: User; // NEW - currently missing! 155 169 note?: { 156 170 id: string; 157 171 text: string; ··· 160 174 ``` 161 175 162 176 **Changes Required**: 177 + 163 178 - Remove `UrlCardView` interface (lines 61-92) 164 179 - Remove `UrlCardListItem` interface (lines 119-144) 165 180 - Remove `CollectionPageUrlCard` interface (lines 175-195) ··· 195 210 id: string; 196 211 uri?: string; 197 212 name: string; 198 - author: User; // Standardize to 'author', not 'createdBy' 213 + author: User; // Standardize to 'author', not 'createdBy' 199 214 description?: string; 200 215 cardCount: number; 201 216 createdAt: string; ··· 204 219 ``` 205 220 206 221 **Changes Required**: 222 + 207 223 - Standardize all collection representations to use this interface 208 224 - Change `createdBy` to `author` in `GetCollectionsResponse` 209 225 - Add missing fields (`cardCount`, `createdAt`, `updatedAt`) where needed ··· 211 227 ### 4. Updated Response Types 212 228 213 229 #### GetUrlCardViewResponse 230 + 214 231 ```tsx 215 - export interface GetUrlCardViewResponse extends UrlCardWithCollectionsAndLibraries {} 232 + export interface GetUrlCardViewResponse 233 + extends UrlCardWithCollectionsAndLibraries {} 216 234 ``` 217 235 218 236 #### GetUrlCardsResponse 237 + 219 238 ```tsx 220 239 export interface GetUrlCardsResponse { 221 - cards: UrlCardWithCollections[]; // Changed from UrlCardListItem[] 240 + cards: UrlCardWithCollections[]; // Changed from UrlCardListItem[] 222 241 pagination: Pagination; 223 242 sorting: CardSorting; 224 243 } 225 244 ``` 226 245 227 246 #### GetCollectionPageResponse 247 + 228 248 ```tsx 229 249 export interface GetCollectionPageResponse { 230 250 id: string; ··· 232 252 name: string; 233 253 description?: string; 234 254 author: User; 235 - urlCards: UrlCard[]; // Changed from CollectionPageUrlCard[], now includes author 236 - cardCount: number; // NEW 237 - createdAt: string; // NEW 238 - updatedAt: string; // NEW 255 + urlCards: UrlCard[]; // Changed from CollectionPageUrlCard[], now includes author 256 + cardCount: number; // NEW 257 + createdAt: string; // NEW 258 + updatedAt: string; // NEW 239 259 pagination: Pagination; 240 260 sorting: CardSorting; 241 261 } 242 262 ``` 243 263 244 264 #### GetCollectionsResponse 265 + 245 266 ```tsx 246 267 export interface GetCollectionsResponse { 247 - collections: Collection[]; // Uses unified Collection interface 268 + collections: Collection[]; // Uses unified Collection interface 248 269 pagination: Pagination; 249 270 sorting: CollectionSorting; 250 271 } 251 272 ``` 252 273 253 274 #### GetCollectionsForUrlResponse 275 + 254 276 ```tsx 255 277 export interface GetCollectionsForUrlResponse { 256 - collections: Collection[]; // Uses unified Collection interface 278 + collections: Collection[]; // Uses unified Collection interface 257 279 pagination: Pagination; 258 280 sorting: CollectionSorting; 259 281 } 260 282 ``` 261 283 262 284 #### GetLibrariesForCardResponse 285 + 263 286 ```tsx 264 287 export interface GetLibrariesForCardResponse { 265 288 cardId: string; 266 - users: User[]; // Changed from LibraryUser[] 289 + users: User[]; // Changed from LibraryUser[] 267 290 totalCount: number; 268 291 } 269 292 ``` 270 293 271 294 #### GetLibrariesForUrlResponse 295 + 272 296 ```tsx 273 297 export interface GetLibrariesForUrlResponse { 274 298 libraries: { 275 - user: User; // The user who has this URL in their library 299 + user: User; // The user who has this URL in their library 276 300 card: UrlCard; // Their specific card (may include a note) 277 301 }[]; 278 302 pagination: Pagination; ··· 281 305 ``` 282 306 283 307 #### GetNoteCardsForUrlResponse 308 + 284 309 ```tsx 285 310 export interface GetNoteCardsForUrlResponse { 286 311 notes: { 287 312 id: string; 288 313 note: string; 289 - author: User; // Changed to use unified User interface 314 + author: User; // Changed to use unified User interface 290 315 createdAt: string; 291 316 updatedAt: string; 292 317 }[]; ··· 296 321 ``` 297 322 298 323 #### GetProfileResponse 324 + 299 325 ```tsx 300 326 export interface GetProfileResponse extends User {} 301 327 ``` 302 328 303 329 #### GetUrlStatusForMyLibraryResponse 330 + 304 331 ```tsx 305 332 export interface GetUrlStatusForMyLibraryResponse { 306 333 cardId?: string; 307 - collections?: Collection[]; // Uses unified Collection interface 334 + collections?: Collection[]; // Uses unified Collection interface 308 335 } 309 336 ``` 310 337 311 338 #### FeedItem 339 + 312 340 ```tsx 313 341 export interface FeedItem { 314 342 id: string; 315 - user: User; // Changed from FeedActivityActor 316 - card: UrlCard; // Changed from FeedActivityCard, now includes author 343 + user: User; // Changed from FeedActivityActor 344 + card: UrlCard; // Changed from FeedActivityCard, now includes author 317 345 createdAt: Date; 318 - collections: Collection[]; // Changed to use unified Collection 346 + collections: Collection[]; // Changed to use unified Collection 319 347 } 320 348 ``` 321 349 ··· 328 356 **File**: `src/modules/cards/domain/ICardQueryRepository.ts` 329 357 330 358 #### Add authorId to UrlCardView 359 + 331 360 ```tsx 332 361 export interface UrlCardView { 333 362 id: string; ··· 345 374 urlInLibrary?: boolean; 346 375 createdAt: Date; 347 376 updatedAt: Date; 348 - authorId: string; // NEW - needed to enrich with author profile 377 + authorId: string; // NEW - needed to enrich with author profile 349 378 note?: { 350 379 id: string; 351 380 text: string; ··· 354 383 ``` 355 384 356 385 #### Update LibraryForUrlDTO 386 + 357 387 ```tsx 358 388 // Repository returns card data - will be enriched with user profile in use case 359 389 export interface LibraryForUrlDTO { ··· 383 413 ``` 384 414 385 415 **Implementation Impact**: 416 + 386 417 - Update SQL queries in `DrizzleCardQueryRepository` to include `cards.curatorId as authorId` in all `UrlCardView` queries 387 418 - Update `getLibrariesForUrl` query to return full card data (similar to how `getUrlCardsOfUser` works) 388 419 - No need to join with profiles table - enrichment happens in use case (following the pattern from `GetCollectionsForUrlUseCase`) ··· 402 433 **File**: `src/modules/cards/application/useCases/queries/GetUrlCardViewUseCase.ts` 403 434 404 435 **Changes**: 436 + 405 437 - Fetch author profile for the card using `cardView.authorId` 406 438 - Transform `libraries` to include full user profiles (already done) 407 439 - Transform `collections` to include full Collection objects (new) 408 440 409 441 **New Code**: 442 + 410 443 ```tsx 411 444 // After fetching cardView, fetch the card author 412 445 const cardAuthorResult = await this.profileService.getProfile( 413 446 cardView.authorId, 414 - query.callingUserId 447 + query.callingUserId, 415 448 ); 416 449 417 450 if (cardAuthorResult.isErr()) { 418 - return err(new Error(`Failed to fetch card author: ${cardAuthorResult.error.message}`)); 451 + return err( 452 + new Error(`Failed to fetch card author: ${cardAuthorResult.error.message}`), 453 + ); 419 454 } 420 455 421 456 const cardAuthor = cardAuthorResult.value; 422 457 423 458 // Enrich collections with full Collection data 424 - const collectionIds = cardView.collections.map(c => c.id); 459 + const collectionIds = cardView.collections.map((c) => c.id); 425 460 const enrichedCollections: Collection[] = await Promise.all( 426 461 collectionIds.map(async (id) => { 427 462 const collectionResult = await this.collectionRepo.findById( 428 - CollectionId.createFromString(id).value 463 + CollectionId.createFromString(id).value, 429 464 ); 430 465 // ... fetch collection and author, build Collection object 431 - }) 466 + }), 432 467 ); 433 468 434 469 const result: UrlCardViewResult = { ··· 446 481 ``` 447 482 448 483 **Dependencies**: 484 + 449 485 - Needs `ICollectionRepository` injected 450 486 - Repository must return `authorId` on `UrlCardView` 451 487 ··· 454 490 **File**: `src/modules/cards/application/useCases/queries/GetUrlCardsUseCase.ts` 455 491 456 492 **Changes**: 493 + 457 494 - Fetch author profiles for all cards 458 495 - Transform `collections` for each card to full Collection objects 459 496 460 497 **New Code**: 498 + 461 499 ```tsx 462 500 // After fetching cards from repository 463 501 const uniqueAuthorIds = Array.from( 464 - new Set(result.items.map(card => card.authorId)) 502 + new Set(result.items.map((card) => card.authorId)), 465 503 ); 466 504 467 505 const authorProfiles = new Map<string, User>(); 468 506 const profileResults = await Promise.all( 469 - uniqueAuthorIds.map(id => this.profileService.getProfile(id, query.callingUserId)) 507 + uniqueAuthorIds.map((id) => 508 + this.profileService.getProfile(id, query.callingUserId), 509 + ), 470 510 ); 471 511 472 512 // Build author map... 473 513 474 514 // Enrich cards with author data 475 - const enrichedCards = result.items.map(card => { 515 + const enrichedCards = result.items.map((card) => { 476 516 const author = authorProfiles.get(card.authorId); 477 517 // ... also enrich collections 478 518 return { ··· 484 524 ``` 485 525 486 526 **Dependencies**: 527 + 487 528 - Needs `IProfileService` injected 488 529 - Needs `ICollectionRepository` injected for collection enrichment 489 530 - Repository must return `authorId` on cards ··· 493 534 **File**: `src/modules/cards/application/useCases/queries/GetCollectionPageUseCase.ts` 494 535 495 536 **Changes**: 537 + 496 538 - Enrich URL cards with author profiles 497 539 - Add `cardCount`, `createdAt`, `updatedAt` to response 498 540 499 541 **New Code**: 542 + 500 543 ```tsx 501 544 // After fetching cards in collection 502 545 const uniqueAuthorIds = Array.from( ··· 527 570 ``` 528 571 529 572 **Dependencies**: 573 + 530 574 - Already has `IProfileService` 531 575 - Repository must return `authorId` on cards 532 576 ··· 535 579 **File**: `src/modules/cards/application/useCases/queries/GetCollectionsUseCase.ts` 536 580 537 581 **Changes**: 582 + 538 583 - Change `createdBy` to `author` in DTO mapping 539 584 - Ensure `cardCount`, `createdAt`, `updatedAt` are included (already present) 540 585 - Add `description` field to author 541 586 542 587 **Updated Code**: 588 + 543 589 ```tsx 544 590 const enrichedCollections: CollectionListItemDTO[] = result.items.map( 545 591 (item) => { ··· 551 597 updatedAt: item.updatedAt, 552 598 createdAt: item.createdAt, 553 599 cardCount: item.cardCount, 554 - author: { // Changed from 'createdBy' 600 + author: { 601 + // Changed from 'createdBy' 555 602 id: profile.id, 556 603 name: profile.name, 557 604 handle: profile.handle, 558 605 avatarUrl: profile.avatarUrl, 559 - description: profile.bio, // NEW 606 + description: profile.bio, // NEW 560 607 }, 561 608 }; 562 609 }, ··· 570 617 **File**: `src/modules/cards/application/useCases/queries/GetCollectionsForUrlUseCase.ts` 571 618 572 619 **Changes**: 620 + 573 621 - Add `cardCount`, `createdAt`, `updatedAt` to collection DTOs 574 622 - Add `description` to author 575 623 576 624 **Updated Code**: 625 + 577 626 ```tsx 578 627 // Need to fetch full collection objects to get cardCount, dates 579 628 const enrichedCollections: CollectionForUrlDTO[] = await Promise.all( ··· 585 634 586 635 // Fetch full collection to get cardCount, dates 587 636 const collectionResult = await this.collectionRepo.findById( 588 - CollectionId.createFromString(item.id).value 637 + CollectionId.createFromString(item.id).value, 589 638 ); 590 639 const collection = collectionResult.value; 591 640 ··· 598 647 ...author, 599 648 description: profileMap.get(item.authorId)?.description, 600 649 }, 601 - cardCount: collection.cardCount, // NEW 602 - createdAt: collection.createdAt.toISOString(), // NEW 603 - updatedAt: collection.updatedAt.toISOString(), // NEW 650 + cardCount: collection.cardCount, // NEW 651 + createdAt: collection.createdAt.toISOString(), // NEW 652 + updatedAt: collection.updatedAt.toISOString(), // NEW 604 653 }; 605 - }) 654 + }), 606 655 ); 607 656 ``` 608 657 609 658 **Dependencies**: 659 + 610 660 - Needs `ICollectionRepository` injected 611 661 612 662 ### 6. GetLibrariesForUrlUseCase ··· 614 664 **File**: `src/modules/cards/application/useCases/queries/GetLibrariesForUrlUseCase.ts` 615 665 616 666 **Changes**: 667 + 617 668 - Repository now returns full card data in `LibraryForUrlDTO` 618 669 - Enrich with user profiles for each library owner 619 670 - Transform to return both `user` and `card` objects 620 671 621 672 **Updated Code** (following pattern from `GetCollectionsForUrlUseCase`): 673 + 622 674 ```tsx 623 675 async execute( 624 676 query: GetLibrariesForUrlQuery, ··· 737 789 ``` 738 790 739 791 **Dependencies**: 792 + 740 793 - Needs `IProfileService` injected (add if not already present) 741 794 - Repository must return full card data in `LibraryForUrlDTO` 742 795 ··· 745 798 **File**: `src/modules/cards/application/useCases/queries/GetNoteCardsForUrlUseCase.ts` 746 799 747 800 **Changes**: 801 + 748 802 - Add `description` field to enriched author objects 749 803 750 804 **Updated Code**: 805 + 751 806 ```tsx 752 807 profileMap.set(authorId, { 753 808 id: profile.id, 754 809 name: profile.name, 755 810 handle: profile.handle, 756 811 avatarUrl: profile.avatarUrl, 757 - description: profile.bio, // NEW 812 + description: profile.bio, // NEW 758 813 }); 759 814 ``` 760 815 ··· 765 820 **File**: `src/modules/cards/application/useCases/queries/GetLibrariesForCardUseCase.ts` 766 821 767 822 **Changes**: 823 + 768 824 - Add `description` field to user DTOs 769 825 770 826 **Updated Code**: 827 + 771 828 ```tsx 772 829 users.push({ 773 830 id: profile.id, 774 831 name: profile.name, 775 832 handle: profile.handle, 776 833 avatarUrl: profile.avatarUrl, 777 - description: profile.bio, // NEW 834 + description: profile.bio, // NEW 778 835 }); 779 836 ``` 780 837 ··· 791 848 **File**: `src/modules/cards/application/useCases/queries/GetUrlStatusForMyLibraryUseCase.ts` 792 849 793 850 **Changes**: 851 + 794 852 - Enrich collections with full Collection objects (add `author`, `cardCount`, `createdAt`, `updatedAt`) 795 853 796 854 **New Code**: 855 + 797 856 ```tsx 798 857 // Instead of just mapping simple collection info 799 858 result.collections = await Promise.all( 800 859 collections.map(async (collection) => { 801 860 // Fetch full collection to get dates and cardCount 802 861 const collectionResult = await this.collectionRepo.findById( 803 - CollectionId.createFromString(collection.id).value 862 + CollectionId.createFromString(collection.id).value, 804 863 ); 805 864 const fullCollection = collectionResult.value; 806 865 807 866 // Fetch author profile 808 867 const authorProfile = await this.profileService.getProfile( 809 - fullCollection.authorId.value 868 + fullCollection.authorId.value, 810 869 ); 811 870 812 871 return { ··· 825 884 createdAt: fullCollection.createdAt.toISOString(), 826 885 updatedAt: fullCollection.updatedAt.toISOString(), 827 886 }; 828 - }) 887 + }), 829 888 ); 830 889 ``` 831 890 832 891 **Dependencies**: 892 + 833 893 - Needs `ICollectionRepository` injected 834 894 - Needs `IProfileService` injected 835 895 ··· 838 898 **File**: `src/modules/feeds/application/useCases/queries/GetGlobalFeedUseCase.ts` 839 899 840 900 **Changes**: 901 + 841 902 - Add author to card data 842 903 - Enrich collections with full Collection objects 843 904 - Use unified `User` type for actors 844 905 845 906 **New Code**: 907 + 846 908 ```tsx 847 909 // After hydrating card data, also fetch card authors 848 910 const uniqueCardAuthorIds = Array.from( 849 - new Set( 850 - Array.from(cardDataMap.values()).map(card => card.authorId) 851 - ) 911 + new Set(Array.from(cardDataMap.values()).map((card) => card.authorId)), 852 912 ); 853 913 854 914 const cardAuthorProfiles = new Map<string, User>(); ··· 861 921 { 862 922 ...card, 863 923 author: cardAuthorProfiles.get(card.authorId), 864 - } 865 - ]) 924 + }, 925 + ]), 866 926 ); 867 927 868 928 // When enriching collections, build full Collection objects ··· 872 932 // Use enriched data in feed items 873 933 feedItems.push({ 874 934 id: activity.activityId.getStringValue(), 875 - user: actor, // Already using unified User type 935 + user: actor, // Already using unified User type 876 936 card: { 877 937 ...cardData, 878 938 author: cardAuthorProfiles.get(cardData.authorId), ··· 883 943 ``` 884 944 885 945 **Dependencies**: 946 + 886 947 - Repository must return `authorId` on cards 887 948 - Already has access to profile and collection services 888 949 ··· 891 952 ## Implementation Steps 892 953 893 954 ### Phase 1: Repository Updates 955 + 894 956 1. Update `UrlCardView` in `ICardQueryRepository.ts` to include `authorId` 895 957 2. Update `LibraryForUrlDTO` in `ICardQueryRepository.ts` to include full card data (url, cardContent, libraryCount, etc.) 896 958 3. Update `DrizzleCardQueryRepository` to: ··· 898 960 - Update `getLibrariesForUrl` query to return full card data (similar to `getUrlCardsOfUser`) 899 961 900 962 ### Phase 2: Response Type Updates 963 + 901 964 1. Add unified `User` interface in `responses.ts` 902 965 2. Add unified `UrlCard` interface with `author: User` field 903 966 3. Add context-specific variations (`UrlCardWithCollections`, etc.) ··· 906 969 6. Remove deprecated types (`LibraryUser`, `UserProfile`, `FeedActivityActor`, etc.) 907 970 908 971 ### Phase 3: Use Case Updates (in order of dependency) 972 + 909 973 1. **GetProfileUseCase** - Add `description` field (minimal change) 910 974 2. **GetLibrariesForCardUseCase** - Add `description` to user DTOs 911 975 3. **GetNoteCardsForUrlUseCase** - Add `description` to author ··· 919 983 11. **GetGlobalFeedUseCase** - Add card authors, enrich collections 920 984 921 985 ### Phase 4: API Client Updates 986 + 922 987 1. Update type imports in `ApiClient.ts` 923 988 2. Update client method signatures to use new response types 924 989 3. Verify all endpoints return the expected unified types 925 990 926 991 ### Phase 5: Frontend Updates (Out of Scope) 992 + 927 993 - Update UI components to consume unified types 928 994 - Remove any frontend-specific type transformations 929 995 - Ensure components work with all endpoints ··· 933 999 ## Testing Strategy 934 1000 935 1001 ### Unit Tests 1002 + 936 1003 - Test each use case with new response structure 937 1004 - Verify author enrichment for cards 938 1005 - Verify collection enrichment with full data 939 1006 - Test error handling when profile/collection fetches fail 940 1007 941 1008 ### Integration Tests 1009 + 942 1010 - Test API endpoints return correctly shaped data 943 1011 - Verify all fields are present in responses 944 1012 - Test that shared UI components work with data from different endpoints 945 1013 946 1014 ### Manual Testing 1015 + 947 1016 - Test each page/view that displays users, cards, and collections 948 1017 - Verify no regressions in existing functionality 949 1018 - Verify new author field displays correctly on cards ··· 953 1022 ## Migration Notes 954 1023 955 1024 ### Breaking Changes 1025 + 956 1026 - All response types have changed 957 1027 - Frontend code consuming these types will need updates 958 1028 - Any DTOs imported from responses.ts will need to be updated 959 1029 960 1030 ### Backwards Compatibility 1031 + 961 1032 - No backwards compatibility at API client level 962 1033 - This is a one-time migration 963 1034 - Coordinate with frontend team for deployment
+7 -1
src/modules/cards/application/useCases/queries/GetCollectionsForUrlUseCase.ts
··· 111 111 // Create a map of profiles 112 112 const profileMap = new Map< 113 113 string, 114 - { id: string; name: string; handle: string; avatarUrl?: string; description?: string } 114 + { 115 + id: string; 116 + name: string; 117 + handle: string; 118 + avatarUrl?: string; 119 + description?: string; 120 + } 115 121 >(); 116 122 117 123 for (let i = 0; i < uniqueAuthorIds.length; i++) {
+7 -1
src/modules/cards/application/useCases/queries/GetNoteCardsForUrlUseCase.ts
··· 101 101 // Create a map of profiles 102 102 const profileMap = new Map< 103 103 string, 104 - { id: string; name: string; handle: string; avatarUrl?: string; description?: string } 104 + { 105 + id: string; 106 + name: string; 107 + handle: string; 108 + avatarUrl?: string; 109 + description?: string; 110 + } 105 111 >(); 106 112 107 113 for (let i = 0; i < uniqueAuthorIds.length; i++) {
+3 -2
src/modules/cards/application/useCases/queries/GetUrlCardViewUseCase.ts
··· 149 149 // Enrich collections with full Collection data 150 150 const enrichedCollections = await Promise.all( 151 151 cardView.collections.map(async (collection) => { 152 - const collectionIdResult = 153 - CollectionId.createFromString(collection.id); 152 + const collectionIdResult = CollectionId.createFromString( 153 + collection.id, 154 + ); 154 155 if (collectionIdResult.isErr()) { 155 156 throw new Error(`Invalid collection ID: ${collection.id}`); 156 157 }
+2 -6
src/modules/cards/application/useCases/queries/GetUrlStatusForMyLibraryUseCase.ts
··· 119 119 collection.id, 120 120 ); 121 121 if (collectionIdResult.isErr()) { 122 - throw new Error( 123 - `Invalid collection ID: ${collection.id}`, 124 - ); 122 + throw new Error(`Invalid collection ID: ${collection.id}`); 125 123 } 126 124 const collectionResult = await this.collectionRepo.findById( 127 125 collectionIdResult.value, 128 126 ); 129 127 if (collectionResult.isErr() || !collectionResult.value) { 130 - throw new Error( 131 - `Collection not found: ${collection.id}`, 132 - ); 128 + throw new Error(`Collection not found: ${collection.id}`); 133 129 } 134 130 const fullCollection = collectionResult.value; 135 131
+10 -3
src/modules/cards/tests/application/GetLibrariesForUrlUseCase.test.ts
··· 28 28 ); 29 29 profileService = new FakeProfileService(); 30 30 31 - useCase = new GetLibrariesForUrlUseCase(cardQueryRepository, profileService); 31 + useCase = new GetLibrariesForUrlUseCase( 32 + cardQueryRepository, 33 + profileService, 34 + ); 32 35 33 36 curator1 = CuratorId.create('did:plc:curator1').unwrap(); 34 37 curator2 = CuratorId.create('did:plc:curator2').unwrap(); ··· 193 196 194 197 expect(response.libraries).toHaveLength(1); 195 198 expect(response.libraries[0]!.user.id).toBe(curator1.value); 196 - expect(response.libraries[0]!.card.id).toBe(card1.cardId.getStringValue()); 199 + expect(response.libraries[0]!.card.id).toBe( 200 + card1.cardId.getStringValue(), 201 + ); 197 202 }); 198 203 }); 199 204 ··· 244 249 245 250 const result1 = await useCase.execute(query1); 246 251 if (result1.isErr()) { 247 - throw new Error(`Use case failed: ${result1.error.message || result1.error}`); 252 + throw new Error( 253 + `Use case failed: ${result1.error.message || result1.error}`, 254 + ); 248 255 } 249 256 expect(result1.isOk()).toBe(true); 250 257 const response1 = result1.unwrap();
+10 -2
src/modules/cards/tests/application/GetUrlCardViewUseCase.test.ts
··· 27 27 collectionRepo = new InMemoryCollectionRepository(); 28 28 cardQueryRepo = new InMemoryCardQueryRepository(cardRepo, collectionRepo); 29 29 profileService = new FakeProfileService(); 30 - useCase = new GetUrlCardViewUseCase(cardQueryRepo, profileService, collectionRepo); 30 + useCase = new GetUrlCardViewUseCase( 31 + cardQueryRepo, 32 + profileService, 33 + collectionRepo, 34 + ); 31 35 32 36 curatorId = CuratorId.create('did:plc:testcurator').unwrap(); 33 37 otherCuratorId = CuratorId.create('did:plc:othercurator').unwrap(); ··· 479 483 getNoteCardsForUrl: jest.fn(), 480 484 }; 481 485 482 - const errorUseCase = new GetUrlCardViewUseCase(errorRepo, profileService, collectionRepo); 486 + const errorUseCase = new GetUrlCardViewUseCase( 487 + errorRepo, 488 + profileService, 489 + collectionRepo, 490 + ); 483 491 484 492 const query = { 485 493 cardId: cardId,
+2 -2
src/webapp/api-client/types/responses.ts
··· 108 108 libraries: User[]; 109 109 } 110 110 111 - export interface GetUrlCardViewResponse extends UrlCardWithCollectionsAndLibraries {} 111 + export interface GetUrlCardViewResponse 112 + extends UrlCardWithCollectionsAndLibraries {} 112 113 113 114 // Unified User interface - used across all endpoints 114 115 export interface User { ··· 126 127 } 127 128 128 129 export interface GetProfileResponse extends User {} 129 - 130 130 131 131 // Base pagination interface 132 132 export interface Pagination {