@stamhoofd/backend 2.97.2 → 2.97.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.97.2",
3
+ "version": "2.97.3",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -45,14 +45,14 @@
45
45
  "@simonbackx/simple-encoding": "2.22.0",
46
46
  "@simonbackx/simple-endpoints": "1.20.1",
47
47
  "@simonbackx/simple-logging": "^1.0.1",
48
- "@stamhoofd/backend-i18n": "2.97.2",
49
- "@stamhoofd/backend-middleware": "2.97.2",
50
- "@stamhoofd/email": "2.97.2",
51
- "@stamhoofd/models": "2.97.2",
52
- "@stamhoofd/queues": "2.97.2",
53
- "@stamhoofd/sql": "2.97.2",
54
- "@stamhoofd/structures": "2.97.2",
55
- "@stamhoofd/utility": "2.97.2",
48
+ "@stamhoofd/backend-i18n": "2.97.3",
49
+ "@stamhoofd/backend-middleware": "2.97.3",
50
+ "@stamhoofd/email": "2.97.3",
51
+ "@stamhoofd/models": "2.97.3",
52
+ "@stamhoofd/queues": "2.97.3",
53
+ "@stamhoofd/sql": "2.97.3",
54
+ "@stamhoofd/structures": "2.97.3",
55
+ "@stamhoofd/utility": "2.97.3",
56
56
  "archiver": "^7.0.1",
57
57
  "axios": "^1.8.2",
58
58
  "cookie": "^0.7.0",
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "b8acc52aa065a07ff0689bd6cd0fd90a49a0c54e"
73
+ "gitHead": "254bc1658dc444653db96e7e4197b05035907a44"
74
74
  }
@@ -40,6 +40,11 @@ describe('Endpoint.GetUserEmails', () => {
40
40
  userToken = await Token.createToken(user);
41
41
  });
42
42
 
43
+ afterEach(async () => {
44
+ // Delete all emails
45
+ await Email.delete();
46
+ });
47
+
43
48
  const getUserEmails = async (query: LimitedFilteredRequest = new LimitedFilteredRequest({ limit: 10 }), token: Token = userToken, testOrganization: Organization = organization) => {
44
49
  const request = Request.get({
45
50
  path: baseUrl,
@@ -379,8 +384,7 @@ describe('Endpoint.GetUserEmails', () => {
379
384
 
380
385
  // Should prefer the exact match recipient
381
386
  expect(emailResult.recipients).toHaveLength(1);
382
- expect(emailResult.recipients[0].firstName).toBe('Exact');
383
- expect(emailResult.recipients[0].lastName).toBe('Match');
387
+ expect(emailResult.recipients[0].id).toBe(exactMatchRecipient.id);
384
388
  });
385
389
 
386
390
  test('Should return generic data when recipient has no matching user id or email', async () => {
@@ -420,12 +424,7 @@ describe('Endpoint.GetUserEmails', () => {
420
424
 
421
425
  // Should return the generic recipient data
422
426
  expect(emailResult.recipients).toHaveLength(1);
423
- expect(emailResult.recipients[0].firstName).toBe('Generic');
424
- expect(emailResult.recipients[0].lastName).toBe('Member');
425
- // The original recipient struct keeps its original userId and email
426
- expect(emailResult.recipients[0].userId).toBe(null); // Original was null
427
- expect(emailResult.recipients[0].email).toBe(null); // Original was null
428
- // But replacements should be generated for the current user (tested below)
427
+ expect(emailResult.recipients[0].id).toBe(genericRecipient.id);
429
428
  expect(emailResult.recipients[0].replacements).toBeDefined();
430
429
  });
431
430
 
@@ -440,7 +439,7 @@ describe('Endpoint.GetUserEmails', () => {
440
439
  email.subject = 'Sensitive Data Email';
441
440
  email.status = EmailStatus.Sent;
442
441
  email.text = 'Email with sensitive replacements {{outstandingBalance}} {{loginDetails}} {{unsubscribeUrl}}';
443
- email.html = '<p>Email with sensitive replacements {{outstandingBalance}} {{loginDetails}} {{unsubscribeUrl}}</p>';
442
+ email.html = '<p>Email with sensitive replacements {{outstandingBalance}} {{loginDetails}} {{unsubscribeUrl}}</p>{{balanceTable}}';
444
443
  email.json = {};
445
444
  email.organizationId = organization.id;
446
445
  email.showInMemberPortal = true;
@@ -498,9 +497,9 @@ describe('Endpoint.GetUserEmails', () => {
498
497
  expect(emailResult.recipients).toHaveLength(1);
499
498
  const recipient = emailResult.recipients[0];
500
499
 
501
- // The original recipient struct keeps its original userId and email from the sensitive user
502
- expect(recipient.userId).toBe(sensitiveUser.id); // Original userId
503
- expect(recipient.email).toBe(sensitiveUser.email); // Original email
500
+ // The original recipient struct keeps its original userId and email from the sensitive user, but returns different data:
501
+ expect(recipient.userId).toBe(user.id); // new userId
502
+ expect(recipient.email).toBe(user.email); // new email
504
503
 
505
504
  // Verify that sensitive data has been properly processed
506
505
  expect(recipient.replacements).toBeDefined();
@@ -552,6 +551,139 @@ describe('Endpoint.GetUserEmails', () => {
552
551
  expect(balanceTableReplacement!.html).toBe('<p class="description">' + $t('4c4f6571-f7b5-469d-a16f-b1547b43a610') + '</p>');
553
552
  });
554
553
 
554
+ test('Should return one recipient for each member the user is associated with, if the email is different for each member', async () => {
555
+ // Create another member associated with the same user
556
+ const secondMember = await new MemberFactory({
557
+ organization,
558
+ user,
559
+ }).create();
560
+
561
+ // Create an email
562
+ const email = new Email();
563
+ email.subject = 'Email for Multiple Members';
564
+ email.status = EmailStatus.Sent;
565
+ email.text = 'Member name: {{memberFirstName}}';
566
+ email.html = '<p>Member name: {{memberFirstName}}</p>';
567
+ email.json = {};
568
+ email.organizationId = organization.id;
569
+ email.showInMemberPortal = true;
570
+ email.sentAt = new Date();
571
+ await email.save();
572
+
573
+ // Create an email recipient linked to the FIRST member
574
+ const recipient1 = new EmailRecipient();
575
+ recipient1.emailId = email.id;
576
+ recipient1.memberId = member.id; // First member
577
+ recipient1.userId = user.id;
578
+ recipient1.email = user.email;
579
+ recipient1.firstName = member.details.firstName;
580
+ recipient1.lastName = member.details.lastName;
581
+ recipient1.replacements = [
582
+ Replacement.create({
583
+ token: 'memberFirstName',
584
+ value: member.details.firstName,
585
+ }),
586
+ ];
587
+ recipient1.sentAt = new Date();
588
+ await recipient1.save();
589
+
590
+ // Create an email recipient linked to the SECOND member
591
+ const recipient2 = new EmailRecipient();
592
+ recipient2.emailId = email.id;
593
+ recipient2.memberId = secondMember.id; // Second member
594
+ recipient2.userId = user.id;
595
+ recipient2.email = user.email;
596
+ recipient2.firstName = secondMember.details.firstName;
597
+ recipient2.lastName = secondMember.details.lastName;
598
+ recipient2.sentAt = new Date();
599
+ recipient2.replacements = [
600
+ Replacement.create({
601
+ token: 'memberFirstName',
602
+ value: secondMember.details.firstName,
603
+ }),
604
+ ];
605
+ await recipient2.save();
606
+
607
+ const response = await getUserEmails(
608
+ new LimitedFilteredRequest({ limit: 10, search: 'Email for Multiple Members' }),
609
+ );
610
+
611
+ expect(response.body.results).toHaveLength(1);
612
+ const emailResult = response.body.results[0];
613
+
614
+ // Should include both members as separate recipients
615
+ expect(emailResult.recipients).toHaveLength(2);
616
+ const firstNames = emailResult.recipients.map(r => r.member?.firstName);
617
+ expect(firstNames).toContain(member.details.firstName);
618
+ expect(firstNames).toContain(secondMember.details.firstName);
619
+ });
620
+
621
+ test('Should return a merged recipient for each member the user is associated with, if the email is the same for each member', async () => {
622
+ // Create another member associated with the same user
623
+ const secondMember = await new MemberFactory({
624
+ organization,
625
+ user,
626
+ }).create();
627
+
628
+ // Create an email
629
+ const email = new Email();
630
+ email.subject = 'Email for Multiple Members';
631
+ email.status = EmailStatus.Sent;
632
+ email.text = 'Same content';
633
+ email.html = '<p>Same content</p>';
634
+ email.json = {};
635
+ email.organizationId = organization.id;
636
+ email.showInMemberPortal = true;
637
+ email.sentAt = new Date();
638
+ await email.save();
639
+
640
+ // Create an email recipient linked to the FIRST member
641
+ const recipient1 = new EmailRecipient();
642
+ recipient1.emailId = email.id;
643
+ recipient1.memberId = member.id; // First member
644
+ recipient1.userId = user.id;
645
+ recipient1.email = user.email;
646
+ recipient1.firstName = member.details.firstName;
647
+ recipient1.lastName = member.details.lastName;
648
+ recipient1.replacements = [
649
+ // will get automatically removed because it is not used
650
+ Replacement.create({
651
+ token: 'memberFirstName',
652
+ value: member.details.firstName,
653
+ }),
654
+ ];
655
+ recipient1.sentAt = new Date();
656
+ await recipient1.save();
657
+
658
+ // Create an email recipient linked to the SECOND member
659
+ const recipient2 = new EmailRecipient();
660
+ recipient2.emailId = email.id;
661
+ recipient2.memberId = secondMember.id; // Second member
662
+ recipient2.userId = user.id;
663
+ recipient2.email = user.email;
664
+ recipient2.firstName = secondMember.details.firstName;
665
+ recipient2.lastName = secondMember.details.lastName;
666
+ recipient2.sentAt = new Date();
667
+ recipient2.replacements = [
668
+ // will get automatically removed because it is not used
669
+ Replacement.create({
670
+ token: 'memberFirstName',
671
+ value: secondMember.details.firstName,
672
+ }),
673
+ ];
674
+ await recipient2.save();
675
+
676
+ const response = await getUserEmails(
677
+ new LimitedFilteredRequest({ limit: 10, search: 'Email for Multiple Members' }),
678
+ );
679
+
680
+ expect(response.body.results).toHaveLength(1);
681
+ const emailResult = response.body.results[0];
682
+
683
+ // Should include both members as separate recipients
684
+ expect(emailResult.recipients).toHaveLength(1);
685
+ });
686
+
555
687
  test('Should not return emails from other members the user does not have access to', async () => {
556
688
  // Create another user and member
557
689
  const otherUser = await new UserFactory({