This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

add new method to fetch collections with a card added by a specific user

+217 -3
+4
src/modules/cards/domain/ICollectionRepository.ts
··· 12 12 authorId: CuratorId, 13 13 cardId: CardId, 14 14 ): Promise<Result<Collection[]>>; 15 + findContainingCardAddedBy( 16 + cardId: CardId, 17 + addedBy: CuratorId, 18 + ): Promise<Result<Collection[]>>; 15 19 save(collection: Collection): Promise<Result<void>>; 16 20 delete(collectionId: CollectionId): Promise<Result<void>>; 17 21 }
+3 -3
src/modules/cards/domain/services/CardLibraryService.ts
··· 359 359 return ok(card); 360 360 } 361 361 362 - // Get all collections owned by the curator that contain this card 362 + // Get all collections where this curator added this card (regardless of collection ownership) 363 363 const collectionsResult = 364 - await this.collectionRepository.findByCuratorIdContainingCard( 364 + await this.collectionRepository.findContainingCardAddedBy( 365 + card.cardId, 365 366 curatorId, 366 - card.cardId, 367 367 ); 368 368 if (collectionsResult.isErr()) { 369 369 return err(AppError.UnexpectedError.create(collectionsResult.error));
+95
src/modules/cards/infrastructure/repositories/DrizzleCollectionRepository.ts
··· 390 390 } 391 391 } 392 392 393 + async findContainingCardAddedBy( 394 + cardId: CardId, 395 + addedBy: CuratorId, 396 + ): Promise<Result<Collection[]>> { 397 + try { 398 + const addedByString = addedBy.value; 399 + const cardIdString = cardId.getStringValue(); 400 + 401 + // Find collections that contain this card where it was added by the specified curator 402 + const collectionResults = await this.db 403 + .select({ 404 + collection: collections, 405 + publishedRecord: publishedRecords, 406 + }) 407 + .from(collections) 408 + .leftJoin( 409 + publishedRecords, 410 + eq(collections.publishedRecordId, publishedRecords.id), 411 + ) 412 + .innerJoin( 413 + collectionCards, 414 + eq(collections.id, collectionCards.collectionId), 415 + ) 416 + .where( 417 + and( 418 + eq(collectionCards.cardId, cardIdString), 419 + eq(collectionCards.addedBy, addedByString), 420 + ), 421 + ); 422 + 423 + const domainCollections: Collection[] = []; 424 + for (const result of collectionResults) { 425 + const collectionId = result.collection.id; 426 + 427 + // Get collaborators for this collection 428 + const collaboratorResults = await this.db 429 + .select() 430 + .from(collectionCollaborators) 431 + .where(eq(collectionCollaborators.collectionId, collectionId)); 432 + 433 + const collaborators = collaboratorResults.map((c) => c.collaboratorId); 434 + 435 + // Get card links for this collection 436 + const cardLinkResults = await this.db 437 + .select({ 438 + cardLink: collectionCards, 439 + publishedRecord: publishedRecords, 440 + }) 441 + .from(collectionCards) 442 + .leftJoin( 443 + publishedRecords, 444 + eq(collectionCards.publishedRecordId, publishedRecords.id), 445 + ) 446 + .where(eq(collectionCards.collectionId, collectionId)); 447 + 448 + const cardLinks = cardLinkResults.map((link) => ({ 449 + cardId: link.cardLink.cardId, 450 + addedBy: link.cardLink.addedBy, 451 + addedAt: link.cardLink.addedAt, 452 + publishedRecordId: link.publishedRecord?.id, 453 + publishedRecord: link.publishedRecord || undefined, 454 + })); 455 + 456 + const collectionDTO: CollectionDTO = { 457 + id: result.collection.id, 458 + authorId: result.collection.authorId, 459 + name: result.collection.name, 460 + description: result.collection.description || undefined, 461 + accessType: result.collection.accessType, 462 + cardCount: result.collection.cardCount, 463 + createdAt: result.collection.createdAt, 464 + updatedAt: result.collection.updatedAt, 465 + publishedRecordId: result.publishedRecord?.id || null, 466 + publishedRecord: result.publishedRecord || undefined, 467 + collaborators, 468 + cardLinks, 469 + }; 470 + 471 + const domainResult = CollectionMapper.toDomain(collectionDTO); 472 + if (domainResult.isErr()) { 473 + console.error( 474 + 'Error mapping collection to domain:', 475 + domainResult.error, 476 + ); 477 + continue; 478 + } 479 + domainCollections.push(domainResult.value); 480 + } 481 + 482 + return ok(domainCollections); 483 + } catch (error) { 484 + return err(error as Error); 485 + } 486 + } 487 + 393 488 async save(collection: Collection): Promise<Result<void>> { 394 489 try { 395 490 const {
+1
src/modules/cards/tests/application/GetCollectionPageUseCase.test.ts
··· 709 709 findByCuratorId: jest.fn(), 710 710 findByCardId: jest.fn(), 711 711 findByCuratorIdContainingCard: jest.fn(), 712 + findContainingCardAddedBy: jest.fn(), 712 713 }; 713 714 714 715 const errorUseCase = new GetCollectionPageUseCase(
+95
src/modules/cards/tests/infrastructure/DrizzleCollectionRepository.integration.test.ts
··· 570 570 expect(collection.authorId.value).toBe(curatorId.value); 571 571 }); 572 572 }); 573 + 574 + it('should find collections containing a card added by a specific user', async () => { 575 + // Create a card 576 + const cardResult = CardFactory.create({ 577 + curatorId: curatorId.value, 578 + cardInput: { 579 + type: CardTypeEnum.NOTE, 580 + text: 'Card added by different users', 581 + }, 582 + }); 583 + 584 + const card = cardResult.unwrap(); 585 + await cardRepository.save(card); 586 + 587 + // Collection 1: Owned by collaboratorId, card added by curatorId 588 + const collection1Id = new UniqueEntityID(); 589 + const collection1 = Collection.create( 590 + { 591 + authorId: collaboratorId, 592 + name: 'Collection Owned By Collaborator', 593 + accessType: CollectionAccessType.OPEN, 594 + collaboratorIds: [], 595 + createdAt: new Date(), 596 + updatedAt: new Date(), 597 + }, 598 + collection1Id, 599 + ).unwrap(); 600 + 601 + // curatorId adds card to collaborator's collection 602 + collection1.addCard(card.cardId, curatorId); 603 + 604 + // Collection 2: Owned by collaboratorId, card added by collaboratorId 605 + const collection2Id = new UniqueEntityID(); 606 + const collection2 = Collection.create( 607 + { 608 + authorId: collaboratorId, 609 + name: 'Collection With Card Added By Collaborator', 610 + accessType: CollectionAccessType.OPEN, 611 + collaboratorIds: [], 612 + createdAt: new Date(), 613 + updatedAt: new Date(), 614 + }, 615 + collection2Id, 616 + ).unwrap(); 617 + 618 + // collaboratorId adds card to their own collection 619 + collection2.addCard(card.cardId, collaboratorId); 620 + 621 + // Collection 3: Owned by curatorId, card added by curatorId 622 + const collection3Id = new UniqueEntityID(); 623 + const collection3 = Collection.create( 624 + { 625 + authorId: curatorId, 626 + name: 'Collection Owned And Added By Curator', 627 + accessType: CollectionAccessType.OPEN, 628 + collaboratorIds: [], 629 + createdAt: new Date(), 630 + updatedAt: new Date(), 631 + }, 632 + collection3Id, 633 + ).unwrap(); 634 + 635 + // curatorId adds card to their own collection 636 + collection3.addCard(card.cardId, curatorId); 637 + 638 + await collectionRepository.save(collection1); 639 + await collectionRepository.save(collection2); 640 + await collectionRepository.save(collection3); 641 + 642 + // Find collections where curatorId added the card 643 + const foundCollectionsResult = 644 + await collectionRepository.findContainingCardAddedBy( 645 + card.cardId, 646 + curatorId, 647 + ); 648 + expect(foundCollectionsResult.isOk()).toBe(true); 649 + 650 + const foundCollections = foundCollectionsResult.unwrap(); 651 + expect(foundCollections).toHaveLength(2); 652 + 653 + const names = foundCollections.map((c) => c.name.value); 654 + expect(names).toContain('Collection Owned By Collaborator'); 655 + expect(names).toContain('Collection Owned And Added By Curator'); 656 + expect(names).not.toContain('Collection With Card Added By Collaborator'); 657 + 658 + // Verify that collections where curatorId added the card are returned 659 + // regardless of who owns the collection 660 + foundCollections.forEach((collection) => { 661 + const cardLink = collection.cardLinks.find((link) => 662 + link.cardId.equals(card.cardId), 663 + ); 664 + expect(cardLink).toBeDefined(); 665 + expect(cardLink?.addedBy.value).toBe(curatorId.value); 666 + }); 667 + }); 573 668 });
+19
src/modules/cards/tests/utils/InMemoryCollectionRepository.ts
··· 100 100 } 101 101 } 102 102 103 + async findContainingCardAddedBy( 104 + cardId: CardId, 105 + addedBy: CuratorId, 106 + ): Promise<Result<Collection[]>> { 107 + try { 108 + const collections = Array.from(this.collections.values()).filter( 109 + (collection) => 110 + collection.cardLinks.some( 111 + (link) => 112 + link.cardId.getStringValue() === cardId.getStringValue() && 113 + link.addedBy.value === addedBy.value, 114 + ), 115 + ); 116 + return ok(collections.map((collection) => this.clone(collection))); 117 + } catch (error) { 118 + return err(error as Error); 119 + } 120 + } 121 + 103 122 async save(collection: Collection): Promise<Result<void>> { 104 123 try { 105 124 this.collections.set(