@stamhoofd/backend 2.105.0 → 2.106.1
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 +10 -10
- package/src/crons.ts +39 -5
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +953 -47
- package/src/endpoints/global/members/GetMembersEndpoint.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +142 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +1 -1
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +163 -8
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.test.ts +108 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +40 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +8 -1
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +8 -1
- package/src/helpers/AdminPermissionChecker.ts +30 -6
- package/src/helpers/AuthenticatedStructures.ts +2 -2
- package/src/helpers/MemberUserSyncer.test.ts +400 -1
- package/src/helpers/MemberUserSyncer.ts +15 -10
- package/src/helpers/ServiceFeeHelper.ts +63 -0
- package/src/helpers/StripeHelper.ts +7 -4
- package/src/helpers/StripePayoutChecker.ts +1 -1
- package/src/seeds/0000000001-development-user.ts +2 -2
- package/src/seeds/0000000004-single-organization.ts +60 -0
- package/src/seeds/1754560914-groups-prices.test.ts +3023 -0
- package/src/seeds/1754560914-groups-prices.ts +408 -0
- package/src/seeds/{1722344162-sync-member-users.ts → 1761665607-sync-member-users.ts} +1 -1
- package/src/sql-filters/members.ts +1 -1
- package/tests/init/initAdmin.ts +19 -5
- package/tests/init/initPermissionRole.ts +14 -4
- package/tests/init/initPlatformRecordCategory.ts +8 -0
|
@@ -434,8 +434,16 @@ export class AdminPermissionChecker {
|
|
|
434
434
|
*/
|
|
435
435
|
async canAccessRegistration(registration: Registration, permissionLevel: PermissionLevel = PermissionLevel.Read, checkMember: boolean | MemberWithRegistrations = true) {
|
|
436
436
|
if (registration.deactivatedAt || !registration.registeredAt) {
|
|
437
|
+
if (!checkMember) {
|
|
438
|
+
// We can't grant access to a member because of a deactivated registration
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
|
|
437
442
|
// No full access: cannot access deactivated registrations
|
|
438
|
-
|
|
443
|
+
if (permissionLevel !== PermissionLevel.Read) {
|
|
444
|
+
// Not allowed to edit registrations that are deleted
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
439
447
|
}
|
|
440
448
|
|
|
441
449
|
const organizationPermissions = await this.getOrganizationPermissions(registration.organizationId);
|
|
@@ -1293,6 +1301,14 @@ export class AdminPermissionChecker {
|
|
|
1293
1301
|
};
|
|
1294
1302
|
}
|
|
1295
1303
|
|
|
1304
|
+
// All member access, means also having access to record categories of non-registered members
|
|
1305
|
+
if (this.canAccessAllPlatformMembers(PermissionLevel.Read)) {
|
|
1306
|
+
return {
|
|
1307
|
+
canAccess: true,
|
|
1308
|
+
record: record.record,
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1296
1312
|
return {
|
|
1297
1313
|
canAccess: false,
|
|
1298
1314
|
record: record.record,
|
|
@@ -1362,6 +1378,14 @@ export class AdminPermissionChecker {
|
|
|
1362
1378
|
};
|
|
1363
1379
|
}
|
|
1364
1380
|
|
|
1381
|
+
// All member access, means also having access to record categories of non-registered members
|
|
1382
|
+
if (this.canAccessAllPlatformMembers(level)) { // needs to be full to also inherit record category access
|
|
1383
|
+
return {
|
|
1384
|
+
canAccess: true,
|
|
1385
|
+
record: record.record,
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1365
1389
|
if (hasTemporaryMemberAccess(this.user.id, member.id, PermissionLevel.Full)) {
|
|
1366
1390
|
// You created this member, so temporary can read all records in order to set the member up correctly
|
|
1367
1391
|
return {
|
|
@@ -1371,13 +1395,13 @@ export class AdminPermissionChecker {
|
|
|
1371
1395
|
}
|
|
1372
1396
|
|
|
1373
1397
|
// It is possible that this is a platform admin (or an admin that has access to multiple organizations), and inherits automatic permissions for tags. So'll need to loop all the organizations where this member has an active registration for
|
|
1374
|
-
if (!record.organizationId && !this.organization
|
|
1398
|
+
if (!record.organizationId && !this.organization) {
|
|
1375
1399
|
const checkedOrganizations = new Map<string, boolean>();
|
|
1376
1400
|
for (const registration of member.registrations) {
|
|
1377
1401
|
const permissions = checkedOrganizations.get(registration.organizationId);
|
|
1378
1402
|
|
|
1379
1403
|
// Checking the organization permissions is faster (and less data lookups required), so we do that first before doing the more expensive registration access check
|
|
1380
|
-
if (permissions !==
|
|
1404
|
+
if (permissions !== undefined) {
|
|
1381
1405
|
if (permissions === true && await this.canAccessRegistration(registration, level)) {
|
|
1382
1406
|
return {
|
|
1383
1407
|
canAccess: true,
|
|
@@ -1648,8 +1672,8 @@ export class AdminPermissionChecker {
|
|
|
1648
1672
|
return data;
|
|
1649
1673
|
}
|
|
1650
1674
|
|
|
1651
|
-
canAccessAllPlatformMembers(): boolean {
|
|
1652
|
-
return
|
|
1675
|
+
canAccessAllPlatformMembers(level: PermissionLevel): boolean {
|
|
1676
|
+
return this.getPlatformAccessibleOrganizationTags(level) === 'all';
|
|
1653
1677
|
}
|
|
1654
1678
|
|
|
1655
1679
|
canAccess(accessRight: AccessRight): boolean {
|
|
@@ -1682,7 +1706,7 @@ export class AdminPermissionChecker {
|
|
|
1682
1706
|
}
|
|
1683
1707
|
}
|
|
1684
1708
|
|
|
1685
|
-
if (tags.length === allTags.length) {
|
|
1709
|
+
if (tags.length > 0 && tags.length === allTags.length) {
|
|
1686
1710
|
return 'all';
|
|
1687
1711
|
}
|
|
1688
1712
|
|
|
@@ -937,7 +937,7 @@ export class AuthenticatedStructures {
|
|
|
937
937
|
]
|
|
938
938
|
: []),
|
|
939
939
|
|
|
940
|
-
...((member.details.
|
|
940
|
+
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
941
941
|
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
942
942
|
firstName: p.firstName ?? '',
|
|
943
943
|
lastName: p.lastName ?? '',
|
|
@@ -980,7 +980,7 @@ export class AuthenticatedStructures {
|
|
|
980
980
|
]
|
|
981
981
|
: []),
|
|
982
982
|
|
|
983
|
-
...((member.details.
|
|
983
|
+
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
984
984
|
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
985
985
|
firstName: p.firstName ?? '',
|
|
986
986
|
lastName: p.lastName ?? '',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Database } from '@simonbackx/simple-database';
|
|
2
2
|
import { Member, MemberFactory, MemberResponsibilityRecordFactory, User, UserFactory } from '@stamhoofd/models';
|
|
3
|
-
import { MemberDetails, Parent, UserPermissions } from '@stamhoofd/structures';
|
|
3
|
+
import { BooleanStatus, MemberDetails, Parent, UserPermissions } from '@stamhoofd/structures';
|
|
4
4
|
import { TestUtils } from '@stamhoofd/test-utils';
|
|
5
5
|
import { MemberUserSyncer } from './MemberUserSyncer';
|
|
6
6
|
|
|
@@ -19,6 +19,143 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
19
19
|
details: MemberDetails.create({
|
|
20
20
|
firstName: 'John',
|
|
21
21
|
lastName: 'Doe',
|
|
22
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
23
|
+
parents: [
|
|
24
|
+
Parent.create({
|
|
25
|
+
firstName: 'Linda',
|
|
26
|
+
lastName: 'Potter',
|
|
27
|
+
email: 'linda@example.com',
|
|
28
|
+
}),
|
|
29
|
+
Parent.create({
|
|
30
|
+
firstName: 'Peter',
|
|
31
|
+
lastName: 'Doe',
|
|
32
|
+
email: 'peter@example.com',
|
|
33
|
+
alternativeEmails: [
|
|
34
|
+
'peter@work.com',
|
|
35
|
+
],
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
email: 'john@example.com',
|
|
39
|
+
alternativeEmails: ['john@work.com'],
|
|
40
|
+
unverifiedEmails: ['untitled@example.com', 'peter@example.com'], // Last one should be ignored
|
|
41
|
+
}),
|
|
42
|
+
}).create();
|
|
43
|
+
|
|
44
|
+
await MemberUserSyncer.onChangeMember(member);
|
|
45
|
+
|
|
46
|
+
const users = await Member.users.load(member);
|
|
47
|
+
expect(users).toIncludeSameMembers([
|
|
48
|
+
// Member
|
|
49
|
+
expect.objectContaining({
|
|
50
|
+
firstName: 'John',
|
|
51
|
+
lastName: 'Doe',
|
|
52
|
+
email: 'john@example.com',
|
|
53
|
+
memberId: member.id,
|
|
54
|
+
}),
|
|
55
|
+
expect.objectContaining({
|
|
56
|
+
firstName: 'John',
|
|
57
|
+
lastName: 'Doe',
|
|
58
|
+
email: 'john@work.com',
|
|
59
|
+
memberId: member.id,
|
|
60
|
+
}),
|
|
61
|
+
|
|
62
|
+
// Parents
|
|
63
|
+
expect.objectContaining({
|
|
64
|
+
firstName: 'Linda',
|
|
65
|
+
lastName: 'Potter',
|
|
66
|
+
email: 'linda@example.com',
|
|
67
|
+
memberId: null,
|
|
68
|
+
permissions: null,
|
|
69
|
+
}),
|
|
70
|
+
expect.objectContaining({
|
|
71
|
+
firstName: 'Peter',
|
|
72
|
+
lastName: 'Doe',
|
|
73
|
+
email: 'peter@example.com',
|
|
74
|
+
memberId: null,
|
|
75
|
+
permissions: null,
|
|
76
|
+
}),
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
firstName: 'Peter',
|
|
79
|
+
lastName: 'Doe',
|
|
80
|
+
email: 'peter@work.com',
|
|
81
|
+
memberId: null,
|
|
82
|
+
permissions: null,
|
|
83
|
+
}),
|
|
84
|
+
|
|
85
|
+
// Unverified
|
|
86
|
+
expect.objectContaining({
|
|
87
|
+
firstName: null,
|
|
88
|
+
lastName: null,
|
|
89
|
+
email: 'untitled@example.com',
|
|
90
|
+
memberId: null,
|
|
91
|
+
permissions: null,
|
|
92
|
+
}),
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('Parents do not get access by default for adult members', async () => {
|
|
97
|
+
const member = await new MemberFactory({
|
|
98
|
+
details: MemberDetails.create({
|
|
99
|
+
firstName: 'John',
|
|
100
|
+
lastName: 'Doe',
|
|
101
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 366 * 18), // 18 years old + compensation for leap years
|
|
102
|
+
parents: [
|
|
103
|
+
Parent.create({
|
|
104
|
+
firstName: 'Linda',
|
|
105
|
+
lastName: 'Potter',
|
|
106
|
+
email: 'linda@example.com',
|
|
107
|
+
}),
|
|
108
|
+
Parent.create({
|
|
109
|
+
firstName: 'Peter',
|
|
110
|
+
lastName: 'Doe',
|
|
111
|
+
email: 'peter@example.com',
|
|
112
|
+
alternativeEmails: [
|
|
113
|
+
'peter@work.com',
|
|
114
|
+
],
|
|
115
|
+
}),
|
|
116
|
+
],
|
|
117
|
+
email: 'john@example.com',
|
|
118
|
+
alternativeEmails: ['john@work.com'],
|
|
119
|
+
unverifiedEmails: ['untitled@example.com', 'peter@example.com'], // Last one should be ignored
|
|
120
|
+
}),
|
|
121
|
+
}).create();
|
|
122
|
+
|
|
123
|
+
await MemberUserSyncer.onChangeMember(member);
|
|
124
|
+
|
|
125
|
+
const users = await Member.users.load(member);
|
|
126
|
+
expect(users).toIncludeSameMembers([
|
|
127
|
+
// Member
|
|
128
|
+
expect.objectContaining({
|
|
129
|
+
firstName: 'John',
|
|
130
|
+
lastName: 'Doe',
|
|
131
|
+
email: 'john@example.com',
|
|
132
|
+
memberId: member.id,
|
|
133
|
+
}),
|
|
134
|
+
expect.objectContaining({
|
|
135
|
+
firstName: 'John',
|
|
136
|
+
lastName: 'Doe',
|
|
137
|
+
email: 'john@work.com',
|
|
138
|
+
memberId: member.id,
|
|
139
|
+
}),
|
|
140
|
+
|
|
141
|
+
// Unverified
|
|
142
|
+
expect.objectContaining({
|
|
143
|
+
firstName: null,
|
|
144
|
+
lastName: null,
|
|
145
|
+
email: 'untitled@example.com',
|
|
146
|
+
memberId: null,
|
|
147
|
+
permissions: null,
|
|
148
|
+
}),
|
|
149
|
+
]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('Parents can get access for adult members if explicitly set', async () => {
|
|
153
|
+
const member = await new MemberFactory({
|
|
154
|
+
details: MemberDetails.create({
|
|
155
|
+
firstName: 'John',
|
|
156
|
+
lastName: 'Doe',
|
|
157
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 366 * 18), // 18 years old + compensation for leap years
|
|
158
|
+
parentsHaveAccess: BooleanStatus.create({ value: true }),
|
|
22
159
|
parents: [
|
|
23
160
|
Parent.create({
|
|
24
161
|
firstName: 'Linda',
|
|
@@ -104,6 +241,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
104
241
|
details: MemberDetails.create({
|
|
105
242
|
firstName: 'John',
|
|
106
243
|
lastName: 'Doe',
|
|
244
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
107
245
|
parents: [
|
|
108
246
|
Parent.create({
|
|
109
247
|
firstName: 'Linda',
|
|
@@ -192,6 +330,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
192
330
|
details: MemberDetails.create({
|
|
193
331
|
firstName: 'John',
|
|
194
332
|
lastName: 'Doe',
|
|
333
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
195
334
|
parents: [
|
|
196
335
|
Parent.create({
|
|
197
336
|
firstName: 'Linda',
|
|
@@ -225,6 +364,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
225
364
|
details: MemberDetails.create({
|
|
226
365
|
firstName: 'John',
|
|
227
366
|
lastName: 'Doe',
|
|
367
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
228
368
|
parents: [
|
|
229
369
|
Parent.create({
|
|
230
370
|
firstName: 'Linda',
|
|
@@ -258,6 +398,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
258
398
|
details: MemberDetails.create({
|
|
259
399
|
firstName: 'John',
|
|
260
400
|
lastName: 'Doe',
|
|
401
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
261
402
|
parents: [
|
|
262
403
|
Parent.create({
|
|
263
404
|
firstName: 'Linda',
|
|
@@ -291,6 +432,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
291
432
|
details: MemberDetails.create({
|
|
292
433
|
firstName: 'John',
|
|
293
434
|
lastName: 'Doe',
|
|
435
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
294
436
|
parents: [
|
|
295
437
|
Parent.create({
|
|
296
438
|
firstName: 'John',
|
|
@@ -322,6 +464,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
322
464
|
details: MemberDetails.create({
|
|
323
465
|
firstName: 'John',
|
|
324
466
|
lastName: 'Doe',
|
|
467
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
325
468
|
parents: [
|
|
326
469
|
Parent.create({
|
|
327
470
|
firstName: 'John',
|
|
@@ -353,6 +496,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
353
496
|
details: MemberDetails.create({
|
|
354
497
|
firstName: 'John',
|
|
355
498
|
lastName: 'Doe',
|
|
499
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
356
500
|
parents: [
|
|
357
501
|
Parent.create({
|
|
358
502
|
firstName: 'Linda',
|
|
@@ -466,6 +610,170 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
466
610
|
});
|
|
467
611
|
});
|
|
468
612
|
|
|
613
|
+
test('Parents are removed if access is revoked', async () => {
|
|
614
|
+
const member = await new MemberFactory({
|
|
615
|
+
details: MemberDetails.create({
|
|
616
|
+
firstName: 'John',
|
|
617
|
+
lastName: 'Doe',
|
|
618
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
619
|
+
parents: [
|
|
620
|
+
Parent.create({
|
|
621
|
+
firstName: 'Linda',
|
|
622
|
+
lastName: 'Potter',
|
|
623
|
+
email: 'linda@example.com',
|
|
624
|
+
}),
|
|
625
|
+
Parent.create({
|
|
626
|
+
firstName: 'Peter',
|
|
627
|
+
lastName: 'Doe',
|
|
628
|
+
email: 'peter@example.com',
|
|
629
|
+
alternativeEmails: [
|
|
630
|
+
'peter@work.com',
|
|
631
|
+
],
|
|
632
|
+
}),
|
|
633
|
+
],
|
|
634
|
+
email: 'john@example.com',
|
|
635
|
+
}),
|
|
636
|
+
}).create();
|
|
637
|
+
await new MemberResponsibilityRecordFactory({
|
|
638
|
+
member,
|
|
639
|
+
}).create();
|
|
640
|
+
|
|
641
|
+
await MemberUserSyncer.onChangeMember(member);
|
|
642
|
+
|
|
643
|
+
let users = await Member.users.load(member);
|
|
644
|
+
expect(users).toIncludeSameMembers([
|
|
645
|
+
// Member
|
|
646
|
+
expect.objectContaining({
|
|
647
|
+
firstName: 'John',
|
|
648
|
+
lastName: 'Doe',
|
|
649
|
+
email: 'john@example.com',
|
|
650
|
+
memberId: member.id,
|
|
651
|
+
permissions: expect.any(UserPermissions),
|
|
652
|
+
}),
|
|
653
|
+
|
|
654
|
+
// Parents
|
|
655
|
+
expect.objectContaining({
|
|
656
|
+
firstName: 'Linda',
|
|
657
|
+
lastName: 'Potter',
|
|
658
|
+
email: 'linda@example.com',
|
|
659
|
+
memberId: null,
|
|
660
|
+
permissions: null,
|
|
661
|
+
}),
|
|
662
|
+
expect.objectContaining({
|
|
663
|
+
firstName: 'Peter',
|
|
664
|
+
lastName: 'Doe',
|
|
665
|
+
email: 'peter@example.com',
|
|
666
|
+
memberId: null,
|
|
667
|
+
permissions: null,
|
|
668
|
+
}),
|
|
669
|
+
expect.objectContaining({
|
|
670
|
+
firstName: 'Peter',
|
|
671
|
+
lastName: 'Doe',
|
|
672
|
+
email: 'peter@work.com',
|
|
673
|
+
memberId: null,
|
|
674
|
+
permissions: null,
|
|
675
|
+
}),
|
|
676
|
+
]);
|
|
677
|
+
|
|
678
|
+
// Revoke parents access
|
|
679
|
+
member.details.parentsHaveAccess = BooleanStatus.create({ value: false });
|
|
680
|
+
await MemberUserSyncer.onChangeMember(member);
|
|
681
|
+
|
|
682
|
+
users = await Member.users.load(member);
|
|
683
|
+
expect(users).toIncludeSameMembers([
|
|
684
|
+
// Member
|
|
685
|
+
expect.objectContaining({
|
|
686
|
+
firstName: 'John',
|
|
687
|
+
lastName: 'Doe',
|
|
688
|
+
email: 'john@example.com',
|
|
689
|
+
memberId: member.id,
|
|
690
|
+
permissions: expect.any(UserPermissions),
|
|
691
|
+
}),
|
|
692
|
+
]);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test('Parents are removed if member turns 18', async () => {
|
|
696
|
+
const member = await new MemberFactory({
|
|
697
|
+
details: MemberDetails.create({
|
|
698
|
+
firstName: 'John',
|
|
699
|
+
lastName: 'Doe',
|
|
700
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
701
|
+
parents: [
|
|
702
|
+
Parent.create({
|
|
703
|
+
firstName: 'Linda',
|
|
704
|
+
lastName: 'Potter',
|
|
705
|
+
email: 'linda@example.com',
|
|
706
|
+
}),
|
|
707
|
+
Parent.create({
|
|
708
|
+
firstName: 'Peter',
|
|
709
|
+
lastName: 'Doe',
|
|
710
|
+
email: 'peter@example.com',
|
|
711
|
+
alternativeEmails: [
|
|
712
|
+
'peter@work.com',
|
|
713
|
+
],
|
|
714
|
+
}),
|
|
715
|
+
],
|
|
716
|
+
email: 'john@example.com',
|
|
717
|
+
}),
|
|
718
|
+
}).create();
|
|
719
|
+
await new MemberResponsibilityRecordFactory({
|
|
720
|
+
member,
|
|
721
|
+
}).create();
|
|
722
|
+
|
|
723
|
+
await MemberUserSyncer.onChangeMember(member);
|
|
724
|
+
|
|
725
|
+
let users = await Member.users.load(member);
|
|
726
|
+
expect(users).toIncludeSameMembers([
|
|
727
|
+
// Member
|
|
728
|
+
expect.objectContaining({
|
|
729
|
+
firstName: 'John',
|
|
730
|
+
lastName: 'Doe',
|
|
731
|
+
email: 'john@example.com',
|
|
732
|
+
memberId: member.id,
|
|
733
|
+
permissions: expect.any(UserPermissions),
|
|
734
|
+
}),
|
|
735
|
+
|
|
736
|
+
// Parents
|
|
737
|
+
expect.objectContaining({
|
|
738
|
+
firstName: 'Linda',
|
|
739
|
+
lastName: 'Potter',
|
|
740
|
+
email: 'linda@example.com',
|
|
741
|
+
memberId: null,
|
|
742
|
+
permissions: null,
|
|
743
|
+
}),
|
|
744
|
+
expect.objectContaining({
|
|
745
|
+
firstName: 'Peter',
|
|
746
|
+
lastName: 'Doe',
|
|
747
|
+
email: 'peter@example.com',
|
|
748
|
+
memberId: null,
|
|
749
|
+
permissions: null,
|
|
750
|
+
}),
|
|
751
|
+
expect.objectContaining({
|
|
752
|
+
firstName: 'Peter',
|
|
753
|
+
lastName: 'Doe',
|
|
754
|
+
email: 'peter@work.com',
|
|
755
|
+
memberId: null,
|
|
756
|
+
permissions: null,
|
|
757
|
+
}),
|
|
758
|
+
]);
|
|
759
|
+
|
|
760
|
+
// Revoke parents access
|
|
761
|
+
member.details.birthDay = new Date(Date.now() - 1000 * 60 * 60 * 24 * 366 * 18); // 18 years old + compensation for leap years
|
|
762
|
+
await MemberUserSyncer.onChangeMember(member);
|
|
763
|
+
|
|
764
|
+
users = await Member.users.load(member);
|
|
765
|
+
expect(users).toIncludeSameMembers([
|
|
766
|
+
// Member
|
|
767
|
+
expect.objectContaining({
|
|
768
|
+
firstName: 'John',
|
|
769
|
+
lastName: 'Doe',
|
|
770
|
+
email: 'john@example.com',
|
|
771
|
+
memberId: member.id,
|
|
772
|
+
permissions: expect.any(UserPermissions),
|
|
773
|
+
}),
|
|
774
|
+
]);
|
|
775
|
+
});
|
|
776
|
+
|
|
469
777
|
test('Old emails with account are not removed', async () => {
|
|
470
778
|
const user_m1 = await new UserFactory({ email: 'john@example.com' }).create();
|
|
471
779
|
const user_m2 = await new UserFactory({ email: 'john@work.com' }).create();
|
|
@@ -478,6 +786,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
478
786
|
details: MemberDetails.create({
|
|
479
787
|
firstName: 'John',
|
|
480
788
|
lastName: 'Doe',
|
|
789
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
481
790
|
parents: [
|
|
482
791
|
Parent.create({
|
|
483
792
|
firstName: 'Linda',
|
|
@@ -634,12 +943,97 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
634
943
|
});
|
|
635
944
|
});
|
|
636
945
|
|
|
946
|
+
describe('Linking', () => {
|
|
947
|
+
test('Parents are added if access is added', async () => {
|
|
948
|
+
const member = await new MemberFactory({
|
|
949
|
+
details: MemberDetails.create({
|
|
950
|
+
firstName: 'John',
|
|
951
|
+
lastName: 'Doe',
|
|
952
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 366 * 18), // 18 years old + leap year margin
|
|
953
|
+
parents: [
|
|
954
|
+
Parent.create({
|
|
955
|
+
firstName: 'Linda',
|
|
956
|
+
lastName: 'Potter',
|
|
957
|
+
email: 'linda@example.com',
|
|
958
|
+
}),
|
|
959
|
+
Parent.create({
|
|
960
|
+
firstName: 'Peter',
|
|
961
|
+
lastName: 'Doe',
|
|
962
|
+
email: 'peter@example.com',
|
|
963
|
+
alternativeEmails: [
|
|
964
|
+
'peter@work.com',
|
|
965
|
+
],
|
|
966
|
+
}),
|
|
967
|
+
],
|
|
968
|
+
email: 'john@example.com',
|
|
969
|
+
}),
|
|
970
|
+
}).create();
|
|
971
|
+
await new MemberResponsibilityRecordFactory({
|
|
972
|
+
member,
|
|
973
|
+
}).create();
|
|
974
|
+
|
|
975
|
+
await MemberUserSyncer.onChangeMember(member);
|
|
976
|
+
|
|
977
|
+
let users = await Member.users.load(member);
|
|
978
|
+
expect(users).toIncludeSameMembers([
|
|
979
|
+
// Member
|
|
980
|
+
expect.objectContaining({
|
|
981
|
+
firstName: 'John',
|
|
982
|
+
lastName: 'Doe',
|
|
983
|
+
email: 'john@example.com',
|
|
984
|
+
memberId: member.id,
|
|
985
|
+
permissions: expect.any(UserPermissions),
|
|
986
|
+
}),
|
|
987
|
+
]);
|
|
988
|
+
|
|
989
|
+
// Revoke parents access
|
|
990
|
+
member.details.parentsHaveAccess = BooleanStatus.create({ value: true });
|
|
991
|
+
await MemberUserSyncer.onChangeMember(member);
|
|
992
|
+
|
|
993
|
+
users = await Member.users.load(member);
|
|
994
|
+
expect(users).toIncludeSameMembers([
|
|
995
|
+
// Member
|
|
996
|
+
expect.objectContaining({
|
|
997
|
+
firstName: 'John',
|
|
998
|
+
lastName: 'Doe',
|
|
999
|
+
email: 'john@example.com',
|
|
1000
|
+
memberId: member.id,
|
|
1001
|
+
permissions: expect.any(UserPermissions),
|
|
1002
|
+
}),
|
|
1003
|
+
|
|
1004
|
+
// Parents
|
|
1005
|
+
expect.objectContaining({
|
|
1006
|
+
firstName: 'Linda',
|
|
1007
|
+
lastName: 'Potter',
|
|
1008
|
+
email: 'linda@example.com',
|
|
1009
|
+
memberId: null,
|
|
1010
|
+
permissions: null,
|
|
1011
|
+
}),
|
|
1012
|
+
expect.objectContaining({
|
|
1013
|
+
firstName: 'Peter',
|
|
1014
|
+
lastName: 'Doe',
|
|
1015
|
+
email: 'peter@example.com',
|
|
1016
|
+
memberId: null,
|
|
1017
|
+
permissions: null,
|
|
1018
|
+
}),
|
|
1019
|
+
expect.objectContaining({
|
|
1020
|
+
firstName: 'Peter',
|
|
1021
|
+
lastName: 'Doe',
|
|
1022
|
+
email: 'peter@work.com',
|
|
1023
|
+
memberId: null,
|
|
1024
|
+
permissions: null,
|
|
1025
|
+
}),
|
|
1026
|
+
]);
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
|
|
637
1030
|
describe('Members with the same email addresses', () => {
|
|
638
1031
|
test('The most recent member is linked to a user if both do not have responsibilities', async () => {
|
|
639
1032
|
const member1 = await new MemberFactory({
|
|
640
1033
|
details: MemberDetails.create({
|
|
641
1034
|
firstName: 'John',
|
|
642
1035
|
lastName: 'Doe',
|
|
1036
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
643
1037
|
email: 'john@example.com',
|
|
644
1038
|
}),
|
|
645
1039
|
}).create();
|
|
@@ -663,6 +1057,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
663
1057
|
details: MemberDetails.create({
|
|
664
1058
|
firstName: 'Other',
|
|
665
1059
|
lastName: 'Doe',
|
|
1060
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
666
1061
|
email: 'john@example.com',
|
|
667
1062
|
}),
|
|
668
1063
|
}).create();
|
|
@@ -698,6 +1093,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
698
1093
|
details: MemberDetails.create({
|
|
699
1094
|
firstName: 'John',
|
|
700
1095
|
lastName: 'Doe',
|
|
1096
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
701
1097
|
email: 'john@example.com',
|
|
702
1098
|
}),
|
|
703
1099
|
}).create();
|
|
@@ -724,6 +1120,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
724
1120
|
details: MemberDetails.create({
|
|
725
1121
|
firstName: 'Other',
|
|
726
1122
|
lastName: 'Doe',
|
|
1123
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
727
1124
|
email: 'john@example.com',
|
|
728
1125
|
}),
|
|
729
1126
|
}).create();
|
|
@@ -764,6 +1161,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
764
1161
|
details: MemberDetails.create({
|
|
765
1162
|
firstName: 'John',
|
|
766
1163
|
lastName: 'Doe',
|
|
1164
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
767
1165
|
email: 'john@example.com',
|
|
768
1166
|
}),
|
|
769
1167
|
}).create();
|
|
@@ -784,6 +1182,7 @@ describe('Helpers.MemberUserSyncer', () => {
|
|
|
784
1182
|
details: MemberDetails.create({
|
|
785
1183
|
firstName: 'Other',
|
|
786
1184
|
lastName: 'Doe',
|
|
1185
|
+
birthDay: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 8), // 8 years old
|
|
787
1186
|
email: 'john@example.com',
|
|
788
1187
|
}),
|
|
789
1188
|
}).create();
|
|
@@ -49,17 +49,19 @@ export class MemberUserSyncerStatic {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
for (const
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
if (member.details.calculatedParentsHaveAccess) {
|
|
53
|
+
for (const parent of member.details.parents) {
|
|
54
|
+
for (const email of parent.getEmails()) {
|
|
55
|
+
if (userEmails.includes(email.toLocaleLowerCase())) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Link parents and unverified emails
|
|
60
|
+
await this.linkUser(email, member, true, {
|
|
61
|
+
firstName: parent.firstName,
|
|
62
|
+
lastName: parent.lastName,
|
|
63
|
+
});
|
|
56
64
|
}
|
|
57
|
-
|
|
58
|
-
// Link parents and unverified emails
|
|
59
|
-
await this.linkUser(email, member, true, {
|
|
60
|
-
firstName: parent.firstName,
|
|
61
|
-
lastName: parent.lastName,
|
|
62
|
-
});
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -89,6 +91,9 @@ export class MemberUserSyncerStatic {
|
|
|
89
91
|
await this.linkUser(user.email, member, true);
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
else if (!member.details.calculatedParentsHaveAccess && parentEmails.includes(user.email.toLocaleLowerCase()) && !userEmails.includes(user.email.toLocaleLowerCase())) {
|
|
95
|
+
await this.unlinkUser(user, member);
|
|
96
|
+
}
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
if (member.details.securityCode === null) {
|