@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.
- package/index.ts +19 -4
- package/package.json +18 -14
- package/src/crons/amazon-ses.ts +26 -5
- package/src/crons/balance-emails.ts +18 -17
- package/src/email-recipient-loaders/registrations.ts +87 -0
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +5 -2
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +40 -40
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +28 -22
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +81 -49
- package/src/endpoints/global/files/UploadFile.ts +11 -16
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +234 -0
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +117 -43
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +1054 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +163 -141
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +6 -6
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +0 -16
- package/src/endpoints/global/members/helpers/validateGroupFilter.ts +73 -0
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +1 -2
- package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +43 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1016 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +234 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -5
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +474 -554
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +191 -52
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +107 -9
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +89 -0
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +9 -6
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +88 -0
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +0 -6
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +10 -6
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +10 -25
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +0 -5
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +0 -5
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +4 -0
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -0
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +44 -19
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +140 -25
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +40 -10
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +4 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +2 -2
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
- package/src/excel-loaders/members.ts +233 -232
- package/src/excel-loaders/payments.ts +1 -1
- package/src/excel-loaders/receivable-balances.ts +1 -1
- package/src/excel-loaders/registrations.ts +153 -0
- package/src/helpers/AdminPermissionChecker.ts +65 -37
- package/src/helpers/AuthenticatedStructures.ts +43 -3
- package/src/helpers/Context.ts +29 -1
- package/src/helpers/GlobalHelper.ts +3 -1
- package/src/helpers/GroupedThrottledQueue.test.ts +219 -0
- package/src/helpers/GroupedThrottledQueue.ts +108 -0
- package/src/helpers/LimitedFilteredRequestHelper.ts +26 -1
- package/src/helpers/MemberCharger.ts +0 -5
- package/src/helpers/MembershipCharger.ts +3 -9
- package/src/helpers/OrganizationCharger.ts +0 -5
- package/src/helpers/ThrottledQueue.test.ts +194 -0
- package/src/helpers/ThrottledQueue.ts +145 -0
- package/src/helpers/XlsxTransformerColumnHelper.ts +44 -1
- package/src/middleware/ContextMiddleware.ts +1 -1
- package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +2 -1
- package/src/seeds/1735577912-update-cached-outstanding-balance-from-items.ts +2 -1
- package/src/services/BalanceItemPaymentService.ts +1 -33
- package/src/services/BalanceItemService.ts +167 -48
- package/src/services/FileSignService.ts +18 -13
- package/src/services/MemberRecordStore.ts +28 -19
- package/src/services/PaymentReallocationService.test.ts +25 -14
- package/src/services/PaymentReallocationService.ts +29 -10
- package/src/services/PaymentService.ts +4 -16
- package/src/services/PlatformMembershipService.ts +8 -4
- package/src/services/RegistrationService.ts +66 -2
- package/src/sql-filters/base-registration-filter-compilers.ts +43 -0
- package/src/sql-filters/groups.ts +67 -0
- package/src/sql-filters/members.ts +33 -58
- package/src/sql-filters/organization-registration-periods.ts +8 -0
- package/src/sql-filters/registration-periods.ts +8 -0
- package/src/sql-filters/registrations.ts +11 -22
- package/src/sql-sorters/groups.ts +24 -0
- package/src/sql-sorters/organization-registration-periods.ts +24 -0
- package/src/sql-sorters/registration-periods.ts +47 -0
- package/src/sql-sorters/registrations.ts +77 -0
- package/tests/actions/patchOrganizationMember.ts +27 -0
- package/tests/actions/patchPaymentStatus.ts +45 -0
- package/tests/actions/patchUserMember.ts +27 -0
- package/tests/assertions/assertBalances.ts +49 -0
- package/tests/e2e/api-rate-limits.test.ts +5 -5
- package/tests/e2e/bundle-discounts.test.ts +4060 -0
- package/tests/e2e/charge-members.test.ts +27 -24
- package/tests/e2e/documents.test.ts +398 -0
- package/tests/e2e/register.test.ts +292 -312
- package/tests/helpers/PayconiqMocker.ts +55 -0
- package/tests/init/index.ts +5 -0
- package/tests/init/initAdmin.ts +14 -0
- package/tests/init/initBundleDiscount.ts +47 -0
- package/tests/init/initPayconiq.ts +9 -0
- package/tests/init/initPlatformAdmin.ts +13 -0
- package/tests/init/initStripe.ts +21 -0
- package/tests/jest.setup.ts +29 -0
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
234
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
}
|