@stamhoofd/backend 2.83.5 → 2.84.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.
Files changed (100) hide show
  1. package/index.ts +19 -4
  2. package/package.json +18 -14
  3. package/src/crons/amazon-ses.ts +26 -5
  4. package/src/crons/balance-emails.ts +18 -17
  5. package/src/email-recipient-loaders/registrations.ts +87 -0
  6. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +5 -2
  7. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +40 -40
  8. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +28 -22
  9. package/src/endpoints/global/events/PatchEventsEndpoint.ts +81 -49
  10. package/src/endpoints/global/files/UploadFile.ts +11 -16
  11. package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +234 -0
  12. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +117 -43
  13. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +1054 -0
  14. package/src/endpoints/global/members/GetMembersEndpoint.ts +163 -141
  15. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +6 -6
  16. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +0 -16
  17. package/src/endpoints/global/members/helpers/validateGroupFilter.ts +73 -0
  18. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +1 -2
  19. package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +43 -0
  20. package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1016 -0
  21. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +234 -0
  22. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -5
  23. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +474 -554
  24. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +191 -52
  25. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +107 -9
  26. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +89 -0
  27. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +9 -6
  28. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +88 -0
  29. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +0 -6
  30. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +10 -6
  31. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +10 -25
  32. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +0 -5
  33. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +0 -5
  34. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +4 -0
  35. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -0
  36. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +44 -19
  37. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +140 -25
  38. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +40 -10
  39. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +2 -2
  40. package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +2 -2
  41. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +4 -1
  42. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +2 -2
  43. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
  44. package/src/excel-loaders/members.ts +233 -232
  45. package/src/excel-loaders/payments.ts +1 -1
  46. package/src/excel-loaders/receivable-balances.ts +1 -1
  47. package/src/excel-loaders/registrations.ts +153 -0
  48. package/src/helpers/AdminPermissionChecker.ts +65 -37
  49. package/src/helpers/AuthenticatedStructures.ts +43 -3
  50. package/src/helpers/Context.ts +29 -1
  51. package/src/helpers/GlobalHelper.ts +3 -1
  52. package/src/helpers/GroupedThrottledQueue.test.ts +219 -0
  53. package/src/helpers/GroupedThrottledQueue.ts +108 -0
  54. package/src/helpers/LimitedFilteredRequestHelper.ts +26 -1
  55. package/src/helpers/MemberCharger.ts +0 -5
  56. package/src/helpers/MembershipCharger.ts +3 -9
  57. package/src/helpers/OrganizationCharger.ts +0 -5
  58. package/src/helpers/ThrottledQueue.test.ts +194 -0
  59. package/src/helpers/ThrottledQueue.ts +145 -0
  60. package/src/helpers/XlsxTransformerColumnHelper.ts +44 -1
  61. package/src/middleware/ContextMiddleware.ts +1 -1
  62. package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +2 -1
  63. package/src/seeds/1735577912-update-cached-outstanding-balance-from-items.ts +2 -1
  64. package/src/services/BalanceItemPaymentService.ts +1 -33
  65. package/src/services/BalanceItemService.ts +167 -48
  66. package/src/services/FileSignService.ts +18 -13
  67. package/src/services/MemberRecordStore.ts +28 -19
  68. package/src/services/PaymentReallocationService.test.ts +25 -14
  69. package/src/services/PaymentReallocationService.ts +29 -10
  70. package/src/services/PaymentService.ts +4 -16
  71. package/src/services/PlatformMembershipService.ts +8 -4
  72. package/src/services/RegistrationService.ts +66 -2
  73. package/src/sql-filters/base-registration-filter-compilers.ts +43 -0
  74. package/src/sql-filters/groups.ts +67 -0
  75. package/src/sql-filters/members.ts +33 -58
  76. package/src/sql-filters/organization-registration-periods.ts +8 -0
  77. package/src/sql-filters/registration-periods.ts +8 -0
  78. package/src/sql-filters/registrations.ts +11 -22
  79. package/src/sql-sorters/groups.ts +24 -0
  80. package/src/sql-sorters/organization-registration-periods.ts +24 -0
  81. package/src/sql-sorters/registration-periods.ts +47 -0
  82. package/src/sql-sorters/registrations.ts +77 -0
  83. package/tests/actions/patchOrganizationMember.ts +27 -0
  84. package/tests/actions/patchPaymentStatus.ts +45 -0
  85. package/tests/actions/patchUserMember.ts +27 -0
  86. package/tests/assertions/assertBalances.ts +49 -0
  87. package/tests/e2e/api-rate-limits.test.ts +5 -5
  88. package/tests/e2e/bundle-discounts.test.ts +4060 -0
  89. package/tests/e2e/charge-members.test.ts +27 -24
  90. package/tests/e2e/documents.test.ts +398 -0
  91. package/tests/e2e/register.test.ts +292 -312
  92. package/tests/helpers/PayconiqMocker.ts +55 -0
  93. package/tests/init/index.ts +5 -0
  94. package/tests/init/initAdmin.ts +14 -0
  95. package/tests/init/initBundleDiscount.ts +47 -0
  96. package/tests/init/initPayconiq.ts +9 -0
  97. package/tests/init/initPlatformAdmin.ts +13 -0
  98. package/tests/init/initStripe.ts +21 -0
  99. package/tests/jest.setup.ts +29 -0
  100. package/src/seeds-temporary/1736266448-recall-balance-item-price-paid.ts +0 -70
@@ -1,9 +1,9 @@
1
1
  import { PatchableArray, PatchMap, patchObject } from '@simonbackx/simple-encoding';
2
2
  import { Endpoint, Request } from '@simonbackx/simple-endpoints';
3
3
  import { EmailMocker } from '@stamhoofd/email';
4
- import { EmailTemplateFactory, EventFactory, EventNotification, EventNotificationFactory, EventNotificationTypeFactory, Organization, OrganizationFactory, RecordAnswerFactory, RecordCategoryFactory, RegistrationPeriodFactory, Token, User, UserFactory } from '@stamhoofd/models';
4
+ import { EmailTemplateFactory, EventFactory, EventNotification, EventNotificationFactory, EventNotificationTypeFactory, Organization, OrganizationFactory, Platform, RecordAnswerFactory, RecordCategoryFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, User, UserFactory } from '@stamhoofd/models';
5
5
  import { AccessRight, BaseOrganization, EmailTemplateType, Event, EventNotificationStatus, EventNotification as EventNotificationStruct, Permissions, PermissionsResourceType, RecordType, ResourcePermissions } from '@stamhoofd/structures';
6
- import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
6
+ import { STExpect, TestUtils } from '@stamhoofd/test-utils';
7
7
  import { testServer } from '../../../../tests/helpers/TestServer';
8
8
  import { PatchEventNotificationsEndpoint } from './PatchEventNotificationsEndpoint';
9
9
 
@@ -150,7 +150,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
150
150
  );
151
151
 
152
152
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
153
- SHExpect.simpleError({ code: 'invalid_field', field: 'typeId' }),
153
+ STExpect.simpleError({ code: 'invalid_field', field: 'typeId' }),
154
154
  );
155
155
  });
156
156
 
@@ -175,11 +175,17 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
175
175
  );
176
176
 
177
177
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
178
- SHExpect.simpleError({ code: 'invalid_field', field: 'events' }),
178
+ STExpect.simpleError({ code: 'invalid_field', field: 'events' }),
179
179
  );
180
180
  });
181
181
 
182
182
  test('It throws when trying to create an event notification for a locked period', async () => {
183
+ // Clear all periods and organizations (to make sure the right locked period is used)
184
+ const platform = await Platform.getForEditing();
185
+ await platform.delete();
186
+ await Organization.delete();
187
+ await RegistrationPeriod.delete();
188
+
183
189
  await new RegistrationPeriodFactory({
184
190
  startDate: new Date(2050, 0, 1),
185
191
  endDate: new Date(2051, 11, 31),
@@ -209,7 +215,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
209
215
  }),
210
216
  );
211
217
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
212
- SHExpect.simpleError({ code: 'invalid_period', field: 'startDate' }),
218
+ STExpect.simpleError({ code: 'invalid_period', field: 'startDate' }),
213
219
  );
214
220
  });
215
221
 
@@ -237,7 +243,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
237
243
  );
238
244
 
239
245
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
240
- SHExpect.simpleError({ code: 'invalid_field', field: 'events' }),
246
+ STExpect.simpleError({ code: 'invalid_field', field: 'events' }),
241
247
  );
242
248
  });
243
249
 
@@ -268,7 +274,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
268
274
  submittedBy: expect.objectContaining({ id: user.id }),
269
275
  });
270
276
 
271
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
277
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
272
278
  expect.objectContaining({
273
279
  to: 'event-notification-reviewer@example.com',
274
280
  subject: EmailTemplateType.EventNotificationSubmittedReviewer,
@@ -314,7 +320,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
314
320
  submittedBy: expect.objectContaining({ id: user.id }),
315
321
  });
316
322
 
317
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
323
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
318
324
  expect.objectContaining({
319
325
  to: 'event-notification-reviewer@example.com',
320
326
  subject: EmailTemplateType.EventNotificationSubmittedReviewer,
@@ -360,7 +366,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
360
366
  submittedBy: expect.objectContaining({ id: user.id }),
361
367
  });
362
368
 
363
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
369
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
364
370
  expect.objectContaining({
365
371
  to: 'event-notification-reviewer@example.com',
366
372
  subject: EmailTemplateType.EventNotificationSubmittedReviewer,
@@ -397,7 +403,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
397
403
  );
398
404
 
399
405
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
400
- SHExpect.simpleError({ code: 'permission_denied' }),
406
+ STExpect.simpleError({ code: 'permission_denied' }),
401
407
  );
402
408
  });
403
409
 
@@ -423,7 +429,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
423
429
  );
424
430
 
425
431
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
426
- SHExpect.simpleError({ code: 'permission_denied' }),
432
+ STExpect.simpleError({ code: 'permission_denied' }),
427
433
  );
428
434
  });
429
435
 
@@ -449,7 +455,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
449
455
  );
450
456
 
451
457
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
452
- SHExpect.simpleError({ code: 'permission_denied' }),
458
+ STExpect.simpleError({ code: 'permission_denied' }),
453
459
  );
454
460
  });
455
461
 
@@ -475,7 +481,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
475
481
  );
476
482
 
477
483
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
478
- SHExpect.simpleError({ code: 'permission_denied' }),
484
+ STExpect.simpleError({ code: 'permission_denied' }),
479
485
  );
480
486
  });
481
487
 
@@ -501,7 +507,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
501
507
  );
502
508
 
503
509
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
504
- SHExpect.simpleError({ code: 'permission_denied' }),
510
+ STExpect.simpleError({ code: 'permission_denied' }),
505
511
  );
506
512
  });
507
513
 
@@ -584,7 +590,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
584
590
  );
585
591
 
586
592
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
587
- SHExpect.simpleError({ code: 'permission_denied' }),
593
+ STExpect.simpleError({ code: 'permission_denied' }),
588
594
  );
589
595
  });
590
596
 
@@ -638,7 +644,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
638
644
  });
639
645
 
640
646
  // Check mails
641
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
647
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
642
648
  expect.objectContaining({
643
649
  to: user.email,
644
650
  subject: EmailTemplateType.EventNotificationAccepted,
@@ -699,7 +705,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
699
705
  });
700
706
 
701
707
  // Check mails
702
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
708
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
703
709
  expect.objectContaining({
704
710
  to: user.email,
705
711
  subject: EmailTemplateType.EventNotificationPartiallyAccepted,
@@ -761,7 +767,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
761
767
  });
762
768
 
763
769
  // Check mails
764
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
770
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([
765
771
  expect.objectContaining({
766
772
  to: user.email,
767
773
  subject: EmailTemplateType.EventNotificationRejected,
@@ -821,7 +827,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
821
827
  });
822
828
 
823
829
  // Check mails
824
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([]);
830
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([]);
825
831
  });
826
832
 
827
833
  test('An admin can edit an accepted event notification', async () => {
@@ -884,7 +890,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
884
890
  });
885
891
 
886
892
  // Check mails
887
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([]);
893
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([]);
888
894
  });
889
895
 
890
896
  test('An admin can delete an event notification', async () => {
@@ -928,7 +934,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
928
934
  expect(result.body).toHaveLength(0);
929
935
 
930
936
  // Check mails
931
- expect(EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([]);
937
+ expect(await EmailMocker.transactional.getSucceededEmails()).toIncludeSameMembers([]);
932
938
 
933
939
  // Check not exists
934
940
  const model = await EventNotification.getByID(eventNotification.id);
@@ -972,7 +978,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
972
978
  );
973
979
 
974
980
  await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
975
- SHExpect.simpleError({ code: 'permission_denied' }),
981
+ STExpect.simpleError({ code: 'permission_denied' }),
976
982
  );
977
983
  });
978
984
  });
@@ -32,6 +32,47 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
32
32
  return [false];
33
33
  }
34
34
 
35
+ async putEventGroup(event: Event, putGroup: GroupStruct) {
36
+ const period = await RegistrationPeriod.getByDate(event.startDate);
37
+
38
+ if (!period) {
39
+ throw new SimpleError({
40
+ code: 'invalid_period',
41
+ message: 'No period found for this start date',
42
+ human: Context.i18n.$t('5959a6a9-064a-413c-871f-c74a145ed569'),
43
+ field: 'startDate',
44
+ });
45
+ }
46
+
47
+ let group = await Group.getByID(putGroup.id);
48
+ const groupOrganizationId = group?.organizationId ?? putGroup.organizationId;
49
+
50
+ if (event.organizationId && groupOrganizationId !== event.organizationId) {
51
+ // Silently ignore organizationId if it is invalid
52
+ putGroup.organizationId = event.organizationId;
53
+ }
54
+
55
+ if (!group) {
56
+ putGroup.type = GroupType.EventRegistration;
57
+
58
+ group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
59
+ putGroup,
60
+ putGroup.organizationId,
61
+ period,
62
+ );
63
+ }
64
+ else {
65
+ if (group.type !== GroupType.EventRegistration) {
66
+ throw new SimpleError({
67
+ code: 'invalid_group',
68
+ message: 'Group is not of type EventRegistration',
69
+ });
70
+ }
71
+ }
72
+
73
+ return group;
74
+ }
75
+
35
76
  async handle(request: DecodedRequest<Params, Query, Body>) {
36
77
  const organization = await Context.setOptionalOrganizationScope();
37
78
  await Context.authenticate();
@@ -83,7 +124,7 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
83
124
  });
84
125
  }
85
126
 
86
- const eventOrganization = await this.checkEventAccess(event);
127
+ const eventOrganization = await Context.auth.checkEventAccess(event);
87
128
  event.id = put.id;
88
129
  event.name = put.name;
89
130
  event.startDate = put.startDate;
@@ -95,23 +136,7 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
95
136
  await PatchEventsEndpoint.checkEventLimits(event);
96
137
 
97
138
  if (put.group) {
98
- const period = await RegistrationPeriod.getByDate(event.startDate);
99
-
100
- if (!period) {
101
- throw new SimpleError({
102
- code: 'invalid_period',
103
- message: 'No period found for this start date',
104
- human: Context.i18n.$t('5959a6a9-064a-413c-871f-c74a145ed569'),
105
- field: 'startDate',
106
- });
107
- }
108
-
109
- put.group.type = GroupType.EventRegistration;
110
- const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
111
- put.group,
112
- put.group.organizationId,
113
- period,
114
- );
139
+ const group = await this.putEventGroup(event, put.group);
115
140
  await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
116
141
  await event.syncGroupRequirements(group);
117
142
  });
@@ -140,7 +165,7 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
140
165
  });
141
166
  }
142
167
 
143
- await this.checkEventAccess(event);
168
+ await Context.auth.checkEventAccess(event);
144
169
 
145
170
  if (patch.meta?.organizationCache) {
146
171
  throw new SimpleError({
@@ -186,7 +211,7 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
186
211
  });
187
212
  }
188
213
 
189
- const eventOrganization = await this.checkEventAccess(event);
214
+ const eventOrganization = await Context.auth.checkEventAccess(event);
190
215
  if (eventOrganization) {
191
216
  event.meta.organizationCache = NamedObject.create({ id: eventOrganization.id, name: eventOrganization.name });
192
217
  }
@@ -206,6 +231,8 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
206
231
 
207
232
  await PatchEventsEndpoint.checkEventLimits(event);
208
233
 
234
+ let group: Group | null = null;
235
+
209
236
  if (patch.group !== undefined) {
210
237
  if (patch.group === null) {
211
238
  // delete
@@ -226,33 +253,22 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
226
253
  patch.group.type = GroupType.EventRegistration;
227
254
 
228
255
  const period = await RegistrationPeriod.getByDate(event.startDate);
229
- await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(patch.group, period);
256
+ group = await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(patch.group, period);
230
257
  }
231
258
  else {
232
- if (event.groupId) {
233
- // need to delete old group first
234
- await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(event.groupId);
235
- event.groupId = null;
259
+ if (event.groupId === patch.group.id) {
260
+ // ignore: bad practice: puts are not allowed like this
261
+ // risk of deleting data
236
262
  }
237
- patch.group.type = GroupType.EventRegistration;
238
-
239
- const period = await RegistrationPeriod.getByDate(event.startDate);
240
-
241
- if (!period) {
242
- throw new SimpleError({
243
- code: 'invalid_period',
244
- message: 'No period found for this start date',
245
- human: Context.i18n.$t('5959a6a9-064a-413c-871f-c74a145ed569'),
246
- field: 'startDate',
247
- });
263
+ else {
264
+ if (event.groupId) {
265
+ // need to delete old group first
266
+ await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(event.groupId);
267
+ event.groupId = null;
268
+ }
269
+ group = await this.putEventGroup(event, patch.group);
270
+ event.groupId = group.id;
248
271
  }
249
-
250
- const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
251
- patch.group,
252
- patch.group.organizationId,
253
- period,
254
- );
255
- event.groupId = group.id;
256
272
  }
257
273
  }
258
274
  else {
@@ -262,13 +278,33 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
262
278
  if (event.groupId) {
263
279
  await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
264
280
  if (event.groupId) {
265
- await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(GroupStruct.patch({ id: event.groupId }), period);
281
+ group = await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(GroupStruct.patch({ id: event.groupId }), period);
266
282
  }
267
283
  });
268
284
  }
269
285
  }
270
286
  }
271
287
 
288
+ if (group || patch.organizationId !== undefined) {
289
+ if (event.organizationId === null) {
290
+ // No validation required
291
+ }
292
+ else {
293
+ // Validate organizationId of group
294
+ if (event.groupId) {
295
+ group = group ?? (await Group.getByID(event.groupId) ?? null);
296
+
297
+ if (group && group.organizationId !== event.organizationId) {
298
+ throw new SimpleError({
299
+ code: 'invalid_group',
300
+ message: 'Group is not of the same organization',
301
+ human: $t('1f64237b-84c4-43e7-b752-2875fd1eb075'),
302
+ });
303
+ }
304
+ }
305
+ }
306
+ }
307
+
272
308
  if (type.isLocationRequired === true) {
273
309
  PatchEventsEndpoint.throwIfAddressIsMissing(event);
274
310
  }
@@ -293,7 +329,7 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
293
329
  throw new SimpleError({ code: 'not_found', message: 'Event not found', statusCode: 404 });
294
330
  }
295
331
 
296
- await this.checkEventAccess(event);
332
+ await Context.auth.checkEventAccess(event);
297
333
 
298
334
  if (event.groupId) {
299
335
  await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(event.groupId);
@@ -415,10 +451,6 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
415
451
  }
416
452
  }
417
453
 
418
- private async checkEventAccess(event: Event) {
419
- return await Context.auth.checkEventAccess(event);
420
- }
421
-
422
454
  private static throwIfAddressIsMissing(event: Event) {
423
455
  const address = event.meta.location?.address;
424
456
 
@@ -1,15 +1,16 @@
1
+ import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; // ES Modules import
1
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
3
  import { SimpleError } from '@simonbackx/simple-errors';
3
4
  import { File } from '@stamhoofd/structures';
4
5
  import { Formatter } from '@stamhoofd/utility';
5
- import AWS from 'aws-sdk';
6
6
  import formidable from 'formidable';
7
7
  import { promises as fs } from 'fs';
8
8
  import { v4 as uuidv4 } from 'uuid';
9
9
 
10
+ import { AutoEncoder, BooleanDecoder, Decoder, field } from '@simonbackx/simple-encoding';
10
11
  import { Context } from '../../../helpers/Context';
11
12
  import { limiter } from './UploadImage';
12
- import { AutoEncoder, BooleanDecoder, Decoder, field } from '@simonbackx/simple-encoding';
13
+ import { Image } from '@stamhoofd/models';
13
14
 
14
15
  type Params = Record<string, never>;
15
16
  class Query extends AutoEncoder {
@@ -115,12 +116,6 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
115
116
 
116
117
  const fileContent = await fs.readFile(file.filepath);
117
118
 
118
- const s3 = new AWS.S3({
119
- endpoint: STAMHOOFD.SPACES_ENDPOINT,
120
- accessKeyId: STAMHOOFD.SPACES_KEY,
121
- secretAccessKey: STAMHOOFD.SPACES_SECRET,
122
- });
123
-
124
119
  let prefix = (STAMHOOFD.SPACES_PREFIX ?? '');
125
120
  if (prefix.length > 0) {
126
121
  prefix += '/';
@@ -175,13 +170,6 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
175
170
 
176
171
  const filenameWithoutExt = file.originalFilename?.split('.').slice(0, -1).join('.') ?? fileId;
177
172
  const key = prefix + fileId + '/' + (Formatter.slug(filenameWithoutExt) + (uploadExt ? ('.' + uploadExt) : ''));
178
- const params = {
179
- Bucket: STAMHOOFD.SPACES_BUCKET,
180
- Key: key,
181
- Body: fileContent, // TODO
182
- ContentType: file.mimetype ?? 'application/pdf',
183
- ACL: request.query.isPrivate ? 'private' : 'public-read',
184
- };
185
173
 
186
174
  const fileStruct = new File({
187
175
  id: fileId,
@@ -204,7 +192,14 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
204
192
  }
205
193
  }
206
194
 
207
- await s3.putObject(params).promise();
195
+ const cmd = new PutObjectCommand({
196
+ Bucket: STAMHOOFD.SPACES_BUCKET,
197
+ Key: key,
198
+ Body: fileContent,
199
+ ContentType: file.mimetype ?? 'application/pdf',
200
+ ACL: request.query.isPrivate ? 'private' : 'public-read',
201
+ });
202
+ await Image.getS3Client().send(cmd);
208
203
 
209
204
  return new Response(fileStruct);
210
205
  }