@stamhoofd/backend 2.81.0 → 2.83.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 (108) hide show
  1. package/package.json +10 -10
  2. package/src/audit-logs/GroupLogger.ts +3 -3
  3. package/src/audit-logs/MemberResponsibilityRecordLogger.ts +1 -1
  4. package/src/audit-logs/OrderLogger.ts +1 -1
  5. package/src/audit-logs/RegistrationLogger.ts +1 -1
  6. package/src/endpoints/admin/members/ChargeMembersEndpoint.ts +4 -4
  7. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +1 -1
  8. package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +5 -5
  9. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +1 -1
  10. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +8 -8
  11. package/src/endpoints/auth/CreateAdminEndpoint.ts +2 -2
  12. package/src/endpoints/auth/CreateTokenEndpoint.ts +10 -10
  13. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +2 -2
  14. package/src/endpoints/auth/PatchUserEndpoint.ts +9 -9
  15. package/src/endpoints/auth/SignupEndpoint.ts +2 -2
  16. package/src/endpoints/auth/VerifyEmailEndpoint.ts +3 -3
  17. package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +1 -1
  18. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +1 -1
  19. package/src/endpoints/global/email/GetEmailEndpoint.ts +1 -1
  20. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +1 -1
  21. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +139 -0
  22. package/src/endpoints/global/email/PatchEmailEndpoint.ts +30 -7
  23. package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +1 -1
  24. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +16 -35
  25. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +1 -1
  26. package/src/endpoints/global/events/PatchEventsEndpoint.ts +22 -16
  27. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +1 -1
  28. package/src/endpoints/global/files/UploadFile.ts +14 -2
  29. package/src/endpoints/global/files/UploadImage.ts +2 -2
  30. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +2 -2
  31. package/src/endpoints/global/members/GetMembersEndpoint.ts +1 -1
  32. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +19 -19
  33. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +34 -34
  34. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +1 -1
  35. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +5 -5
  36. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +5 -1
  37. package/src/endpoints/global/platform/GetPlatformEndpoint.test.ts +68 -0
  38. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +1 -1
  39. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +2 -2
  40. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +15 -17
  41. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +4 -4
  42. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +37 -37
  43. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +2 -2
  44. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
  45. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +1 -1
  46. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +5 -5
  47. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +2 -2
  48. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +1 -1
  49. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +3 -3
  50. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +9 -9
  51. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +4 -4
  52. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +1 -1
  53. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +1 -1
  54. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +11 -11
  55. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +13 -13
  56. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +16 -16
  57. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
  58. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +2 -2
  59. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +1 -1
  60. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +1 -1
  61. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +1 -1
  62. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +106 -0
  63. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +16 -3
  64. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +2 -2
  65. package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +247 -0
  66. package/src/endpoints/{auth → organization/dashboard/users}/PatchApiUserEndpoint.ts +25 -6
  67. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +4 -4
  68. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +2 -2
  69. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +8 -8
  70. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +1 -1
  71. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +1 -1
  72. package/src/endpoints/organization/shared/GetDocumentHtml.ts +2 -2
  73. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +1 -1
  74. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +5 -0
  75. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +1 -1
  76. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +2 -2
  77. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +1 -1
  78. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +3 -3
  79. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +6 -1
  80. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +1 -1
  81. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +10 -8
  82. package/src/excel-loaders/event-notifications.ts +11 -11
  83. package/src/excel-loaders/members.ts +34 -34
  84. package/src/excel-loaders/organizations.ts +23 -23
  85. package/src/excel-loaders/payments.ts +39 -39
  86. package/src/excel-loaders/receivable-balances.ts +21 -21
  87. package/src/helpers/AddressValidator.ts +6 -6
  88. package/src/helpers/AdminPermissionChecker.ts +7 -4
  89. package/src/helpers/AuthenticatedStructures.ts +16 -8
  90. package/src/helpers/BuckarooHelper.ts +1 -1
  91. package/src/helpers/CheckSettlements.ts +1 -1
  92. package/src/helpers/Context.ts +31 -15
  93. package/src/helpers/FileCache.ts +7 -7
  94. package/src/helpers/ForwardHandler.ts +1 -1
  95. package/src/helpers/GlobalHelper.ts +6 -4
  96. package/src/helpers/MembershipCharger.ts +2 -2
  97. package/src/helpers/SetupStepUpdater.ts +1 -1
  98. package/src/helpers/StripeHelper.ts +18 -7
  99. package/src/helpers/XlsxTransformerColumnHelper.ts +18 -18
  100. package/src/services/DocumentService.ts +1 -1
  101. package/src/services/EventNotificationService.ts +1 -1
  102. package/src/services/MemberNumberService.ts +3 -3
  103. package/src/services/SSOService.ts +5 -5
  104. package/src/sql-filters/members.ts +1 -1
  105. package/tests/e2e/api-rate-limits.test.ts +188 -0
  106. package/tests/e2e/private-files.test.ts +3 -3
  107. package/tests/helpers/StripeMocker.ts +7 -1
  108. /package/src/endpoints/global/platform/{GetPlatformEnpoint.ts → GetPlatformEndpoint.ts} +0 -0
@@ -2,15 +2,15 @@ import { Decoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Token, User } from '@stamhoofd/models';
5
- import { ApiUser, ApiUserWithToken, UserPermissions } from '@stamhoofd/structures';
5
+ import { ApiUser, ApiUserWithToken, UserMeta, UserPermissions } from '@stamhoofd/structures';
6
6
 
7
7
  import { Context } from '../../../../helpers/Context';
8
8
  type Params = Record<string, never>;
9
9
  type Query = undefined;
10
10
  type Body = ApiUser;
11
- type ResponseBody = ApiUser;
11
+ type ResponseBody = ApiUserWithToken;
12
12
 
13
- export class CreateAdminEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
13
+ export class CreateApiUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
14
  bodyDecoder = ApiUser as Decoder<ApiUser>;
15
15
 
16
16
  protected doesMatch(request: Request): [true, Params] | [false] {
@@ -56,6 +56,19 @@ export class CreateAdminEndpoint extends Endpoint<Params, Query, Body, ResponseB
56
56
  }
57
57
 
58
58
  admin.permissions = UserPermissions.limitedAdd(null, request.body.permissions, organization.id);
59
+
60
+ if (request.body.meta) {
61
+ const rateLimits = request.body.meta.rateLimits;
62
+ if (rateLimits) {
63
+ if (!Context.auth.hasPlatformFullAccess()) {
64
+ throw Context.auth.error($t('370932fc-6614-44be-bb8f-ff921305fadd'));
65
+ }
66
+
67
+ admin.meta = admin.meta ?? UserMeta.create({});
68
+ admin.meta.rateLimits = rateLimits;
69
+ }
70
+ }
71
+
59
72
  await admin.save();
60
73
 
61
74
  // Set id
@@ -41,7 +41,7 @@ export class DeleteUserEndpoint extends Endpoint<Params, Query, Body, ResponseBo
41
41
  if (user.id == request.params.id) {
42
42
  throw new SimpleError({
43
43
  code: 'permission_denied',
44
- message: 'Je kan jezelf niet verwijderen',
44
+ message: $t(`253f8a6e-bef8-4a3a-8a7c-2580d8bad49c`),
45
45
  });
46
46
  }
47
47
 
@@ -49,7 +49,7 @@ export class DeleteUserEndpoint extends Endpoint<Params, Query, Body, ResponseBo
49
49
  if (!editUser || !Context.auth.checkScope(editUser.organizationId)) {
50
50
  throw new SimpleError({
51
51
  code: 'permission_denied',
52
- message: 'Je hebt geen toegang om deze gebruiker te verwijderen',
52
+ message: $t(`dd63dfe1-1a13-476b-b729-10b9df944e88`),
53
53
  });
54
54
  }
55
55
 
@@ -0,0 +1,247 @@
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
+ import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
3
+
4
+ import { testServer } from '../../../../../tests/helpers/TestServer';
5
+ import { PatchApiUserEndpoint } from './PatchApiUserEndpoint';
6
+ import { SHExpect, TestUtils } from '@stamhoofd/test-utils';
7
+ import { ApiUser, ApiUserRateLimits, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions, UserMeta, UserPermissions } from '@stamhoofd/structures';
8
+ import { CreateApiUserEndpoint } from './CreateApiUserEndpoint';
9
+ import { PatchMap } from '@simonbackx/simple-encoding';
10
+
11
+ describe('Endpoint.PatchApiUserEndpoint', () => {
12
+ // Test endpoint
13
+ const createEndpoint = new CreateApiUserEndpoint();
14
+ const endpoint = new PatchApiUserEndpoint();
15
+
16
+ beforeEach(() => {
17
+ TestUtils.setEnvironment('userMode', 'platform');
18
+ });
19
+
20
+ test('Only a platform admin can alter the rate limits of a key', async () => {
21
+ const organization = await new OrganizationFactory({}).create();
22
+ const user = await new UserFactory({
23
+ globalPermissions: Permissions.create({
24
+ level: PermissionLevel.Full,
25
+ }),
26
+ }).create();
27
+ const token = await Token.createToken(user);
28
+
29
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
30
+ permissions: UserPermissions.create({
31
+ organizationPermissions: new Map([
32
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
33
+ ]),
34
+ }),
35
+ }));
36
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
37
+ const createResponse = await testServer.test(createEndpoint, createRequest);
38
+
39
+ const apiUserId = createResponse.body.id;
40
+
41
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
42
+ id: apiUserId,
43
+ meta: UserMeta.patch({
44
+ rateLimits: ApiUserRateLimits.High,
45
+ }),
46
+ }));
47
+ r.headers.authorization = 'Bearer ' + token.accessToken;
48
+
49
+ const response = await testServer.test(endpoint, r);
50
+ expect(response.body).toBeDefined();
51
+ expect(response.body.id).toEqual(apiUserId);
52
+ expect(response.body.meta?.rateLimits).toEqual(ApiUserRateLimits.High);
53
+ });
54
+
55
+ test('An organization admin cannot alter rate limits', async () => {
56
+ const organization = await new OrganizationFactory({}).create();
57
+ const user = await new UserFactory({
58
+ permissions: Permissions.create({
59
+ level: PermissionLevel.Full,
60
+ }),
61
+ organization,
62
+ }).create();
63
+ const token = await Token.createToken(user);
64
+
65
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
66
+ permissions: UserPermissions.create({
67
+ organizationPermissions: new Map([
68
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
69
+ ]),
70
+ }),
71
+ }));
72
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
73
+ const createResponse = await testServer.test(createEndpoint, createRequest);
74
+
75
+ const apiUserId = createResponse.body.id;
76
+
77
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
78
+ id: apiUserId,
79
+ meta: UserMeta.patch({
80
+ rateLimits: ApiUserRateLimits.High,
81
+ }),
82
+ }));
83
+ r.headers.authorization = 'Bearer ' + token.accessToken;
84
+
85
+ await expect(testServer.test(endpoint, r)).rejects.toThrow(SHExpect.simpleError({
86
+ code: 'permission_denied',
87
+ }));
88
+ });
89
+
90
+ test('You cannot grant an API-key permissions outside its organization using a put', async () => {
91
+ const organization = await new OrganizationFactory({}).create();
92
+ const user = await new UserFactory({
93
+ permissions: Permissions.create({
94
+ level: PermissionLevel.Full,
95
+ }),
96
+ organization,
97
+ }).create();
98
+ const token = await Token.createToken(user);
99
+
100
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
101
+ permissions: UserPermissions.create({
102
+ organizationPermissions: new Map([
103
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
104
+ ]),
105
+ }),
106
+ }));
107
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
108
+ const createResponse = await testServer.test(createEndpoint, createRequest);
109
+ const apiUserId = createResponse.body.id;
110
+
111
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
112
+ id: apiUserId,
113
+ permissions: UserPermissions.create({
114
+ organizationPermissions: new Map([
115
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
116
+ ['other', Permissions.create({ level: PermissionLevel.Full })],
117
+ ]),
118
+ globalPermissions: Permissions.create({
119
+ level: PermissionLevel.Full,
120
+ }),
121
+ }),
122
+ }));
123
+ r.headers.authorization = 'Bearer ' + token.accessToken;
124
+
125
+ const response = await testServer.test(endpoint, r);
126
+ expect(response.body).toBeDefined();
127
+ expect(response.body.id).toEqual(apiUserId);
128
+ expect(response.body.permissions).toEqual(
129
+ UserPermissions.create({
130
+ organizationPermissions: new Map([
131
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
132
+ ]),
133
+ globalPermissions: null,
134
+ }),
135
+ );
136
+ });
137
+
138
+ test('You cannot grant an API-key permissions outside its organization using a patch', async () => {
139
+ const organization = await new OrganizationFactory({}).create();
140
+ const user = await new UserFactory({
141
+ permissions: Permissions.create({
142
+ level: PermissionLevel.Full,
143
+ }),
144
+ organization,
145
+ }).create();
146
+ const token = await Token.createToken(user);
147
+
148
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
149
+ permissions: UserPermissions.create({
150
+ organizationPermissions: new Map([
151
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
152
+ ]),
153
+ }),
154
+ }));
155
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
156
+ const createResponse = await testServer.test(createEndpoint, createRequest);
157
+ const apiUserId = createResponse.body.id;
158
+
159
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
160
+ id: apiUserId,
161
+ permissions: UserPermissions.patch({
162
+ organizationPermissions: new PatchMap([
163
+ ['other', Permissions.create({ level: PermissionLevel.Full })],
164
+ ]),
165
+ globalPermissions: Permissions.create({
166
+ level: PermissionLevel.Full,
167
+ }),
168
+ }),
169
+ }));
170
+ r.headers.authorization = 'Bearer ' + token.accessToken;
171
+
172
+ const response = await testServer.test(endpoint, r);
173
+ expect(response.body).toBeDefined();
174
+ expect(response.body.id).toEqual(apiUserId);
175
+ expect(response.body.permissions).toEqual(
176
+ UserPermissions.create({
177
+ organizationPermissions: new Map([
178
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
179
+ ]),
180
+ globalPermissions: null,
181
+ }),
182
+ );
183
+ });
184
+
185
+ test('You can grant an API-key permissions using a patch', async () => {
186
+ const organization = await new OrganizationFactory({}).create();
187
+ const user = await new UserFactory({
188
+ permissions: Permissions.create({
189
+ level: PermissionLevel.Full,
190
+ }),
191
+ organization,
192
+ }).create();
193
+ const token = await Token.createToken(user);
194
+
195
+ const createRequest = Request.buildJson('POST', '/api-keys', organization.getApiHost(), ApiUser.create({
196
+ permissions: UserPermissions.create({
197
+ organizationPermissions: new Map([
198
+ [organization.id, Permissions.create({ level: PermissionLevel.Read })],
199
+ ]),
200
+ }),
201
+ }));
202
+ createRequest.headers.authorization = 'Bearer ' + token.accessToken;
203
+ const createResponse = await testServer.test(createEndpoint, createRequest);
204
+ const apiUserId = createResponse.body.id;
205
+
206
+ const r = Request.buildJson('PATCH', '/api-keys/' + apiUserId, organization.getApiHost(), ApiUser.patch({
207
+ id: apiUserId,
208
+ permissions: UserPermissions.patch({
209
+ organizationPermissions: new PatchMap([
210
+ [organization.id, Permissions.patch({
211
+ resources: new PatchMap([[
212
+ PermissionsResourceType.Groups,
213
+ new Map([
214
+ ['group-id', ResourcePermissions.create({
215
+ level: PermissionLevel.Full,
216
+ })],
217
+ ]),
218
+ ]]),
219
+ })],
220
+ ]),
221
+ }),
222
+ }));
223
+ r.headers.authorization = 'Bearer ' + token.accessToken;
224
+
225
+ const response = await testServer.test(endpoint, r);
226
+ expect(response.body).toBeDefined();
227
+ expect(response.body.id).toEqual(apiUserId);
228
+ expect(response.body.permissions).toEqual(
229
+ UserPermissions.create({
230
+ organizationPermissions: new Map([
231
+ [organization.id, Permissions.create({
232
+ level: PermissionLevel.Read,
233
+ resources: new Map([[
234
+ PermissionsResourceType.Groups,
235
+ new Map([
236
+ ['group-id', ResourcePermissions.create({
237
+ level: PermissionLevel.Full,
238
+ })],
239
+ ]),
240
+ ]]),
241
+ })],
242
+ ]),
243
+ globalPermissions: null,
244
+ }),
245
+ );
246
+ });
247
+ });
@@ -1,17 +1,16 @@
1
- import { AutoEncoderPatchType, Decoder } from '@simonbackx/simple-encoding';
1
+ import { AutoEncoderPatchType, Decoder, isPatch } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Token, User } from '@stamhoofd/models';
5
- import { ApiUser, PermissionLevel, UserPermissions } from '@stamhoofd/structures';
6
-
7
- import { Context } from '../../helpers/Context';
5
+ import { ApiUser, PermissionLevel, UserMeta, UserPermissions } from '@stamhoofd/structures';
6
+ import { Context } from '../../../../helpers/Context';
8
7
 
9
8
  type Params = { id: string };
10
9
  type Query = undefined;
11
10
  type Body = AutoEncoderPatchType<ApiUser>;
12
11
  type ResponseBody = ApiUser;
13
12
 
14
- export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
13
+ export class PatchApiUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
15
14
  bodyDecoder = ApiUser.patchType() as Decoder<AutoEncoderPatchType<ApiUser>>;
16
15
 
17
16
  protected doesMatch(request: Request): [true, Params] | [false] {
@@ -42,7 +41,7 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
42
41
  const editUser = request.body.id === user.id ? user : await User.getByID(request.body.id);
43
42
 
44
43
  if (!editUser || !await Context.auth.canAccessUser(editUser, PermissionLevel.Write) || !editUser.isApiUser) {
45
- throw Context.auth.notFoundOrNoAccess('Je hebt geen toegang om deze API-user te wijzigen');
44
+ throw Context.auth.notFoundOrNoAccess($t(`cd099f48-7a5e-4993-854d-697104201871`));
46
45
  }
47
46
 
48
47
  editUser.firstName = request.body.name ?? editUser.name;
@@ -85,6 +84,26 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
85
84
  }
86
85
  }
87
86
 
87
+ if (request.body.meta) {
88
+ if (!isPatch(request.body.meta)) {
89
+ throw new SimpleError({
90
+ code: 'invalid_request',
91
+ message: 'Invalid request: meta is not a patch',
92
+ statusCode: 400,
93
+ });
94
+ }
95
+
96
+ const rateLimits = request.body.meta.rateLimits;
97
+ if (rateLimits && rateLimits !== editUser.meta?.rateLimits) {
98
+ if (!Context.auth.hasPlatformFullAccess()) {
99
+ throw Context.auth.error($t('370932fc-6614-44be-bb8f-ff921305fadd'));
100
+ }
101
+
102
+ editUser.meta = editUser.meta ?? UserMeta.create({});
103
+ editUser.meta.rateLimits = rateLimits;
104
+ }
105
+ }
106
+
88
107
  await editUser.save();
89
108
 
90
109
  return new Response(await Token.getAPIUserWithToken(editUser));
@@ -38,7 +38,7 @@ export class CreateWebshopEndpoint extends Endpoint<Params, Query, Body, Respons
38
38
 
39
39
  // Fast throw first (more in depth checking for patches later)
40
40
  if (!await Context.auth.canCreateWebshops(organization.id)) {
41
- throw Context.auth.error('Je kan geen webshops maken, vraag aan de hoofdbeheerders om jou toegang te geven.');
41
+ throw Context.auth.error($t(`e97dabba-7c7f-4705-b5be-fa4ea9f701ed`));
42
42
  }
43
43
 
44
44
  const errors = new SimpleErrors();
@@ -141,7 +141,7 @@ export class CreateWebshopEndpoint extends Endpoint<Params, Query, Body, Respons
141
141
  throw new SimpleError({
142
142
  code: 'failed_to_generate_unique_uri',
143
143
  message: 'Failed to generate unique uri',
144
- human: 'Er is een fout opgetreden bij het maken van de webshop, kies een andere naam voor jouw webshop',
144
+ human: $t(`a159e71b-e975-48c0-a33b-fdb52ad22299`),
145
145
  statusCode: 500,
146
146
  });
147
147
  }
@@ -168,7 +168,7 @@ export class CreateWebshopEndpoint extends Endpoint<Params, Query, Body, Respons
168
168
  throw new SimpleError({
169
169
  code: 'missing_permissions',
170
170
  message: 'You cannot create a webshop without having full permissions on the created webshop',
171
- human: 'Als je een webshop aanmaakt moet je ervoor zorgen dat jezelf ook volledige toegang hebt.',
171
+ human: $t(`28f1afe6-b40d-495c-a58e-2b665c20a294`),
172
172
  });
173
173
  }
174
174
 
@@ -211,7 +211,7 @@ export class CreateWebshopEndpoint extends Endpoint<Params, Query, Body, Respons
211
211
  throw new SimpleError({
212
212
  code: 'failed_to_generate_unique_domainUri',
213
213
  message: 'Failed to generate unique domainUri',
214
- human: 'Er is een fout opgetreden bij het maken van de webshop, kies een andere naam voor jouw webshop',
214
+ human: $t(`a159e71b-e975-48c0-a33b-fdb52ad22299`),
215
215
  statusCode: 500,
216
216
  });
217
217
  }
@@ -66,7 +66,7 @@ export class PatchWebshopDiscountCodesEndpoint extends Endpoint<Params, Query, B
66
66
  throw new SimpleError({
67
67
  code: 'used_code',
68
68
  message: 'Discount code already in use',
69
- human: 'Er bestaat al een kortingscode met de code ' + struct.code + ', een code moet uniek zijn.',
69
+ human: $t(`d8cf1267-28dd-4d70-9f5f-1ed800729f6d`) + ' ' + struct.code + $t(`9145b0bf-8539-4ddc-8867-859f8716f026`),
70
70
  });
71
71
  }
72
72
  throw e;
@@ -98,7 +98,7 @@ export class PatchWebshopDiscountCodesEndpoint extends Endpoint<Params, Query, B
98
98
  throw new SimpleError({
99
99
  code: 'used_code',
100
100
  message: 'Discount code already in use',
101
- human: 'Er bestaat al een kortingscode met de code ' + model.code + ', een code moet uniek zijn.',
101
+ human: $t(`d8cf1267-28dd-4d70-9f5f-1ed800729f6d`) + ' ' + model.code + $t(`9145b0bf-8539-4ddc-8867-859f8716f026`),
102
102
  });
103
103
  }
104
104
  throw e;
@@ -91,7 +91,7 @@ export class PatchWebshopEndpoint extends Endpoint<Params, Query, Body, Response
91
91
  throw new SimpleError({
92
92
  code: 'domain_already_used',
93
93
  message: 'This domain is already used by another organization',
94
- human: 'Deze domeinnaam is al in gebruik door een andere vereniging. Neem contact op met Stamhoofd als je denkt dat je toch toegang zou moeten krijgen.',
94
+ human: $t(`91e43562-956f-41ac-b5b6-a8bcb63edd66`),
95
95
  statusCode: 400,
96
96
  });
97
97
  }
@@ -107,7 +107,7 @@ export class PatchWebshopEndpoint extends Endpoint<Params, Query, Body, Response
107
107
  throw new SimpleError({
108
108
  code: 'invalid_field',
109
109
  message: 'Invalid domain',
110
- human: 'Ongeldige domeinnaam',
110
+ human: $t(`0aa565ac-c12a-4b38-9612-0a615e6a67e0`),
111
111
  field: 'customUrl',
112
112
  });
113
113
  }
@@ -128,7 +128,7 @@ export class PatchWebshopEndpoint extends Endpoint<Params, Query, Body, Response
128
128
  throw new SimpleError({
129
129
  code: 'invalid_field',
130
130
  message: 'domainUri contains invalid characters',
131
- human: 'Een link mag geen spaties, hoofdletters of speciale tekens bevatten',
131
+ human: $t(`4e842c04-da6c-4690-a187-665fa4a60bae`),
132
132
  field: 'customUrl',
133
133
  });
134
134
  }
@@ -139,7 +139,7 @@ export class PatchWebshopEndpoint extends Endpoint<Params, Query, Body, Response
139
139
  throw new SimpleError({
140
140
  code: 'invalid_domain',
141
141
  message: 'This domain is already in use',
142
- human: 'Deze link is al in gebruik door een andere webshop: ' + existing.meta.name + '. Verwijder of pas daar de link eerst aan als je die wilt hergebruiken.',
142
+ human: $t(`11064157-89fd-4c12-b56a-1f5f236a010d`) + ' ' + existing.meta.name + $t(`42dccb0d-88b1-4a18-9250-04aa969b1299`),
143
143
  });
144
144
  }
145
145
  }
@@ -159,7 +159,7 @@ export class PatchWebshopEndpoint extends Endpoint<Params, Query, Body, Response
159
159
  throw new SimpleError({
160
160
  code: 'invalid_field',
161
161
  message: 'Uri cannot be empty',
162
- human: 'De link mag niet leeg zijn',
162
+ human: $t(`6af872f3-4547-4fd2-8949-0163e6473c04`),
163
163
  field: 'uri',
164
164
  });
165
165
  }
@@ -168,7 +168,7 @@ export class PatchWebshopEndpoint extends Endpoint<Params, Query, Body, Response
168
168
  throw new SimpleError({
169
169
  code: 'invalid_field',
170
170
  message: 'Uri contains invalid characters',
171
- human: 'Een link mag geen spaties, hoofdletters of speciale tekens bevatten',
171
+ human: $t(`4e842c04-da6c-4690-a187-665fa4a60bae`),
172
172
  field: 'uri',
173
173
  });
174
174
  }
@@ -181,7 +181,7 @@ export class PatchWebshopEndpoint extends Endpoint<Params, Query, Body, Response
181
181
  throw new SimpleError({
182
182
  code: 'missing_permissions',
183
183
  message: 'You cannot restrict your own permissions',
184
- human: 'Je kan je eigen volledige toegang tot deze webshop niet verwijderen (algemeen > toegangsbeheer). Vraag aan een hoofdbeheerder om jouw toegang te verwijderen.',
184
+ human: $t(`0b70b194-296a-457e-804b-abe800bd5b4c`),
185
185
  });
186
186
  }
187
187
 
@@ -194,7 +194,7 @@ export class PatchWebshopEndpoint extends Endpoint<Params, Query, Body, Response
194
194
  throw new SimpleError({
195
195
  code: 'invalid_field',
196
196
  message: 'Uri already in use',
197
- human: 'De link die je hebt gekozen is al in gebruik. Kies een andere.',
197
+ human: $t(`17267b0d-0be9-4d04-b837-7687da20347f`),
198
198
  field: 'uri',
199
199
  });
200
200
  }
@@ -42,7 +42,7 @@ export class PatchWebshopTicketsEndpoint extends Endpoint<Params, Query, Body, R
42
42
 
43
43
  const webshop = await Webshop.getByID(request.params.id);
44
44
  if (!webshop || !await Context.auth.canAccessWebshopTickets(webshop, PermissionLevel.Write)) {
45
- throw Context.auth.notFoundOrNoAccess('Je hebt geen toegang om tickets te wijzigen van deze webshop');
45
+ throw Context.auth.notFoundOrNoAccess($t(`b4969b2f-c128-44ed-a8de-279c1dd55382`));
46
46
  }
47
47
 
48
48
  const tickets: Ticket[] = [];
@@ -53,7 +53,7 @@ export class ExchangePaymentEndpoint extends Endpoint<Params, Query, Body, Respo
53
53
  if (!payment) {
54
54
  throw new SimpleError({
55
55
  code: '',
56
- message: 'Deze link is ongeldig',
56
+ message: $t('35b369bd-5766-41d1-8da3-3d362e316c1a'),
57
57
  });
58
58
  }
59
59
 
@@ -31,7 +31,7 @@ export class GetDocumentHtml extends Endpoint<Params, Query, Body, ResponseBody>
31
31
  if (!document || !(await Context.auth.canAccessDocument(document))) {
32
32
  throw new SimpleError({
33
33
  code: 'not_found',
34
- message: 'Onbekend document',
34
+ message: $t('a9860203-6a6d-4612-b4d4-7851e47d4dad'),
35
35
  });
36
36
  }
37
37
 
@@ -48,7 +48,7 @@ export class GetDocumentHtml extends Endpoint<Params, Query, Body, ResponseBody>
48
48
  if (!html) {
49
49
  throw new SimpleError({
50
50
  code: 'failed_generating',
51
- message: 'Er ging iets mis bij het aanmaken van het document. Probeer later opieuw en neem contact met ons op als het probleem blijft herhalen.',
51
+ message: $t('74ba486a-60b2-4d94-a6fd-159a3adfca8b'),
52
52
  });
53
53
  }
54
54
 
@@ -34,7 +34,7 @@ export class GetPaymentEndpoint extends Endpoint<Params, Query, Body, ResponseBo
34
34
  throw new SimpleError({
35
35
  code: 'not_found',
36
36
  message: 'Payment not found',
37
- human: 'Je hebt geen toegang tot deze betaling',
37
+ human: $t(`6024d1b5-f0a0-42f4-b972-636286edf929`),
38
38
  });
39
39
  }
40
40
 
@@ -2,6 +2,7 @@ import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
3
3
  import { Organization, PermissionLevel, Permissions } from '@stamhoofd/structures';
4
4
 
5
+ import { TestUtils } from '@stamhoofd/test-utils';
5
6
  import { testServer } from '../../../../../tests/helpers/TestServer';
6
7
  import { GetOrganizationEndpoint } from './GetOrganizationEndpoint';
7
8
 
@@ -9,6 +10,10 @@ describe('Endpoint.GetOrganization', () => {
9
10
  // Test endpoint
10
11
  const endpoint = new GetOrganizationEndpoint();
11
12
 
13
+ beforeEach(async () => {
14
+ TestUtils.setEnvironment('userMode', 'platform');
15
+ });
16
+
12
17
  test('Get organization as signed in user', async () => {
13
18
  const organization = await new OrganizationFactory({}).create();
14
19
  const user = await new UserFactory({ organization }).create();
@@ -34,7 +34,7 @@ export class CheckWebshopDiscountCodesEndpoint extends Endpoint<Params, Query, B
34
34
  throw new SimpleError({
35
35
  code: 'not_found',
36
36
  message: 'Webshop not found',
37
- human: 'Deze webshop bestaat niet (meer)',
37
+ human: $t(`45c039cd-e937-42cd-934b-a2bb4ee0abdd`),
38
38
  });
39
39
  }
40
40
 
@@ -33,7 +33,7 @@ export class GetOrderByPaymentEndpoint extends Endpoint<Params, Query, Body, Res
33
33
  throw new SimpleError({
34
34
  code: 'not_found',
35
35
  message: 'Order not found',
36
- human: 'Deze bestelling bestaat niet (meer)',
36
+ human: $t(`1aced6e8-6b32-4da5-b176-906df0a1cc0a`),
37
37
  });
38
38
  }
39
39
  const [order] = await Order.where({ paymentId: payment.id }, { limit: 1 });
@@ -41,7 +41,7 @@ export class GetOrderByPaymentEndpoint extends Endpoint<Params, Query, Body, Res
41
41
  throw new SimpleError({
42
42
  code: 'not_found',
43
43
  message: 'Order not found',
44
- human: 'Deze bestelling bestaat niet (meer)',
44
+ human: $t(`1aced6e8-6b32-4da5-b176-906df0a1cc0a`),
45
45
  });
46
46
  }
47
47
 
@@ -31,7 +31,7 @@ export class GetOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBody
31
31
  throw new SimpleError({
32
32
  code: 'not_found',
33
33
  message: 'Order not found',
34
- human: 'Deze bestelling bestaat niet (meer)',
34
+ human: $t(`1aced6e8-6b32-4da5-b176-906df0a1cc0a`),
35
35
  });
36
36
  }
37
37
 
@@ -55,7 +55,7 @@ export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBo
55
55
  throw new SimpleError({
56
56
  code: 'not_found',
57
57
  message: 'Ticket not found',
58
- human: 'Dit ticket bestaat niet',
58
+ human: $t(`d7f095ca-0f05-4c8a-9a65-bbbf69b28af0`),
59
59
  });
60
60
  }
61
61
 
@@ -67,7 +67,7 @@ export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBo
67
67
  throw new SimpleError({
68
68
  code: 'not_found',
69
69
  message: 'Ticket not found',
70
- human: 'Dit ticket bestaat niet',
70
+ human: $t(`d7f095ca-0f05-4c8a-9a65-bbbf69b28af0`),
71
71
  });
72
72
  }
73
73
 
@@ -79,7 +79,7 @@ export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBo
79
79
  throw new SimpleError({
80
80
  code: 'not_found',
81
81
  message: 'Ticket not found',
82
- human: 'Dit ticket bestaat niet',
82
+ human: $t(`d7f095ca-0f05-4c8a-9a65-bbbf69b28af0`),
83
83
  });
84
84
  }
85
85
 
@@ -1,7 +1,8 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { OrganizationFactory, Token, UserFactory, WebshopFactory } from '@stamhoofd/models';
3
- import { PermissionLevel, Permissions, PrivateWebshop, Webshop as WebshopStruct } from '@stamhoofd/structures';
3
+ import { PermissionLevel, Permissions } from '@stamhoofd/structures';
4
4
 
5
+ import { TestUtils } from '@stamhoofd/test-utils';
5
6
  import { testServer } from '../../../../tests/helpers/TestServer';
6
7
  import { GetWebshopEndpoint } from './GetWebshopEndpoint';
7
8
 
@@ -9,6 +10,10 @@ describe('Endpoint.GetWebshop', () => {
9
10
  // Test endpoint
10
11
  const endpoint = new GetWebshopEndpoint();
11
12
 
13
+ beforeEach(async () => {
14
+ TestUtils.setEnvironment('userMode', 'platform');
15
+ });
16
+
12
17
  test('Get webshop as signed in user', async () => {
13
18
  const organization = await new OrganizationFactory({}).create();
14
19
  const user = await new UserFactory({ organization }).create();
@@ -40,7 +40,7 @@ export class GetWebshopEndpoint extends Endpoint<Params, Query, Body, ResponseBo
40
40
  throw new SimpleError({
41
41
  code: 'not_found',
42
42
  message: 'Webshop not found',
43
- human: 'Deze webshop bestaat niet (meer)',
43
+ human: $t(`45c039cd-e937-42cd-934b-a2bb4ee0abdd`),
44
44
  });
45
45
  }
46
46