@stamhoofd/backend 2.80.1 → 2.82.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 -4
- package/package.json +10 -10
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +139 -0
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +28 -5
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +16 -35
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +8 -8
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +5 -1
- package/src/endpoints/global/platform/GetPlatformEndpoint.test.ts +68 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -7
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +106 -0
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +16 -3
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +247 -0
- package/src/endpoints/{auth → organization/dashboard/users}/PatchApiUserEndpoint.ts +24 -5
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +5 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +6 -1
- package/src/excel-loaders/event-notifications.ts +133 -0
- package/src/excel-loaders/index.ts +5 -0
- package/src/excel-loaders/members.ts +15 -38
- package/src/excel-loaders/organizations.ts +2 -2
- package/src/excel-loaders/payments.ts +2 -2
- package/src/helpers/AuthenticatedStructures.ts +8 -0
- package/src/helpers/CheckSettlements.ts +1 -1
- package/src/helpers/Context.ts +28 -12
- package/src/helpers/StripeHelper.ts +12 -1
- package/src/helpers/{xlsxAddressTransformerColumnFactory.ts → XlsxTransformerColumnHelper.ts} +68 -2
- package/tests/e2e/api-rate-limits.test.ts +188 -0
- package/tests/helpers/StripeMocker.ts +7 -1
- /package/src/endpoints/global/platform/{GetPlatformEnpoint.ts → GetPlatformEndpoint.ts} +0 -0
package/index.ts
CHANGED
|
@@ -111,10 +111,7 @@ const start = async () => {
|
|
|
111
111
|
console.log('Loading loaders...');
|
|
112
112
|
|
|
113
113
|
// Register Excel loaders
|
|
114
|
-
await import('./src/excel-loaders
|
|
115
|
-
await import('./src/excel-loaders/payments');
|
|
116
|
-
await import('./src/excel-loaders/organizations');
|
|
117
|
-
await import('./src/excel-loaders/receivable-balances');
|
|
114
|
+
await import('./src/excel-loaders');
|
|
118
115
|
|
|
119
116
|
// Register Email Recipient loaders
|
|
120
117
|
await import('./src/email-recipient-loaders/members');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.82.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"@simonbackx/simple-encoding": "2.22.0",
|
|
39
39
|
"@simonbackx/simple-endpoints": "1.19.1",
|
|
40
40
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
41
|
-
"@stamhoofd/backend-i18n": "2.
|
|
42
|
-
"@stamhoofd/backend-middleware": "2.
|
|
43
|
-
"@stamhoofd/email": "2.
|
|
44
|
-
"@stamhoofd/models": "2.
|
|
45
|
-
"@stamhoofd/queues": "2.
|
|
46
|
-
"@stamhoofd/sql": "2.
|
|
47
|
-
"@stamhoofd/structures": "2.
|
|
48
|
-
"@stamhoofd/utility": "2.
|
|
41
|
+
"@stamhoofd/backend-i18n": "2.82.0",
|
|
42
|
+
"@stamhoofd/backend-middleware": "2.82.0",
|
|
43
|
+
"@stamhoofd/email": "2.82.0",
|
|
44
|
+
"@stamhoofd/models": "2.82.0",
|
|
45
|
+
"@stamhoofd/queues": "2.82.0",
|
|
46
|
+
"@stamhoofd/sql": "2.82.0",
|
|
47
|
+
"@stamhoofd/structures": "2.82.0",
|
|
48
|
+
"@stamhoofd/utility": "2.82.0",
|
|
49
49
|
"archiver": "^7.0.1",
|
|
50
50
|
"aws-sdk": "^2.885.0",
|
|
51
51
|
"axios": "1.6.8",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"publishConfig": {
|
|
66
66
|
"access": "public"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "ce2a8fa52bc4881cc00f1c5d61c737cf19f3a62e"
|
|
69
69
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
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";
|
|
5
|
+
import { testServer } from "../../../../tests/helpers/TestServer";
|
|
6
|
+
import { PatchEmailEndpoint } from "./PatchEmailEndpoint";
|
|
7
|
+
|
|
8
|
+
const baseUrl = `/v${Version}/email`;
|
|
9
|
+
|
|
10
|
+
describe('Endpoint.PatchEmailEndpoint', () => {
|
|
11
|
+
const endpoint = new PatchEmailEndpoint();
|
|
12
|
+
let period: RegistrationPeriod;
|
|
13
|
+
let organization: Organization;
|
|
14
|
+
let token: Token;
|
|
15
|
+
let user: User;
|
|
16
|
+
|
|
17
|
+
const patchEmail = async (email: EmailStruct, token: Token, organization?: Organization) => {
|
|
18
|
+
const id = email.id;
|
|
19
|
+
const request = Request.buildJson('PATCH', `${baseUrl}/${id}`, organization?.getApiHost(), email);
|
|
20
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
21
|
+
return await testServer.test(endpoint, request);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
TestUtils.setEnvironment('userMode', 'platform');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
period = await new RegistrationPeriodFactory({
|
|
30
|
+
startDate: new Date(2023, 0, 1),
|
|
31
|
+
endDate: new Date(2023, 11, 31),
|
|
32
|
+
}).create();
|
|
33
|
+
|
|
34
|
+
organization = await new OrganizationFactory({ period })
|
|
35
|
+
.create();
|
|
36
|
+
|
|
37
|
+
user = await new UserFactory({
|
|
38
|
+
organization,
|
|
39
|
+
permissions: Permissions.create({
|
|
40
|
+
level: PermissionLevel.Read,
|
|
41
|
+
}),
|
|
42
|
+
})
|
|
43
|
+
.create();
|
|
44
|
+
|
|
45
|
+
token = await Token.createToken(user);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('Should throw error if no unsubscribe button in email html', async () => {
|
|
49
|
+
const email = new Email();
|
|
50
|
+
email.subject = 'test subject';
|
|
51
|
+
email.status = EmailStatus.Draft;
|
|
52
|
+
email.text = 'test email {{unsubscribeUrl}}';
|
|
53
|
+
email.html = `<!DOCTYPE html>
|
|
54
|
+
<html>
|
|
55
|
+
|
|
56
|
+
<head>
|
|
57
|
+
<meta charset="utf-8" />
|
|
58
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
59
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
|
60
|
+
<title>test</title>
|
|
61
|
+
</head>
|
|
62
|
+
|
|
63
|
+
<body>
|
|
64
|
+
<p style="margin: 0; padding: 0; line-height: 1.4;">test email</p>
|
|
65
|
+
</body>
|
|
66
|
+
|
|
67
|
+
</html>`;
|
|
68
|
+
email.json = {
|
|
69
|
+
"content": [
|
|
70
|
+
{
|
|
71
|
+
"content": [
|
|
72
|
+
{
|
|
73
|
+
"text": "test email",
|
|
74
|
+
"type": "text"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"type": "paragraph"
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"type": "doc"
|
|
81
|
+
};
|
|
82
|
+
email.userId = user.id;
|
|
83
|
+
email.organizationId = organization.id;
|
|
84
|
+
|
|
85
|
+
await email.save();
|
|
86
|
+
|
|
87
|
+
const body = EmailStruct.create({...email, fromAddress:'test@test.be', status: EmailStatus.Sending})
|
|
88
|
+
|
|
89
|
+
await expect(async () => await patchEmail(body, token, organization))
|
|
90
|
+
.rejects
|
|
91
|
+
.toThrow('Missing unsubscribe button');
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('Should throw error if no unsubscribe button in email text', async () => {
|
|
95
|
+
const email = new Email();
|
|
96
|
+
email.subject = 'test subject';
|
|
97
|
+
email.status = EmailStatus.Draft;
|
|
98
|
+
email.text = 'test email';
|
|
99
|
+
email.html = `<!DOCTYPE html>
|
|
100
|
+
<html>
|
|
101
|
+
|
|
102
|
+
<head>
|
|
103
|
+
<meta charset="utf-8" />
|
|
104
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
105
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
|
106
|
+
<title>test</title>
|
|
107
|
+
</head>
|
|
108
|
+
|
|
109
|
+
<body>
|
|
110
|
+
<p style="margin: 0; padding: 0; line-height: 1.4;">test email {{unsubscribeUrl}}</p>
|
|
111
|
+
</body>
|
|
112
|
+
|
|
113
|
+
</html>`;
|
|
114
|
+
email.json = {
|
|
115
|
+
"content": [
|
|
116
|
+
{
|
|
117
|
+
"content": [
|
|
118
|
+
{
|
|
119
|
+
"text": "test email",
|
|
120
|
+
"type": "text"
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
"type": "paragraph"
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"type": "doc"
|
|
127
|
+
};
|
|
128
|
+
email.userId = user.id;
|
|
129
|
+
email.organizationId = organization.id;
|
|
130
|
+
|
|
131
|
+
await email.save();
|
|
132
|
+
|
|
133
|
+
const body = EmailStruct.create({...email, fromAddress:'test@test.be', status: EmailStatus.Sending})
|
|
134
|
+
|
|
135
|
+
await expect(async () => await patchEmail(body, token, organization))
|
|
136
|
+
.rejects
|
|
137
|
+
.toThrow('Missing unsubscribe button');
|
|
138
|
+
})
|
|
139
|
+
})
|
|
@@ -2,19 +2,15 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
2
2
|
import { Email } from '@stamhoofd/models';
|
|
3
3
|
import { EmailPreview, EmailStatus, Email as EmailStruct } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
|
+
import { AutoEncoderPatchType, Decoder, patchObject } from '@simonbackx/simple-encoding';
|
|
5
6
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
7
|
import { Context } from '../../../helpers/Context';
|
|
7
|
-
import { AutoEncoderPatchType, Decoder, patchObject } from '@simonbackx/simple-encoding';
|
|
8
8
|
|
|
9
9
|
type Params = { id: string };
|
|
10
10
|
type Query = undefined;
|
|
11
11
|
type Body = AutoEncoderPatchType<EmailStruct>;
|
|
12
12
|
type ResponseBody = EmailPreview;
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* One endpoint to create, patch and delete groups. Usefull because on organization setup, we need to create multiple groups at once. Also, sometimes we need to link values and update multiple groups at once
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
14
|
export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
19
15
|
bodyDecoder = EmailStruct.patchType() as Decoder<AutoEncoderPatchType<EmailStruct>>;
|
|
20
16
|
|
|
@@ -107,6 +103,33 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
107
103
|
|
|
108
104
|
if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent) {
|
|
109
105
|
model.throwIfNotReadyToSend();
|
|
106
|
+
|
|
107
|
+
const replacement = '{{unsubscribeUrl}}';
|
|
108
|
+
|
|
109
|
+
if (model.html) {
|
|
110
|
+
// Check email contains an unsubscribe button
|
|
111
|
+
if (!model.html.includes(replacement)) {
|
|
112
|
+
throw new SimpleError({
|
|
113
|
+
code: "missing_unsubscribe_button",
|
|
114
|
+
message: "Missing unsubscribe button",
|
|
115
|
+
human: "Je moet een ‘uitschrijven’-knop of link toevoegen onderaan je e-mail. Klik daarvoor onderaan op het ‘toverstaf’ icoontje en kies voor ‘Knop om uit te schrijven voor e-mails’. Dit is verplicht volgens de GDPR-wetgeving, maar het zorgt ook voor een betere e-mail reputatie omdat minder e-mails als spam worden gemarkeerd.",
|
|
116
|
+
field: "html"
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (model.text) {
|
|
122
|
+
// Check email contains an unsubscribe button
|
|
123
|
+
if (!model.text.includes(replacement)) {
|
|
124
|
+
throw new SimpleError({
|
|
125
|
+
code: "missing_unsubscribe_button",
|
|
126
|
+
message: "Missing unsubscribe button",
|
|
127
|
+
human: "Je moet een ‘uitschrijven’-knop of link toevoegen onderaan je e-mail. Klik daarvoor onderaan op het ‘toverstaf’ icoontje en kies voor ‘Knop om uit te schrijven voor e-mails’. Dit is verplicht volgens de GDPR-wetgeving, maar het zorgt ook voor een betere e-mail reputatie omdat minder e-mails als spam worden gemarkeerd.",
|
|
128
|
+
field: "text"
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
110
133
|
model.send().catch(console.error);
|
|
111
134
|
}
|
|
112
135
|
|
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
import { PatchableArray, PatchMap, patchObject } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { Endpoint, Request } from '@simonbackx/simple-endpoints';
|
|
3
|
-
import { EventNotificationFactory, EventFactory, EventNotificationTypeFactory, Organization, OrganizationFactory, Token, User, UserFactory, EmailTemplateFactory, RecordCategoryFactory, RegistrationPeriodFactory, RecordAnswerFactory, EventNotification } from '@stamhoofd/models';
|
|
4
|
-
import { AccessRight, BaseOrganization, EmailTemplateType, Event, EventNotificationStatus, EventNotification as EventNotificationStruct, PermissionLevel, Permissions, PermissionsResourceType, RecordAnswer, RecordType, ResourcePermissions } from '@stamhoofd/structures';
|
|
5
|
-
import { TestUtils } from '@stamhoofd/test-utils';
|
|
6
|
-
import { PatchEventNotificationsEndpoint } from './PatchEventNotificationsEndpoint';
|
|
7
|
-
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
8
3
|
import { EmailMocker } from '@stamhoofd/email';
|
|
4
|
+
import { EmailTemplateFactory, EventFactory, EventNotification, EventNotificationFactory, EventNotificationTypeFactory, Organization, OrganizationFactory, RecordAnswerFactory, RecordCategoryFactory, RegistrationPeriodFactory, Token, User, UserFactory } from '@stamhoofd/models';
|
|
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';
|
|
7
|
+
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
8
|
+
import { PatchEventNotificationsEndpoint } from './PatchEventNotificationsEndpoint';
|
|
9
9
|
|
|
10
10
|
const baseUrl = `/event-notifications`;
|
|
11
11
|
const endpoint = new PatchEventNotificationsEndpoint();
|
|
12
12
|
type EndpointType = typeof endpoint;
|
|
13
13
|
type Body = EndpointType extends Endpoint<any, any, infer B, any> ? B : never;
|
|
14
14
|
|
|
15
|
-
const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
|
|
16
|
-
const errorWithMessage = (message: string) => expect.objectContaining({ message }) as jest.Constructable;
|
|
17
|
-
const simpleError = (data: {
|
|
18
|
-
code?: string;
|
|
19
|
-
message?: string;
|
|
20
|
-
field?: string;
|
|
21
|
-
}) => {
|
|
22
|
-
const d = {
|
|
23
|
-
code: data.code ?? expect.any(String),
|
|
24
|
-
message: data.message ?? expect.any(String),
|
|
25
|
-
field: data.field ?? expect.anything(),
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
if (!data.field) {
|
|
29
|
-
delete d.field;
|
|
30
|
-
}
|
|
31
|
-
return expect.objectContaining(d) as jest.Constructable;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
15
|
const minimumUserPermissions = Permissions.create({
|
|
35
16
|
resources: new Map([
|
|
36
17
|
[PermissionsResourceType.Groups, new Map([
|
|
@@ -169,7 +150,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
169
150
|
);
|
|
170
151
|
|
|
171
152
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
172
|
-
simpleError({ code: 'invalid_field', field: 'typeId' }),
|
|
153
|
+
SHExpect.simpleError({ code: 'invalid_field', field: 'typeId' }),
|
|
173
154
|
);
|
|
174
155
|
});
|
|
175
156
|
|
|
@@ -194,7 +175,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
194
175
|
);
|
|
195
176
|
|
|
196
177
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
197
|
-
simpleError({ code: 'invalid_field', field: 'events' }),
|
|
178
|
+
SHExpect.simpleError({ code: 'invalid_field', field: 'events' }),
|
|
198
179
|
);
|
|
199
180
|
});
|
|
200
181
|
|
|
@@ -228,7 +209,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
228
209
|
}),
|
|
229
210
|
);
|
|
230
211
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
231
|
-
simpleError({ code: 'invalid_period', field: 'startDate' }),
|
|
212
|
+
SHExpect.simpleError({ code: 'invalid_period', field: 'startDate' }),
|
|
232
213
|
);
|
|
233
214
|
});
|
|
234
215
|
|
|
@@ -256,7 +237,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
256
237
|
);
|
|
257
238
|
|
|
258
239
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
259
|
-
simpleError({ code: 'invalid_field', field: 'events' }),
|
|
240
|
+
SHExpect.simpleError({ code: 'invalid_field', field: 'events' }),
|
|
260
241
|
);
|
|
261
242
|
});
|
|
262
243
|
|
|
@@ -416,7 +397,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
416
397
|
);
|
|
417
398
|
|
|
418
399
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
419
|
-
simpleError({ code: 'permission_denied' }),
|
|
400
|
+
SHExpect.simpleError({ code: 'permission_denied' }),
|
|
420
401
|
);
|
|
421
402
|
});
|
|
422
403
|
|
|
@@ -442,7 +423,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
442
423
|
);
|
|
443
424
|
|
|
444
425
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
445
|
-
simpleError({ code: 'permission_denied' }),
|
|
426
|
+
SHExpect.simpleError({ code: 'permission_denied' }),
|
|
446
427
|
);
|
|
447
428
|
});
|
|
448
429
|
|
|
@@ -468,7 +449,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
468
449
|
);
|
|
469
450
|
|
|
470
451
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
471
|
-
simpleError({ code: 'permission_denied' }),
|
|
452
|
+
SHExpect.simpleError({ code: 'permission_denied' }),
|
|
472
453
|
);
|
|
473
454
|
});
|
|
474
455
|
|
|
@@ -494,7 +475,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
494
475
|
);
|
|
495
476
|
|
|
496
477
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
497
|
-
simpleError({ code: 'permission_denied' }),
|
|
478
|
+
SHExpect.simpleError({ code: 'permission_denied' }),
|
|
498
479
|
);
|
|
499
480
|
});
|
|
500
481
|
|
|
@@ -520,7 +501,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
520
501
|
);
|
|
521
502
|
|
|
522
503
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
523
|
-
simpleError({ code: 'permission_denied' }),
|
|
504
|
+
SHExpect.simpleError({ code: 'permission_denied' }),
|
|
524
505
|
);
|
|
525
506
|
});
|
|
526
507
|
|
|
@@ -603,7 +584,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
603
584
|
);
|
|
604
585
|
|
|
605
586
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
606
|
-
simpleError({ code: 'permission_denied' }),
|
|
587
|
+
SHExpect.simpleError({ code: 'permission_denied' }),
|
|
607
588
|
);
|
|
608
589
|
});
|
|
609
590
|
|
|
@@ -991,7 +972,7 @@ describe('Endpoint.PatchEventNotificationsEndpoint', () => {
|
|
|
991
972
|
);
|
|
992
973
|
|
|
993
974
|
await expect(TestRequest.patch({ body, user, organization })).rejects.toThrow(
|
|
994
|
-
simpleError({ code: 'permission_denied' }),
|
|
975
|
+
SHExpect.simpleError({ code: 'permission_denied' }),
|
|
995
976
|
);
|
|
996
977
|
});
|
|
997
978
|
});
|
|
@@ -3,7 +3,7 @@ import { PatchableArray, PatchableArrayAutoEncoder, PatchMap } from '@simonbackx
|
|
|
3
3
|
import { Endpoint, Request } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { GroupFactory, MemberFactory, OrganizationFactory, OrganizationTagFactory, Platform, RegistrationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
5
5
|
import { Address, Country, EmergencyContact, MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, OrganizationRecordsConfiguration, Parent, PatchAnswers, PermissionLevel, Permissions, PermissionsResourceType, RecordCategory, RecordSettings, RecordTextAnswer, ResourcePermissions, ReviewTime, ReviewTimes } from '@stamhoofd/structures';
|
|
6
|
-
import { TestUtils } from '@stamhoofd/test-utils';
|
|
6
|
+
import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
7
7
|
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
8
8
|
import { PatchOrganizationMembersEndpoint } from './PatchOrganizationMembersEndpoint';
|
|
9
9
|
|
|
@@ -16,8 +16,6 @@ const firstName = 'John';
|
|
|
16
16
|
const lastName = 'Doe';
|
|
17
17
|
const birthDay = { year: 1993, month: 4, day: 5 };
|
|
18
18
|
|
|
19
|
-
const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
|
|
20
|
-
|
|
21
19
|
describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
|
|
22
20
|
beforeEach(async () => {
|
|
23
21
|
TestUtils.setEnvironment('userMode', 'platform');
|
|
@@ -59,7 +57,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
|
|
|
59
57
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
60
58
|
await expect(testServer.test(endpoint, request))
|
|
61
59
|
.rejects
|
|
62
|
-
.toThrow(errorWithCode('known_member_missing_rights'));
|
|
60
|
+
.toThrow(SHExpect.errorWithCode('known_member_missing_rights'));
|
|
63
61
|
});
|
|
64
62
|
|
|
65
63
|
test('The security code is not a requirement for members without additional data', async () => {
|
|
@@ -219,7 +217,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
|
|
|
219
217
|
|
|
220
218
|
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
221
219
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
222
|
-
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('not_found'));
|
|
220
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('not_found'));
|
|
223
221
|
});
|
|
224
222
|
|
|
225
223
|
test('An admin can edit members registered in its own organization', async () => {
|
|
@@ -495,7 +493,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
|
|
|
495
493
|
|
|
496
494
|
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
497
495
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
498
|
-
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
496
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
|
|
499
497
|
});
|
|
500
498
|
|
|
501
499
|
test('An admin without record category permission cannot set the records in that category', async () => {
|
|
@@ -564,7 +562,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
|
|
|
564
562
|
|
|
565
563
|
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
566
564
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
567
|
-
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
565
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
|
|
568
566
|
});
|
|
569
567
|
|
|
570
568
|
test('An admin can set records of the platform', async () => {
|
|
@@ -1458,7 +1456,9 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
|
|
|
1458
1456
|
await member3.refresh();
|
|
1459
1457
|
|
|
1460
1458
|
// Check all parents equal
|
|
1461
|
-
const expectedParent = parent3
|
|
1459
|
+
const expectedParent = parent3.patch({
|
|
1460
|
+
createdAt: parent1.createdAt, // parent1 created at can be 1ms smaller, and oldest will be used
|
|
1461
|
+
});
|
|
1462
1462
|
|
|
1463
1463
|
expect(member1.details.parents).toEqual([expectedParent]);
|
|
1464
1464
|
expect(member2.details.parents).toEqual([expectedParent]);
|
|
@@ -52,6 +52,11 @@ export class StripeWebookEndpoint extends Endpoint<Params, Query, Body, Response
|
|
|
52
52
|
|
|
53
53
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
54
54
|
console.log('Received Stripe Webhook', request.body.type);
|
|
55
|
+
const secret = request.body.account ? STAMHOOFD.STRIPE_CONNECT_ENDPOINT_SECRET : STAMHOOFD.STRIPE_ENDPOINT_SECRET;
|
|
56
|
+
|
|
57
|
+
if (!secret) {
|
|
58
|
+
throw StripeHelper.notConfiguredError;
|
|
59
|
+
}
|
|
55
60
|
|
|
56
61
|
// Verify webhook signature and extract the event.
|
|
57
62
|
// See https://stripe.com/docs/webhooks/signatures for more information.
|
|
@@ -66,7 +71,6 @@ export class StripeWebookEndpoint extends Endpoint<Params, Query, Body, Response
|
|
|
66
71
|
statusCode: 400,
|
|
67
72
|
});
|
|
68
73
|
}
|
|
69
|
-
const secret = request.body.account ? STAMHOOFD.STRIPE_CONNECT_ENDPOINT_SECRET : STAMHOOFD.STRIPE_ENDPOINT_SECRET;
|
|
70
74
|
event = await stripe.webhooks.constructEventAsync(await request.request.bodyPromise!, sig, secret);
|
|
71
75
|
}
|
|
72
76
|
catch (err) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { Token, UserFactory } from '@stamhoofd/models';
|
|
3
|
+
import { PermissionLevel, Permissions, Version } from '@stamhoofd/structures';
|
|
4
|
+
|
|
5
|
+
import { TestUtils } from '@stamhoofd/test-utils';
|
|
6
|
+
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
7
|
+
import { GetPlatformEndpoint } from './GetPlatformEndpoint';
|
|
8
|
+
|
|
9
|
+
describe('Endpoint.GetPlatformEndpoint', () => {
|
|
10
|
+
const endpoint = new GetPlatformEndpoint();
|
|
11
|
+
|
|
12
|
+
const getPlatform = async (token?: Token) => {
|
|
13
|
+
const request = Request.buildJson('GET', `/v${Version}/platform`);
|
|
14
|
+
if (token) {
|
|
15
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
16
|
+
}
|
|
17
|
+
return await testServer.test(endpoint, request);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
TestUtils.setEnvironment('userMode', 'platform');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('Should return platform without private config if not authenticated', async () => {
|
|
25
|
+
const platform = await getPlatform();
|
|
26
|
+
expect(platform.body.privateConfig).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('Should return platform without private config if no platform access', async () => {
|
|
30
|
+
const user = await new UserFactory({
|
|
31
|
+
globalPermissions: Permissions.create({
|
|
32
|
+
level: PermissionLevel.None,
|
|
33
|
+
}),
|
|
34
|
+
})
|
|
35
|
+
.create();
|
|
36
|
+
|
|
37
|
+
const token = await Token.createToken(user);
|
|
38
|
+
const platform = await getPlatform(token);
|
|
39
|
+
expect(platform.body.privateConfig).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('Should return platform with private config if authenticated and has platform access', async () => {
|
|
43
|
+
const user = await new UserFactory({
|
|
44
|
+
globalPermissions: Permissions.create({
|
|
45
|
+
level: PermissionLevel.Full,
|
|
46
|
+
}),
|
|
47
|
+
})
|
|
48
|
+
.create();
|
|
49
|
+
|
|
50
|
+
const token = await Token.createToken(user);
|
|
51
|
+
const platform = await getPlatform(token);
|
|
52
|
+
expect(platform.body.privateConfig).not.toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('Should throw if invalid token', async () => {
|
|
56
|
+
const user = await new UserFactory({
|
|
57
|
+
globalPermissions: Permissions.create({
|
|
58
|
+
level: PermissionLevel.Full,
|
|
59
|
+
}),
|
|
60
|
+
})
|
|
61
|
+
.create();
|
|
62
|
+
|
|
63
|
+
const token = await Token.createToken(user);
|
|
64
|
+
token.accessToken = 'invalid-token';
|
|
65
|
+
await token.save();
|
|
66
|
+
await expect(getPlatform(token)).rejects.toThrow('The access token is invalid');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -5,7 +5,7 @@ import { MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, Organ
|
|
|
5
5
|
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
6
6
|
import { PatchUserMembersEndpoint } from './PatchUserMembersEndpoint';
|
|
7
7
|
import { Database } from '@simonbackx/simple-database';
|
|
8
|
-
import { TestUtils } from '@stamhoofd/test-utils';
|
|
8
|
+
import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
9
9
|
|
|
10
10
|
const baseUrl = `/members`;
|
|
11
11
|
const endpoint = new PatchUserMembersEndpoint();
|
|
@@ -16,8 +16,6 @@ const firstName = 'John';
|
|
|
16
16
|
const lastName = 'Doe';
|
|
17
17
|
const birthDay = { year: 1993, month: 4, day: 5 };
|
|
18
18
|
|
|
19
|
-
const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
|
|
20
|
-
|
|
21
19
|
describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
22
20
|
beforeEach(async () => {
|
|
23
21
|
TestUtils.setEnvironment('userMode', 'platform');
|
|
@@ -55,7 +53,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
55
53
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
56
54
|
await expect(testServer.test(endpoint, request))
|
|
57
55
|
.rejects
|
|
58
|
-
.toThrow(errorWithCode('known_member_missing_rights'));
|
|
56
|
+
.toThrow(SHExpect.errorWithCode('known_member_missing_rights'));
|
|
59
57
|
});
|
|
60
58
|
|
|
61
59
|
test('The security code is not a requirement for members without additional data', async () => {
|
|
@@ -280,7 +278,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
280
278
|
|
|
281
279
|
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
282
280
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
283
|
-
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
281
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
|
|
284
282
|
});
|
|
285
283
|
|
|
286
284
|
test('A user can save answers of records of the platform', async () => {
|
|
@@ -391,7 +389,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
391
389
|
|
|
392
390
|
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
393
391
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
394
|
-
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
392
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
|
|
395
393
|
});
|
|
396
394
|
|
|
397
395
|
test('A user can not save anwers to inexisting records', async () => {
|
|
@@ -437,7 +435,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
437
435
|
|
|
438
436
|
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
439
437
|
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
440
|
-
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
438
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(SHExpect.errorWithCode('permission_denied'));
|
|
441
439
|
});
|
|
442
440
|
});
|
|
443
441
|
});
|
|
@@ -26,8 +26,8 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
|
|
|
26
26
|
queryDecoder = Query as Decoder<Query>;
|
|
27
27
|
webshopDomains = [
|
|
28
28
|
...new Set([
|
|
29
|
-
...Object.values(STAMHOOFD.domains.webshop),
|
|
30
|
-
...Object.values(STAMHOOFD.domains.marketing),
|
|
29
|
+
...Object.values(STAMHOOFD.domains.webshop ?? {}),
|
|
30
|
+
...Object.values(STAMHOOFD.domains.marketing ?? {}),
|
|
31
31
|
]),
|
|
32
32
|
];
|
|
33
33
|
|
|
@@ -113,7 +113,7 @@ export class SetOrganizationDomainEndpoint extends Endpoint<Params, Query, Body,
|
|
|
113
113
|
type: DNSRecordType.CNAME,
|
|
114
114
|
name: organization.privateMeta.mailFromDomain + '.',
|
|
115
115
|
// Use shops for mail domain, to allow reuse
|
|
116
|
-
value: STAMHOOFD.domains.webshopCname + '.',
|
|
116
|
+
value: (STAMHOOFD.domains.webshopCname ?? STAMHOOFD.domains.registrationCname) + '.',
|
|
117
117
|
}));
|
|
118
118
|
|
|
119
119
|
if (STAMHOOFD.domains.registration && organization.privateMeta.pendingRegisterDomain) {
|
|
@@ -46,7 +46,7 @@ export class ConnectMollieEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const type = STAMHOOFD.STRIPE_CONNECT_METHOD;
|
|
49
|
+
const type = STAMHOOFD.STRIPE_CONNECT_METHOD ?? 'standard';
|
|
50
50
|
|
|
51
51
|
const sharedData: Stripe.AccountCreateParams = {
|
|
52
52
|
capabilities: {
|
|
@@ -42,7 +42,7 @@ export class DeleteStripeAccountEndpoint extends Endpoint<Params, Query, Body, R
|
|
|
42
42
|
throw Context.auth.notFoundOrNoAccess('Account niet gevonden');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
if (model.accountId === STAMHOOFD.STRIPE_ACCOUNT_ID) {
|
|
45
|
+
if (STAMHOOFD.STRIPE_ACCOUNT_ID && model.accountId === STAMHOOFD.STRIPE_ACCOUNT_ID) {
|
|
46
46
|
throw new SimpleError({
|
|
47
47
|
code: 'invalid_request',
|
|
48
48
|
message: 'Je kan het hoofdaccount van het platform niet verwijderen.',
|