@stamhoofd/backend 2.90.3 → 2.92.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.
Files changed (47) hide show
  1. package/package.json +10 -10
  2. package/src/audit-logs/EmailLogger.ts +4 -4
  3. package/src/audit-logs/ModelLogger.ts +0 -1
  4. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +20 -0
  5. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +2 -0
  6. package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -7
  7. package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +207 -0
  8. package/src/endpoints/global/email/GetEmailEndpoint.ts +5 -1
  9. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +404 -8
  10. package/src/endpoints/global/email/PatchEmailEndpoint.ts +67 -22
  11. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +6 -4
  12. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -7
  13. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +112 -105
  14. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -1
  15. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +5 -5
  16. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +1 -1
  17. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +10 -1
  18. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +8 -1
  19. package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +1 -1
  20. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -67
  21. package/src/helpers/AdminPermissionChecker.ts +81 -10
  22. package/src/helpers/FlagMomentCleanup.ts +13 -1
  23. package/src/helpers/GroupedThrottledQueue.ts +5 -3
  24. package/src/helpers/PeriodHelper.ts +10 -137
  25. package/src/helpers/SetupStepUpdater.ts +54 -7
  26. package/src/helpers/UitpasTokenRepository.ts +3 -3
  27. package/src/seeds/1750090030-records-configuration.ts +5 -1
  28. package/src/seeds/1752848560-groups-registration-periods.ts +768 -0
  29. package/src/seeds/1755181288-remove-duplicate-members.ts +145 -0
  30. package/src/seeds/1755532883-update-email-sender-ids.ts +47 -0
  31. package/src/services/BalanceItemService.ts +12 -7
  32. package/src/services/DocumentService.ts +0 -1
  33. package/src/services/RegistrationService.ts +30 -1
  34. package/src/services/uitpas/UitpasService.ts +72 -3
  35. package/src/services/uitpas/cancelTicketSales.ts +1 -1
  36. package/src/services/uitpas/checkPermissionsFor.ts +9 -9
  37. package/src/services/uitpas/checkUitpasNumbers.ts +3 -2
  38. package/src/services/uitpas/getSocialTariffForEvent.ts +4 -4
  39. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +5 -5
  40. package/src/services/uitpas/registerTicketSales.ts +4 -4
  41. package/src/services/uitpas/searchUitpasEvents.ts +3 -3
  42. package/src/services/uitpas/searchUitpasOrganizers.ts +3 -3
  43. package/src/sql-filters/emails.ts +65 -0
  44. package/src/sql-filters/members.ts +1 -1
  45. package/src/sql-filters/organizations.ts +52 -0
  46. package/src/sql-sorters/emails.ts +47 -0
  47. package/tests/e2e/register.test.ts +1 -1
@@ -1,9 +1,10 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { Email, Organization, OrganizationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, User, UserFactory } from '@stamhoofd/models';
3
- import { EmailStatus, Email as EmailStruct, PermissionLevel, Permissions, Version } from '@stamhoofd/structures';
4
- import { TestUtils } from '@stamhoofd/test-utils';
3
+ import { AccessRight, EmailStatus, Email as EmailStruct, OrganizationEmail, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions, Version } from '@stamhoofd/structures';
4
+ import { STExpect, TestUtils } from '@stamhoofd/test-utils';
5
5
  import { testServer } from '../../../../tests/helpers/TestServer';
6
6
  import { PatchEmailEndpoint } from './PatchEmailEndpoint';
7
+ import { AutoEncoderPatchType } from '@simonbackx/simple-encoding';
7
8
 
8
9
  const baseUrl = `/v${Version}/email`;
9
10
 
@@ -13,8 +14,13 @@ describe('Endpoint.PatchEmailEndpoint', () => {
13
14
  let organization: Organization;
14
15
  let token: Token;
15
16
  let user: User;
17
+ let sender: OrganizationEmail;
18
+ let sender2: OrganizationEmail;
16
19
 
17
- const patchEmail = async (email: EmailStruct, token: Token, organization?: Organization) => {
20
+ let token2: Token;
21
+ let user2: User;
22
+
23
+ const patchEmail = async (email: AutoEncoderPatchType<EmailStruct>, token: Token, organization?: Organization) => {
18
24
  const id = email.id;
19
25
  const request = Request.buildJson('PATCH', `${baseUrl}/${id}`, organization?.getApiHost(), email);
20
26
  request.headers.authorization = 'Bearer ' + token.accessToken;
@@ -34,15 +40,360 @@ describe('Endpoint.PatchEmailEndpoint', () => {
34
40
  organization = await new OrganizationFactory({ period })
35
41
  .create();
36
42
 
43
+ sender = OrganizationEmail.create({
44
+ email: 'groepsleiding@voorbeeld.com',
45
+ name: 'Groepsleiding',
46
+ });
47
+ sender2 = OrganizationEmail.create({
48
+ email: 'kapoenen@voorbeeld.com',
49
+ name: 'Kapoenen',
50
+ });
51
+
52
+ organization.privateMeta.emails.push(sender);
53
+ organization.privateMeta.emails.push(sender2);
54
+ await organization.save();
55
+
37
56
  user = await new UserFactory({
38
57
  organization,
39
58
  permissions: Permissions.create({
40
- level: PermissionLevel.Read,
59
+ level: PermissionLevel.None,
60
+ resources: new Map([
61
+ [PermissionsResourceType.Senders, new Map([[sender.id, ResourcePermissions.create({
62
+ resourceName: sender.name!,
63
+ level: PermissionLevel.None,
64
+ accessRights: [AccessRight.SendMessages],
65
+ })]])],
66
+ ]),
41
67
  }),
42
68
  })
43
69
  .create();
44
70
 
45
71
  token = await Token.createToken(user);
72
+
73
+ user2 = await new UserFactory({
74
+ organization,
75
+ permissions: Permissions.create({
76
+ level: PermissionLevel.None,
77
+ resources: new Map([
78
+ [PermissionsResourceType.Senders, new Map([[sender2.id, ResourcePermissions.create({
79
+ resourceName: sender.name!,
80
+ level: PermissionLevel.Write,
81
+ accessRights: [AccessRight.SendMessages],
82
+ })]])],
83
+ ]),
84
+ }),
85
+ })
86
+ .create();
87
+
88
+ token2 = await Token.createToken(user2);
89
+ });
90
+
91
+ test('Should throw for invalid senderId', async () => {
92
+ const email = new Email();
93
+ email.subject = 'test subject';
94
+ email.status = EmailStatus.Draft;
95
+ email.text = 'test email {{unsubscribeUrl}}';
96
+ email.html = `<!DOCTYPE html>
97
+ <html>
98
+
99
+ <head>
100
+ <meta charset="utf-8" />
101
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
102
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
103
+ <title>test</title>
104
+ </head>
105
+
106
+ <body>
107
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email</p>
108
+
109
+ {{unsubscribeUrl}}
110
+ </body>
111
+
112
+ </html>`;
113
+ email.json = {
114
+ content: [
115
+ {
116
+ content: [
117
+ {
118
+ text: 'test email',
119
+ type: 'text',
120
+ },
121
+ ],
122
+ type: 'paragraph',
123
+ },
124
+ ],
125
+ type: 'doc',
126
+ };
127
+ email.userId = user.id;
128
+ email.organizationId = organization.id;
129
+
130
+ await email.save();
131
+
132
+ const body = EmailStruct.patch({
133
+ id: email.id,
134
+ status: EmailStatus.Sending,
135
+ senderId: 'invalid-sender-id',
136
+ });
137
+
138
+ await expect(async () => await patchEmail(body, token, organization))
139
+ .rejects
140
+ .toThrow(STExpect.errorWithCode('invalid_sender'));
141
+ });
142
+
143
+ test('Should throw when patching other users email without sender id', async () => {
144
+ const email = new Email();
145
+ email.subject = 'test subject';
146
+ email.status = EmailStatus.Draft;
147
+ email.text = 'test email {{unsubscribeUrl}}';
148
+ email.html = `<!DOCTYPE html>
149
+ <html>
150
+
151
+ <head>
152
+ <meta charset="utf-8" />
153
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
154
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
155
+ <title>test</title>
156
+ </head>
157
+
158
+ <body>
159
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email</p>
160
+
161
+ {{unsubscribeUrl}}
162
+ </body>
163
+
164
+ </html>`;
165
+ email.json = {
166
+ content: [
167
+ {
168
+ content: [
169
+ {
170
+ text: 'test email',
171
+ type: 'text',
172
+ },
173
+ ],
174
+ type: 'paragraph',
175
+ },
176
+ ],
177
+ type: 'doc',
178
+ };
179
+ email.userId = user2.id;
180
+ email.organizationId = organization.id;
181
+
182
+ await email.save();
183
+
184
+ const body = EmailStruct.patch({
185
+ id: email.id,
186
+ subject: 'new subject',
187
+ });
188
+
189
+ await expect(async () => await patchEmail(body, token, organization))
190
+ .rejects
191
+ .toThrow(STExpect.errorWithCode('permission_denied'));
192
+ });
193
+
194
+ test('Should throw when patching other users email even when matching sender', async () => {
195
+ const email = new Email();
196
+ email.subject = 'test subject';
197
+ email.status = EmailStatus.Draft;
198
+ email.text = 'test email {{unsubscribeUrl}}';
199
+ email.html = `<!DOCTYPE html>
200
+ <html>
201
+
202
+ <head>
203
+ <meta charset="utf-8" />
204
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
205
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
206
+ <title>test</title>
207
+ </head>
208
+
209
+ <body>
210
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email</p>
211
+
212
+ {{unsubscribeUrl}}
213
+ </body>
214
+
215
+ </html>`;
216
+ email.json = {
217
+ content: [
218
+ {
219
+ content: [
220
+ {
221
+ text: 'test email',
222
+ type: 'text',
223
+ },
224
+ ],
225
+ type: 'paragraph',
226
+ },
227
+ ],
228
+ type: 'doc',
229
+ };
230
+ email.userId = user2.id;
231
+ email.organizationId = organization.id;
232
+ email.senderId = sender.id;
233
+
234
+ await email.save();
235
+
236
+ const body = EmailStruct.patch({
237
+ id: email.id,
238
+ subject: 'new subject',
239
+ });
240
+
241
+ await expect(async () => await patchEmail(body, token, organization))
242
+ .rejects
243
+ .toThrow(STExpect.errorWithCode('permission_denied'));
244
+ });
245
+
246
+ test('Should not throw when patching other users email when having write access to sender', async () => {
247
+ const email = new Email();
248
+ email.subject = 'test subject';
249
+ email.status = EmailStatus.Draft;
250
+ email.text = 'test email {{unsubscribeUrl}}';
251
+ email.html = `<!DOCTYPE html>
252
+ <html>
253
+
254
+ <head>
255
+ <meta charset="utf-8" />
256
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
257
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
258
+ <title>test</title>
259
+ </head>
260
+
261
+ <body>
262
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email</p>
263
+
264
+ {{unsubscribeUrl}}
265
+ </body>
266
+
267
+ </html>`;
268
+ email.json = {
269
+ content: [
270
+ {
271
+ content: [
272
+ {
273
+ text: 'test email',
274
+ type: 'text',
275
+ },
276
+ ],
277
+ type: 'paragraph',
278
+ },
279
+ ],
280
+ type: 'doc',
281
+ };
282
+ email.userId = user.id; // other user
283
+ email.organizationId = organization.id;
284
+ email.senderId = sender2.id; // write access to this sender
285
+
286
+ await email.save();
287
+
288
+ const body = EmailStruct.patch({
289
+ id: email.id,
290
+ subject: 'new subject',
291
+ });
292
+
293
+ await expect(patchEmail(body, token2, organization)).toResolve();
294
+ });
295
+
296
+ test('Should throw when patching if no permission for sender', async () => {
297
+ const email = new Email();
298
+ email.subject = 'test subject';
299
+ email.status = EmailStatus.Draft;
300
+ email.text = 'test email {{unsubscribeUrl}}';
301
+ email.html = `<!DOCTYPE html>
302
+ <html>
303
+
304
+ <head>
305
+ <meta charset="utf-8" />
306
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
307
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
308
+ <title>test</title>
309
+ </head>
310
+
311
+ <body>
312
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email</p>
313
+
314
+ {{unsubscribeUrl}}
315
+ </body>
316
+
317
+ </html>`;
318
+ email.json = {
319
+ content: [
320
+ {
321
+ content: [
322
+ {
323
+ text: 'test email',
324
+ type: 'text',
325
+ },
326
+ ],
327
+ type: 'paragraph',
328
+ },
329
+ ],
330
+ type: 'doc',
331
+ };
332
+ email.userId = user.id;
333
+ email.organizationId = organization.id;
334
+
335
+ await email.save();
336
+
337
+ const body = EmailStruct.patch({
338
+ id: email.id,
339
+ senderId: sender2.id,
340
+ });
341
+
342
+ await expect(async () => await patchEmail(body, token, organization))
343
+ .rejects
344
+ .toThrow(STExpect.errorWithCode('permission_denied'));
345
+ });
346
+
347
+ test('Should throw when sending if no permission for sender', async () => {
348
+ const email = new Email();
349
+ email.subject = 'test subject';
350
+ email.status = EmailStatus.Draft;
351
+ email.text = 'test email {{unsubscribeUrl}}';
352
+ email.html = `<!DOCTYPE html>
353
+ <html>
354
+
355
+ <head>
356
+ <meta charset="utf-8" />
357
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
358
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
359
+ <title>test</title>
360
+ </head>
361
+
362
+ <body>
363
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email</p>
364
+
365
+ {{unsubscribeUrl}}
366
+ </body>
367
+
368
+ </html>`;
369
+ email.json = {
370
+ content: [
371
+ {
372
+ content: [
373
+ {
374
+ text: 'test email',
375
+ type: 'text',
376
+ },
377
+ ],
378
+ type: 'paragraph',
379
+ },
380
+ ],
381
+ type: 'doc',
382
+ };
383
+ email.userId = user.id;
384
+ email.organizationId = organization.id;
385
+ email.senderId = sender2.id;
386
+
387
+ await email.save();
388
+
389
+ const body = EmailStruct.patch({
390
+ id: email.id,
391
+ status: EmailStatus.Sending,
392
+ });
393
+
394
+ await expect(async () => await patchEmail(body, token, organization))
395
+ .rejects
396
+ .toThrow(STExpect.errorWithCode('permission_denied'));
46
397
  });
47
398
 
48
399
  test('Should throw error if no unsubscribe button in email html', async () => {
@@ -84,11 +435,11 @@ describe('Endpoint.PatchEmailEndpoint', () => {
84
435
 
85
436
  await email.save();
86
437
 
87
- const body = EmailStruct.create({ ...email, fromAddress: 'test@test.be', status: EmailStatus.Sending });
438
+ const body = EmailStruct.patch({ id: email.id, senderId: sender.id, status: EmailStatus.Sending });
88
439
 
89
440
  await expect(async () => await patchEmail(body, token, organization))
90
441
  .rejects
91
- .toThrow('Missing unsubscribe button');
442
+ .toThrow(STExpect.errorWithCode('missing_unsubscribe_button'));
92
443
  });
93
444
 
94
445
  test('Should throw error if no unsubscribe button in email text', async () => {
@@ -130,10 +481,55 @@ describe('Endpoint.PatchEmailEndpoint', () => {
130
481
 
131
482
  await email.save();
132
483
 
133
- const body = EmailStruct.create({ ...email, fromAddress: 'test@test.be', status: EmailStatus.Sending });
484
+ const body = EmailStruct.patch({ id: email.id, senderId: sender.id, status: EmailStatus.Sending });
134
485
 
135
486
  await expect(async () => await patchEmail(body, token, organization))
136
487
  .rejects
137
- .toThrow('Missing unsubscribe button');
488
+ .toThrow(STExpect.errorWithCode('missing_unsubscribe_button'));
489
+ });
490
+
491
+ test('Can send an email', async () => {
492
+ const email = new Email();
493
+ email.subject = 'test subject';
494
+ email.status = EmailStatus.Draft;
495
+ email.text = 'test email {{unsubscribeUrl}}';
496
+ email.html = `<!DOCTYPE html>
497
+ <html>
498
+
499
+ <head>
500
+ <meta charset="utf-8" />
501
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
502
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
503
+ <title>test</title>
504
+ </head>
505
+
506
+ <body>
507
+ <p style="margin: 0; padding: 0; line-height: 1.4;">test email {{unsubscribeUrl}}</p>
508
+ </body>
509
+
510
+ </html>`;
511
+ email.json = {
512
+ content: [
513
+ {
514
+ content: [
515
+ {
516
+ text: 'test email',
517
+ type: 'text',
518
+ },
519
+ ],
520
+ type: 'paragraph',
521
+ },
522
+ ],
523
+ type: 'doc',
524
+ };
525
+ email.userId = user.id;
526
+ email.organizationId = organization.id;
527
+ email.senderId = sender.id;
528
+
529
+ await email.save();
530
+
531
+ const body = EmailStruct.patch({ id: email.id, status: EmailStatus.Sending });
532
+
533
+ await expect(patchEmail(body, token, organization)).toResolve();
138
534
  });
139
535
  });
@@ -1,6 +1,6 @@
1
1
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
- import { Email } from '@stamhoofd/models';
3
- import { EmailPreview, EmailStatus, Email as EmailStruct } from '@stamhoofd/structures';
2
+ import { Email, Platform } from '@stamhoofd/models';
3
+ import { EmailPreview, EmailStatus, Email as EmailStruct, PermissionLevel } from '@stamhoofd/structures';
4
4
 
5
5
  import { AutoEncoderPatchType, Decoder, patchObject } from '@simonbackx/simple-encoding';
6
6
  import { SimpleError } from '@simonbackx/simple-errors';
@@ -29,14 +29,15 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
29
29
 
30
30
  async handle(request: DecodedRequest<Params, Query, Body>) {
31
31
  const organization = await Context.setOptionalOrganizationScope();
32
- const { user } = await Context.authenticate();
32
+ await Context.authenticate();
33
33
 
34
- if (!Context.auth.canSendEmails()) {
34
+ if (!await Context.auth.canReadEmails(organization)) {
35
+ // Fast fail before query
35
36
  throw Context.auth.error();
36
37
  }
37
38
 
38
39
  const model = await Email.getByID(request.params.id);
39
- if (!model || model.userId !== user.id || (model.organizationId !== (organization?.id ?? null))) {
40
+ if (!model || (model.organizationId !== (organization?.id ?? null))) {
40
41
  throw new SimpleError({
41
42
  code: 'not_found',
42
43
  human: 'Email not found',
@@ -45,6 +46,10 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
45
46
  });
46
47
  }
47
48
 
49
+ if (!await Context.auth.canAccessEmail(model, PermissionLevel.Write)) {
50
+ throw Context.auth.error();
51
+ }
52
+
48
53
  if (model.status !== EmailStatus.Draft) {
49
54
  throw new SimpleError({
50
55
  code: 'not_draft',
@@ -60,6 +65,47 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
60
65
  model.subject = request.body.subject;
61
66
  }
62
67
 
68
+ if (request.body.senderId !== undefined) {
69
+ const list = organization ? organization.privateMeta.emails : (await Platform.getShared()).privateConfig.emails;
70
+ const sender = list.find(e => e.id === request.body.senderId);
71
+ if (sender) {
72
+ if (!await Context.auth.canSendEmailsFrom(organization, sender.id)) {
73
+ throw Context.auth.error({
74
+ message: 'Cannot send emails from this sender',
75
+ human: $t('1b509614-30b0-484c-af72-57d4bc9ea788'),
76
+ });
77
+ }
78
+ model.senderId = sender.id;
79
+ model.fromAddress = sender.email;
80
+ model.fromName = sender.name;
81
+ }
82
+ else {
83
+ throw new SimpleError({
84
+ code: 'invalid_sender',
85
+ human: 'Sender not found',
86
+ message: $t(`94adb4e0-2ef1-4ee8-9f02-5a76efa51c1d`),
87
+ statusCode: 400,
88
+ });
89
+ }
90
+ }
91
+ else if (model.senderId) {
92
+ // Update data, to avoid sending from an old address
93
+ const list = organization ? organization.privateMeta.emails : (await Platform.getShared()).privateConfig.emails;
94
+ const sender = list.find(e => e.id === model.senderId);
95
+ if (sender) {
96
+ model.fromAddress = sender.email;
97
+ model.fromName = sender.name;
98
+ }
99
+ else {
100
+ throw new SimpleError({
101
+ code: 'invalid_sender',
102
+ human: 'Sender not found',
103
+ message: $t(`f08cccb3-faf9-473f-b729-16120fadec9c`),
104
+ statusCode: 400,
105
+ });
106
+ }
107
+ }
108
+
63
109
  if (request.body.html !== undefined) {
64
110
  model.html = request.body.html;
65
111
  }
@@ -72,14 +118,6 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
72
118
  model.json = request.body.json;
73
119
  }
74
120
 
75
- if (request.body.fromAddress !== undefined) {
76
- model.fromAddress = request.body.fromAddress;
77
- }
78
-
79
- if (request.body.fromName !== undefined) {
80
- model.fromName = request.body.fromName;
81
- }
82
-
83
121
  if (request.body.recipientFilter) {
84
122
  model.recipientFilter = patchObject(model.recipientFilter, request.body.recipientFilter);
85
123
  rebuild = true;
@@ -102,19 +140,26 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
102
140
  }
103
141
 
104
142
  if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent) {
143
+ if (!await Context.auth.canSendEmail(model)) {
144
+ throw Context.auth.error({
145
+ message: 'Cannot send emails from this sender',
146
+ human: $t('1b509614-30b0-484c-af72-57d4bc9ea788'),
147
+ });
148
+ }
149
+
105
150
  model.throwIfNotReadyToSend();
106
151
 
107
152
  const replacement = '{{unsubscribeUrl}}';
108
-
153
+
109
154
  if (model.html) {
110
155
  // Check email contains an unsubscribe button
111
156
  if (!model.html.includes(replacement)) {
112
157
  throw new SimpleError({
113
- code: "missing_unsubscribe_button",
114
- message: "Missing unsubscribe button",
158
+ code: 'missing_unsubscribe_button',
159
+ message: 'Missing unsubscribe button',
115
160
  human: $t(`dd55e04b-e5d9-4d9a-befc-443eef4175a8`),
116
- field: "html"
117
- })
161
+ field: 'html',
162
+ });
118
163
  }
119
164
  }
120
165
 
@@ -122,11 +167,11 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
122
167
  // Check email contains an unsubscribe button
123
168
  if (!model.text.includes(replacement)) {
124
169
  throw new SimpleError({
125
- code: "missing_unsubscribe_button",
126
- message: "Missing unsubscribe button",
170
+ code: 'missing_unsubscribe_button',
171
+ message: 'Missing unsubscribe button',
127
172
  human: $t(`dd55e04b-e5d9-4d9a-befc-443eef4175a8`),
128
- field: "text"
129
- })
173
+ field: 'text',
174
+ });
130
175
  }
131
176
  }
132
177
 
@@ -3,7 +3,7 @@ import { AutoEncoderPatchType, ConvertArrayToPatchableArray, Decoder, isEmptyPat
3
3
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
4
  import { SimpleError } from '@simonbackx/simple-errors';
5
5
  import { AuditLog, BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, mergeTwoMembers, Organization, Platform, RateLimiter, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
6
- import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, EmergencyContact, GroupType, MemberDetails, MemberResponsibility, MembersBlob, MemberWithRegistrationsBlob, Parent, PermissionLevel } from '@stamhoofd/structures';
6
+ import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, EmergencyContact, GroupType, MemberDetails, MemberResponsibility, MembersBlob, MemberWithRegistrationsBlob, Parent, PermissionLevel, SetupStepType } from '@stamhoofd/structures';
7
7
  import { Formatter } from '@stamhoofd/utility';
8
8
 
9
9
  import { Email } from '@stamhoofd/email';
@@ -317,7 +317,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
317
317
  throw new SimpleError({
318
318
  code: 'invalid_field',
319
319
  message: 'Invalid organization',
320
- human: Context.i18n.$t('d41cdbe3-57e3-4a2e-83bc-cb9e65c9c840'),
320
+ human: platformResponsibility ? $t('ec6a555e-6bb1-4b5f-b17e-38eaa8a478b5') : $t('d41cdbe3-57e3-4a2e-83bc-cb9e65c9c840'),
321
321
  });
322
322
  }
323
323
 
@@ -688,7 +688,9 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
688
688
  }
689
689
 
690
690
  if (shouldUpdateSetupSteps && organization) {
691
- SetupStepUpdater.updateForOrganization(organization).catch(console.error);
691
+ SetupStepUpdater.updateForOrganization(organization, { types: [
692
+ SetupStepType.Responsibilities,
693
+ ] }).catch(console.error);
692
694
  }
693
695
 
694
696
  return new Response(
@@ -957,7 +959,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
957
959
  throw new SimpleError({
958
960
  code: 'known_member_missing_rights',
959
961
  message: 'Creating known member without sufficient access rights',
960
- human: $t(`{member} is al gekend in ons systeem, maar jouw e-mailadres niet. Om toegang te krijgen heb je de beveiligingscode nodig.`, { member: member.details.firstName }),
962
+ human: $t(`510807a1-d4c7-45fa-9e3b-ddc8764d3f6e`, { member: member.details.firstName }),
961
963
  statusCode: 400,
962
964
  });
963
965
  }
@@ -90,6 +90,10 @@ export class PatchPlatformEndpoint extends Endpoint<
90
90
  throw Context.auth.error();
91
91
  }
92
92
 
93
+ if (request.body.config.organizationLevelRecordsConfiguration) {
94
+ shouldUpdateSetupSteps = true;
95
+ }
96
+
93
97
  const newConfig = request.body.config;
94
98
 
95
99
  // Update config
@@ -100,13 +104,11 @@ export class PatchPlatformEndpoint extends Endpoint<
100
104
  platform.config = patchObject(platform.config, newConfig);
101
105
  const currentConfig: PlatformConfig = platform.config;
102
106
 
103
- if (shouldCheckSteps) {
104
- shouldUpdateSetupSteps = this.shouldUpdateSetupSteps(
105
- currentConfig,
106
- newConfig,
107
- oldConfig,
108
- );
109
- }
107
+ shouldUpdateSetupSteps = shouldUpdateSetupSteps || this.shouldUpdateSetupSteps(
108
+ currentConfig,
109
+ newConfig,
110
+ oldConfig,
111
+ );
110
112
  }
111
113
  else {
112
114
  platform.config = patchObject(platform.config, newConfig);