@stamhoofd/backend 2.78.4 → 2.79.0
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/index.ts +1 -1
- package/package.json +13 -12
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateAdminEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
- package/src/endpoints/auth/PatchUserEndpoint.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +1964 -4
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +175 -2
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +1 -1
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +7 -1
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +3 -23
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +164 -0
- package/src/helpers/AdminPermissionChecker.ts +22 -1
- package/src/helpers/AuthenticatedStructures.ts +77 -20
- package/src/helpers/ForwardHandler.test.ts +16 -5
- package/src/helpers/ForwardHandler.ts +21 -9
- package/src/helpers/MemberUserSyncer.test.ts +822 -0
- package/src/helpers/MemberUserSyncer.ts +137 -108
- package/src/helpers/TagHelper.ts +3 -3
- package/src/seeds/1734596144-fill-previous-period-id.ts +1 -1
- package/src/seeds/1741008870-fix-auditlog-description.ts +50 -0
- package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +88 -0
- package/src/services/PlatformMembershipService.ts +7 -2
- package/src/services/SSOService.ts +1 -1
- package/tests/e2e/register.test.ts +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
2
|
import { AuditLog, BalanceItem, CachedBalance, Document, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
|
|
3
|
-
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, DetailedReceivableBalance, Document as DocumentStruct, EventNotification as EventNotificationStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, DetailedReceivableBalance, Document as DocumentStruct, EventNotification as EventNotificationStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, GroupType, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
4
|
import { Sorter } from '@stamhoofd/utility';
|
|
5
5
|
|
|
6
6
|
import { SQL } from '@stamhoofd/sql';
|
|
@@ -58,11 +58,29 @@ export class AuthenticatedStructures {
|
|
|
58
58
|
|
|
59
59
|
static async groups(groups: Group[]) {
|
|
60
60
|
const waitingListIds = Formatter.uniqueArray(groups.map(g => g.waitingListId).filter(id => id !== null));
|
|
61
|
-
|
|
61
|
+
|
|
62
|
+
const waitingLists: Group[] = [];
|
|
63
|
+
const waitingListsToFetch: string[] = [];
|
|
64
|
+
|
|
65
|
+
for (const waitingListId of waitingListIds) {
|
|
66
|
+
const existingGroup = groups.find(g => g.id === waitingListId);
|
|
67
|
+
|
|
68
|
+
if (existingGroup) {
|
|
69
|
+
waitingLists.push(existingGroup);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
waitingListsToFetch.push(waitingListId);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (waitingListsToFetch.length) {
|
|
77
|
+
const fetchedWaitingLists = await Group.getByIDs(...waitingListsToFetch);
|
|
78
|
+
waitingLists.push(...fetchedWaitingLists);
|
|
79
|
+
}
|
|
62
80
|
|
|
63
81
|
const structs: GroupStruct[] = [];
|
|
64
82
|
for (const group of groups) {
|
|
65
|
-
const waitingList = waitingLists.find(g => g.id
|
|
83
|
+
const waitingList = waitingLists.find(g => g.id === group.waitingListId) ?? null;
|
|
66
84
|
const waitingListStruct = waitingList ? GroupStruct.create(waitingList) : null;
|
|
67
85
|
if (waitingList && waitingListStruct && !await Context.optionalAuth?.canAccessGroup(waitingList)) {
|
|
68
86
|
waitingListStruct.privateSettings = null;
|
|
@@ -88,31 +106,70 @@ export class AuthenticatedStructures {
|
|
|
88
106
|
return [];
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
const periodIds = periods ? Formatter.uniqueArray(periods.map(p => p.id)) : Formatter.uniqueArray(organizationRegistrationPeriods.map(p => p.periodId));
|
|
110
|
+
|
|
91
111
|
if (!periods) {
|
|
92
|
-
const periodIds = Formatter.uniqueArray(organizationRegistrationPeriods.map(p => p.periodId));
|
|
93
112
|
periods = await RegistrationPeriod.getByIDs(...periodIds);
|
|
94
113
|
}
|
|
95
114
|
|
|
115
|
+
const organizationIds = Formatter.uniqueArray(organizationRegistrationPeriods.map(r => r.organizationId));
|
|
116
|
+
|
|
96
117
|
const groupIds = Formatter.uniqueArray(organizationRegistrationPeriods.flatMap(p => p.settings.categories.flatMap(c => c.groupIds)));
|
|
97
|
-
|
|
118
|
+
|
|
119
|
+
let groups: Group[] = [];
|
|
120
|
+
|
|
121
|
+
const whereWaitingList = SQL.where('organizationId', organizationIds)
|
|
122
|
+
.and('periodId', periodIds)
|
|
123
|
+
.and('type', GroupType.WaitingList)
|
|
124
|
+
.and('deletedAt', null);
|
|
125
|
+
|
|
126
|
+
if (groupIds.length) {
|
|
127
|
+
const whereGroupIds = SQL.where('id', groupIds);
|
|
128
|
+
|
|
129
|
+
if (organizationIds.length && periodIds.length) {
|
|
130
|
+
groups = await Group.select()
|
|
131
|
+
.where(whereGroupIds)
|
|
132
|
+
.orWhere(whereWaitingList)
|
|
133
|
+
.fetch();
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
groups = await Group.select()
|
|
137
|
+
.where(whereGroupIds).fetch();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (organizationIds.length && periodIds.length) {
|
|
141
|
+
groups = await Group.select()
|
|
142
|
+
.where(whereWaitingList)
|
|
143
|
+
.fetch();
|
|
144
|
+
}
|
|
98
145
|
|
|
99
146
|
const groupStructs = await this.groups(groups);
|
|
100
147
|
|
|
101
148
|
const structs: OrganizationRegistrationPeriodStruct[] = [];
|
|
102
149
|
for (const organizationPeriod of organizationRegistrationPeriods) {
|
|
103
|
-
const period = periods.find(p => p.id
|
|
150
|
+
const period = periods.find(p => p.id === organizationPeriod.periodId) ?? null;
|
|
104
151
|
if (!period) {
|
|
105
152
|
continue;
|
|
106
153
|
}
|
|
107
|
-
const groupIds = Formatter.uniqueArray(organizationPeriod.settings.categories.flatMap(c => c.groupIds));
|
|
108
154
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
const struct = OrganizationRegistrationPeriodStruct.create({
|
|
156
|
+
...organizationPeriod,
|
|
157
|
+
period: period.getStructure(),
|
|
158
|
+
groups: groupStructs.filter((gg) => {
|
|
159
|
+
if (gg.organizationId !== organizationPeriod.organizationId) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (gg.periodId !== organizationPeriod.periodId) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return true;
|
|
168
|
+
})
|
|
169
|
+
.sort(GroupStruct.defaultSort),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
structs.push(struct);
|
|
116
173
|
}
|
|
117
174
|
|
|
118
175
|
return structs;
|
|
@@ -767,11 +824,11 @@ export class AuthenticatedStructures {
|
|
|
767
824
|
]
|
|
768
825
|
: []),
|
|
769
826
|
|
|
770
|
-
...(member.details.
|
|
771
|
-
? member.details.parents.filter(p =>
|
|
827
|
+
...((member.details.defaultAge <= 18 || member.details.getMemberEmails().length === 0)
|
|
828
|
+
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
772
829
|
firstName: p.firstName ?? '',
|
|
773
830
|
lastName: p.lastName ?? '',
|
|
774
|
-
emails:
|
|
831
|
+
emails: p.getEmails(),
|
|
775
832
|
meta: {
|
|
776
833
|
type: 'parent',
|
|
777
834
|
responsibilityIds: [],
|
|
@@ -810,11 +867,11 @@ export class AuthenticatedStructures {
|
|
|
810
867
|
]
|
|
811
868
|
: []),
|
|
812
869
|
|
|
813
|
-
...(member.details.
|
|
814
|
-
? member.details.parents.filter(p =>
|
|
870
|
+
...((member.details.defaultAge <= 18 || member.details.getMemberEmails().length === 0)
|
|
871
|
+
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
815
872
|
firstName: p.firstName ?? '',
|
|
816
873
|
lastName: p.lastName ?? '',
|
|
817
|
-
emails:
|
|
874
|
+
emails: p.getEmails(),
|
|
818
875
|
meta: {
|
|
819
876
|
type: 'parent',
|
|
820
877
|
responsibilityIds: [],
|
|
@@ -34,7 +34,9 @@ describe('ForwardHandler', () => {
|
|
|
34
34
|
},
|
|
35
35
|
],
|
|
36
36
|
subject: 'Hello',
|
|
37
|
-
replyTo:
|
|
37
|
+
replyTo: {
|
|
38
|
+
email: 'someone@example.com',
|
|
39
|
+
},
|
|
38
40
|
});
|
|
39
41
|
expect(options!.text).toContain('Content hier');
|
|
40
42
|
});
|
|
@@ -67,7 +69,9 @@ describe('ForwardHandler', () => {
|
|
|
67
69
|
},
|
|
68
70
|
],
|
|
69
71
|
subject: 'Hello',
|
|
70
|
-
replyTo:
|
|
72
|
+
replyTo: {
|
|
73
|
+
email: 'someone@example.com',
|
|
74
|
+
},
|
|
71
75
|
});
|
|
72
76
|
expect(options!.text).toContain('Content hier');
|
|
73
77
|
});
|
|
@@ -92,7 +96,9 @@ describe('ForwardHandler', () => {
|
|
|
92
96
|
},
|
|
93
97
|
],
|
|
94
98
|
subject: 'Hello',
|
|
95
|
-
replyTo:
|
|
99
|
+
replyTo: {
|
|
100
|
+
email: 'someone@example.com',
|
|
101
|
+
},
|
|
96
102
|
});
|
|
97
103
|
expect(options!.text).toContain('Content hier');
|
|
98
104
|
|
|
@@ -119,7 +125,9 @@ describe('ForwardHandler', () => {
|
|
|
119
125
|
|
|
120
126
|
expect(options).toMatchObject({
|
|
121
127
|
subject: 'Hello',
|
|
122
|
-
replyTo:
|
|
128
|
+
replyTo: {
|
|
129
|
+
email: 'someone@example.com',
|
|
130
|
+
},
|
|
123
131
|
});
|
|
124
132
|
expect(options!.to).toIncludeAllMembers([
|
|
125
133
|
{
|
|
@@ -207,7 +215,10 @@ describe('ForwardHandler', () => {
|
|
|
207
215
|
dmarcVerdict: { status: 'PASS' },
|
|
208
216
|
});
|
|
209
217
|
expect(options).toMatchObject({
|
|
210
|
-
to:
|
|
218
|
+
to: [{
|
|
219
|
+
email: 'hallo@stamhoofd.be',
|
|
220
|
+
name: 'Stamhoofd',
|
|
221
|
+
}],
|
|
211
222
|
subject: 'E-mail unsubscribe mislukt',
|
|
212
223
|
});
|
|
213
224
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
-
|
|
3
|
-
import { Email, EmailAddress, EmailInterfaceRecipient } from '@stamhoofd/email';
|
|
2
|
+
|
|
3
|
+
import { Email, EmailAddress, EmailInterface, EmailInterfaceRecipient } from '@stamhoofd/email';
|
|
4
4
|
import { Organization } from '@stamhoofd/models';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { simpleParser } from 'mailparser';
|
|
@@ -49,7 +49,7 @@ export class ForwardHandler {
|
|
|
49
49
|
// Forward
|
|
50
50
|
return {
|
|
51
51
|
from: Email.getWebmasterFromEmail(),
|
|
52
|
-
to: Email.getWebmasterToEmail(),
|
|
52
|
+
to: [Email.getWebmasterToEmail()],
|
|
53
53
|
subject: 'E-mail unsubscribe mislukt',
|
|
54
54
|
text: 'Beste,\n\nEr werd een unsubscribe gemeld op ' + email + ' die niet kon worden verwerkt. Gelieve dit na te kijken.\n\nStamhoofd',
|
|
55
55
|
};
|
|
@@ -64,7 +64,7 @@ export class ForwardHandler {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// Send a new e-mail
|
|
67
|
-
let defaultEmail: EmailInterfaceRecipient[]
|
|
67
|
+
let defaultEmail: EmailInterfaceRecipient[] = [Email.getWebmasterToEmail()];
|
|
68
68
|
let organizationEmails: EmailInterfaceRecipient[] = [];
|
|
69
69
|
const extraDescription = 'Dit bericht werd verstuurd naar ' + email + ', en werd automatisch doorgestuurd naar alle beheerders. Stel in Stamhoofd de e-mailadressen in om ervoor te zorgen dat antwoorden naar een specifiek e-mailadres worden verstuurd.';
|
|
70
70
|
|
|
@@ -80,8 +80,12 @@ export class ForwardHandler {
|
|
|
80
80
|
|
|
81
81
|
// Send back to receiver without including the original message to avoid spam
|
|
82
82
|
return {
|
|
83
|
-
from: email
|
|
84
|
-
|
|
83
|
+
from: email
|
|
84
|
+
? {
|
|
85
|
+
email,
|
|
86
|
+
}
|
|
87
|
+
: Email.getWebmasterToEmail(),
|
|
88
|
+
to: [{ email: from }],
|
|
85
89
|
subject: 'Ongeldig e-mailadres',
|
|
86
90
|
text: 'Beste,\n\nDe vereniging die je probeert te bereiken via ' + email + ' is helaas niet bereikbaar via dit e-mailadres. Dit e-mailadres wordt enkel gebruikt voor het versturen van automatische e-mails in naam van een vereniging. Probeer de vereniging te contacteren via een ander e-mailadres.\n\nBedankt.',
|
|
87
91
|
};
|
|
@@ -123,10 +127,18 @@ export class ForwardHandler {
|
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
const options = {
|
|
127
|
-
from: email
|
|
130
|
+
const options: EmailInterface = {
|
|
131
|
+
from: email
|
|
132
|
+
? {
|
|
133
|
+
email,
|
|
134
|
+
}
|
|
135
|
+
: Email.getWebmasterToEmail(),
|
|
128
136
|
to: defaultEmail,
|
|
129
|
-
replyTo: parsed.from?.text
|
|
137
|
+
replyTo: parsed.from?.text
|
|
138
|
+
? {
|
|
139
|
+
email: parsed.from?.text,
|
|
140
|
+
}
|
|
141
|
+
: undefined,
|
|
130
142
|
subject: parsed.subject ?? 'Doorgestuurd bericht',
|
|
131
143
|
text: parsed.text ? extraDescription + '\n\n' + parsed.text : undefined,
|
|
132
144
|
html: html,
|