tangled
alpha
login
or
join now
cosmik.network
/
semble
43
fork
atom
A social knowledge tool for researchers built on ATProto
43
fork
atom
overview
issues
13
pulls
pipelines
formatting and linting
Wesley Finck
4 months ago
f78ce6a4
678e8cd7
+159
-64
8 changed files
expand all
collapse all
unified
split
docs
plan
api_client_type_unification.md
src
modules
cards
application
useCases
queries
GetCollectionsForUrlUseCase.ts
GetNoteCardsForUrlUseCase.ts
GetUrlCardViewUseCase.ts
GetUrlStatusForMyLibraryUseCase.ts
tests
application
GetLibrariesForUrlUseCase.test.ts
GetUrlCardViewUseCase.test.ts
webapp
api-client
types
responses.ts
+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
+
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
23
+
22
24
```tsx
23
23
-
{ id, name, handle, avatarUrl }
25
25
+
{
26
26
+
(id, name, handle, avatarUrl);
27
27
+
}
24
28
```
25
29
26
30
2. **`UserProfile`** (responses.ts:109-115)
31
31
+
27
32
```tsx
28
28
-
{ id, name, handle, description, avatarUrl }
33
33
+
{
34
34
+
(id, name, handle, description, avatarUrl);
35
35
+
}
29
36
```
30
37
31
38
3. **`FeedActivityActor`** (responses.ts:259-264)
39
39
+
32
40
```tsx
33
33
-
{ id, name, handle, avatarUrl }
41
41
+
{
42
42
+
(id, name, handle, avatarUrl);
43
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
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
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
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
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
154
-
author: User; // NEW - currently missing!
168
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
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
198
-
author: User; // Standardize to 'author', not 'createdBy'
213
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
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
230
+
214
231
```tsx
215
215
-
export interface GetUrlCardViewResponse extends UrlCardWithCollectionsAndLibraries {}
232
232
+
export interface GetUrlCardViewResponse
233
233
+
extends UrlCardWithCollectionsAndLibraries {}
216
234
```
217
235
218
236
#### GetUrlCardsResponse
237
237
+
219
238
```tsx
220
239
export interface GetUrlCardsResponse {
221
221
-
cards: UrlCardWithCollections[]; // Changed from UrlCardListItem[]
240
240
+
cards: UrlCardWithCollections[]; // Changed from UrlCardListItem[]
222
241
pagination: Pagination;
223
242
sorting: CardSorting;
224
243
}
225
244
```
226
245
227
246
#### GetCollectionPageResponse
247
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
235
-
urlCards: UrlCard[]; // Changed from CollectionPageUrlCard[], now includes author
236
236
-
cardCount: number; // NEW
237
237
-
createdAt: string; // NEW
238
238
-
updatedAt: string; // NEW
255
255
+
urlCards: UrlCard[]; // Changed from CollectionPageUrlCard[], now includes author
256
256
+
cardCount: number; // NEW
257
257
+
createdAt: string; // NEW
258
258
+
updatedAt: string; // NEW
239
259
pagination: Pagination;
240
260
sorting: CardSorting;
241
261
}
242
262
```
243
263
244
264
#### GetCollectionsResponse
265
265
+
245
266
```tsx
246
267
export interface GetCollectionsResponse {
247
247
-
collections: Collection[]; // Uses unified Collection interface
268
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
275
+
254
276
```tsx
255
277
export interface GetCollectionsForUrlResponse {
256
256
-
collections: Collection[]; // Uses unified Collection interface
278
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
285
+
263
286
```tsx
264
287
export interface GetLibrariesForCardResponse {
265
288
cardId: string;
266
266
-
users: User[]; // Changed from LibraryUser[]
289
289
+
users: User[]; // Changed from LibraryUser[]
267
290
totalCount: number;
268
291
}
269
292
```
270
293
271
294
#### GetLibrariesForUrlResponse
295
295
+
272
296
```tsx
273
297
export interface GetLibrariesForUrlResponse {
274
298
libraries: {
275
275
-
user: User; // The user who has this URL in their library
299
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
308
+
284
309
```tsx
285
310
export interface GetNoteCardsForUrlResponse {
286
311
notes: {
287
312
id: string;
288
313
note: string;
289
289
-
author: User; // Changed to use unified User interface
314
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
324
+
299
325
```tsx
300
326
export interface GetProfileResponse extends User {}
301
327
```
302
328
303
329
#### GetUrlStatusForMyLibraryResponse
330
330
+
304
331
```tsx
305
332
export interface GetUrlStatusForMyLibraryResponse {
306
333
cardId?: string;
307
307
-
collections?: Collection[]; // Uses unified Collection interface
334
334
+
collections?: Collection[]; // Uses unified Collection interface
308
335
}
309
336
```
310
337
311
338
#### FeedItem
339
339
+
312
340
```tsx
313
341
export interface FeedItem {
314
342
id: string;
315
315
-
user: User; // Changed from FeedActivityActor
316
316
-
card: UrlCard; // Changed from FeedActivityCard, now includes author
343
343
+
user: User; // Changed from FeedActivityActor
344
344
+
card: UrlCard; // Changed from FeedActivityCard, now includes author
317
345
createdAt: Date;
318
318
-
collections: Collection[]; // Changed to use unified Collection
346
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
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
348
-
authorId: string; // NEW - needed to enrich with author profile
377
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
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
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
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
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
414
-
query.callingUserId
447
447
+
query.callingUserId,
415
448
);
416
449
417
450
if (cardAuthorResult.isErr()) {
418
418
-
return err(new Error(`Failed to fetch card author: ${cardAuthorResult.error.message}`));
451
451
+
return err(
452
452
+
new Error(`Failed to fetch card author: ${cardAuthorResult.error.message}`),
453
453
+
);
419
454
}
420
455
421
456
const cardAuthor = cardAuthorResult.value;
422
457
423
458
// Enrich collections with full Collection data
424
424
-
const collectionIds = cardView.collections.map(c => c.id);
459
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
428
-
CollectionId.createFromString(id).value
463
463
+
CollectionId.createFromString(id).value,
429
464
);
430
465
// ... fetch collection and author, build Collection object
431
431
-
})
466
466
+
}),
432
467
);
433
468
434
469
const result: UrlCardViewResult = {
···
446
481
```
447
482
448
483
**Dependencies**:
484
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
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
498
+
461
499
```tsx
462
500
// After fetching cards from repository
463
501
const uniqueAuthorIds = Array.from(
464
464
-
new Set(result.items.map(card => card.authorId))
502
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
469
-
uniqueAuthorIds.map(id => this.profileService.getProfile(id, query.callingUserId))
507
507
+
uniqueAuthorIds.map((id) =>
508
508
+
this.profileService.getProfile(id, query.callingUserId),
509
509
+
),
470
510
);
471
511
472
512
// Build author map...
473
513
474
514
// Enrich cards with author data
475
475
-
const enrichedCards = result.items.map(card => {
515
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
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
537
+
496
538
- Enrich URL cards with author profiles
497
539
- Add `cardCount`, `createdAt`, `updatedAt` to response
498
540
499
541
**New Code**:
542
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
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
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
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
554
-
author: { // Changed from 'createdBy'
600
600
+
author: {
601
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
559
-
description: profile.bio, // NEW
606
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
620
+
573
621
- Add `cardCount`, `createdAt`, `updatedAt` to collection DTOs
574
622
- Add `description` to author
575
623
576
624
**Updated Code**:
625
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
588
-
CollectionId.createFromString(item.id).value
637
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
601
-
cardCount: collection.cardCount, // NEW
602
602
-
createdAt: collection.createdAt.toISOString(), // NEW
603
603
-
updatedAt: collection.updatedAt.toISOString(), // NEW
650
650
+
cardCount: collection.cardCount, // NEW
651
651
+
createdAt: collection.createdAt.toISOString(), // NEW
652
652
+
updatedAt: collection.updatedAt.toISOString(), // NEW
604
653
};
605
605
-
})
654
654
+
}),
606
655
);
607
656
```
608
657
609
658
**Dependencies**:
659
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
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
673
+
622
674
```tsx
623
675
async execute(
624
676
query: GetLibrariesForUrlQuery,
···
737
789
```
738
790
739
791
**Dependencies**:
792
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
801
+
748
802
- Add `description` field to enriched author objects
749
803
750
804
**Updated Code**:
805
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
757
-
description: profile.bio, // NEW
812
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
823
+
768
824
- Add `description` field to user DTOs
769
825
770
826
**Updated Code**:
827
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
777
-
description: profile.bio, // NEW
834
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
851
+
794
852
- Enrich collections with full Collection objects (add `author`, `cardCount`, `createdAt`, `updatedAt`)
795
853
796
854
**New Code**:
855
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
803
-
CollectionId.createFromString(collection.id).value
862
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
809
-
fullCollection.authorId.value
868
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
828
-
})
887
887
+
}),
829
888
);
830
889
```
831
890
832
891
**Dependencies**:
892
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
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
907
+
846
908
```tsx
847
909
// After hydrating card data, also fetch card authors
848
910
const uniqueCardAuthorIds = Array.from(
849
849
-
new Set(
850
850
-
Array.from(cardDataMap.values()).map(card => card.authorId)
851
851
-
)
911
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
864
-
}
865
865
-
])
924
924
+
},
925
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
875
-
user: actor, // Already using unified User type
935
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
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
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
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
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
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
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
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
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
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
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
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
114
-
{ id: string; name: string; handle: string; avatarUrl?: string; description?: string }
114
114
+
{
115
115
+
id: string;
116
116
+
name: string;
117
117
+
handle: string;
118
118
+
avatarUrl?: string;
119
119
+
description?: string;
120
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
104
-
{ id: string; name: string; handle: string; avatarUrl?: string; description?: string }
104
104
+
{
105
105
+
id: string;
106
106
+
name: string;
107
107
+
handle: string;
108
108
+
avatarUrl?: string;
109
109
+
description?: string;
110
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
152
-
const collectionIdResult =
153
153
-
CollectionId.createFromString(collection.id);
152
152
+
const collectionIdResult = CollectionId.createFromString(
153
153
+
collection.id,
154
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
122
-
throw new Error(
123
123
-
`Invalid collection ID: ${collection.id}`,
124
124
-
);
122
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
130
-
throw new Error(
131
131
-
`Collection not found: ${collection.id}`,
132
132
-
);
128
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
31
-
useCase = new GetLibrariesForUrlUseCase(cardQueryRepository, profileService);
31
31
+
useCase = new GetLibrariesForUrlUseCase(
32
32
+
cardQueryRepository,
33
33
+
profileService,
34
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
196
-
expect(response.libraries[0]!.card.id).toBe(card1.cardId.getStringValue());
199
199
+
expect(response.libraries[0]!.card.id).toBe(
200
200
+
card1.cardId.getStringValue(),
201
201
+
);
197
202
});
198
203
});
199
204
···
244
249
245
250
const result1 = await useCase.execute(query1);
246
251
if (result1.isErr()) {
247
247
-
throw new Error(`Use case failed: ${result1.error.message || result1.error}`);
252
252
+
throw new Error(
253
253
+
`Use case failed: ${result1.error.message || result1.error}`,
254
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
30
-
useCase = new GetUrlCardViewUseCase(cardQueryRepo, profileService, collectionRepo);
30
30
+
useCase = new GetUrlCardViewUseCase(
31
31
+
cardQueryRepo,
32
32
+
profileService,
33
33
+
collectionRepo,
34
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
482
-
const errorUseCase = new GetUrlCardViewUseCase(errorRepo, profileService, collectionRepo);
486
486
+
const errorUseCase = new GetUrlCardViewUseCase(
487
487
+
errorRepo,
488
488
+
profileService,
489
489
+
collectionRepo,
490
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
111
-
export interface GetUrlCardViewResponse extends UrlCardWithCollectionsAndLibraries {}
111
111
+
export interface GetUrlCardViewResponse
112
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
129
-
130
130
131
131
// Base pagination interface
132
132
export interface Pagination {