@stamhoofd/backend 2.106.1 → 2.107.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 +6 -1
- package/package.json +17 -11
- package/src/boot.ts +28 -22
- package/src/endpoints/frontend/FrontendEnvironmentEndpoint.ts +89 -0
- package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +30 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +3 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +8 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +109 -109
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +7 -0
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +9 -7
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +7 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +9 -2
- package/src/excel-loaders/payments.ts +5 -5
- package/src/excel-loaders/receivable-balances.ts +7 -7
- package/src/helpers/AdminPermissionChecker.ts +20 -0
- package/src/helpers/BuckarooHelper.ts +1 -1
- package/src/helpers/ServiceFeeHelper.ts +8 -4
- package/src/helpers/StripeHelper.ts +20 -35
- package/src/seeds/1752848561-groups-registration-periods.ts +35 -0
- package/src/services/BalanceItemService.ts +15 -2
- package/src/services/PaymentReallocationService.test.ts +298 -128
- package/src/services/PaymentReallocationService.ts +46 -16
- package/src/services/PaymentService.ts +49 -2
- package/src/services/uitpas/getSocialTariffForEvent.ts +2 -2
- package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +2 -2
- package/src/services/uitpas/registerTicketSales.ts +2 -2
- package/tests/e2e/bundle-discounts.test.ts +415 -391
- package/tests/e2e/documents.test.ts +21 -21
- package/tests/e2e/register.test.ts +93 -93
- package/tests/e2e/stock.test.ts +4 -4
package/index.ts
CHANGED
|
@@ -8,7 +8,12 @@ backendEnv.load({ service: 'api' }).catch((error) => {
|
|
|
8
8
|
const { run } = await import('./src/migrate');
|
|
9
9
|
await run();
|
|
10
10
|
}
|
|
11
|
-
await import('./src/boot');
|
|
11
|
+
const { boot } = await import('./src/boot');
|
|
12
|
+
|
|
13
|
+
boot({ killProcess: true }).catch((error) => {
|
|
14
|
+
console.error('unhandledRejection', error);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
12
17
|
}).catch((error) => {
|
|
13
18
|
console.error('Failed to start the API:', error);
|
|
14
19
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.107.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"require": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"./src/migrate": {
|
|
10
|
+
"require": "./dist/src/migrate.js"
|
|
11
|
+
},
|
|
12
|
+
"./src/boot": {
|
|
13
|
+
"require": "./dist/src/boot.js"
|
|
8
14
|
}
|
|
9
15
|
},
|
|
10
16
|
"license": "UNLICENCED",
|
|
@@ -41,18 +47,18 @@
|
|
|
41
47
|
"@aws-sdk/s3-request-presigner": "3.823.0",
|
|
42
48
|
"@bwip-js/node": "^4.5.1",
|
|
43
49
|
"@mollie/api-client": "3.7.0",
|
|
44
|
-
"@simonbackx/simple-database": "1.
|
|
50
|
+
"@simonbackx/simple-database": "1.34.0",
|
|
45
51
|
"@simonbackx/simple-encoding": "2.22.0",
|
|
46
52
|
"@simonbackx/simple-endpoints": "1.20.1",
|
|
47
53
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
48
|
-
"@stamhoofd/backend-i18n": "2.
|
|
49
|
-
"@stamhoofd/backend-middleware": "2.
|
|
50
|
-
"@stamhoofd/email": "2.
|
|
51
|
-
"@stamhoofd/models": "2.
|
|
52
|
-
"@stamhoofd/queues": "2.
|
|
53
|
-
"@stamhoofd/sql": "2.
|
|
54
|
-
"@stamhoofd/structures": "2.
|
|
55
|
-
"@stamhoofd/utility": "2.
|
|
54
|
+
"@stamhoofd/backend-i18n": "2.107.1",
|
|
55
|
+
"@stamhoofd/backend-middleware": "2.107.1",
|
|
56
|
+
"@stamhoofd/email": "2.107.1",
|
|
57
|
+
"@stamhoofd/models": "2.107.1",
|
|
58
|
+
"@stamhoofd/queues": "2.107.1",
|
|
59
|
+
"@stamhoofd/sql": "2.107.1",
|
|
60
|
+
"@stamhoofd/structures": "2.107.1",
|
|
61
|
+
"@stamhoofd/utility": "2.107.1",
|
|
56
62
|
"archiver": "^7.0.1",
|
|
57
63
|
"axios": "^1.8.2",
|
|
58
64
|
"cookie": "^0.7.0",
|
|
@@ -70,5 +76,5 @@
|
|
|
70
76
|
"publishConfig": {
|
|
71
77
|
"access": "public"
|
|
72
78
|
},
|
|
73
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "daa87b6d1883f2b7eb0b56b9885dc86c74c73789"
|
|
74
80
|
}
|
package/src/boot.ts
CHANGED
|
@@ -6,8 +6,10 @@ import { loadLogger } from '@stamhoofd/logging';
|
|
|
6
6
|
import { Version } from '@stamhoofd/structures';
|
|
7
7
|
import { sleep } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
9
10
|
import { startCrons, stopCrons, waitForCrons } from '@stamhoofd/crons';
|
|
10
11
|
import { Platform } from '@stamhoofd/models';
|
|
12
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
11
13
|
import { resumeEmails } from './helpers/EmailResumer';
|
|
12
14
|
import { GlobalHelper } from './helpers/GlobalHelper';
|
|
13
15
|
import { SetupStepUpdater } from './helpers/SetupStepUpdater';
|
|
@@ -17,10 +19,8 @@ import { BalanceItemService } from './services/BalanceItemService';
|
|
|
17
19
|
import { DocumentService } from './services/DocumentService';
|
|
18
20
|
import { FileSignService } from './services/FileSignService';
|
|
19
21
|
import { PlatformMembershipService } from './services/PlatformMembershipService';
|
|
20
|
-
import { UniqueUserService } from './services/UniqueUserService';
|
|
21
|
-
import { QueueHandler } from '@stamhoofd/queues';
|
|
22
|
-
import { SimpleError } from '@simonbackx/simple-errors';
|
|
23
22
|
import { UitpasService } from './services/uitpas/UitpasService';
|
|
23
|
+
import { UniqueUserService } from './services/UniqueUserService';
|
|
24
24
|
|
|
25
25
|
process.on('unhandledRejection', (error: Error) => {
|
|
26
26
|
console.error('unhandledRejection');
|
|
@@ -59,8 +59,11 @@ function productionLog(message: string) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const
|
|
62
|
+
export const boot = async (options: { killProcess: boolean }) => {
|
|
63
63
|
productionLog('Running server at v' + Version);
|
|
64
|
+
productionLog('Running server at port ' + STAMHOOFD.PORT);
|
|
65
|
+
productionLog('Running server on DB ' + process.env.DB_DATABASE); // note, should always use process env here
|
|
66
|
+
|
|
64
67
|
loadLogger();
|
|
65
68
|
|
|
66
69
|
await GlobalHelper.load();
|
|
@@ -77,6 +80,8 @@ const start = async () => {
|
|
|
77
80
|
// Note: we should load endpoints one by once to have a reliable order of url matching
|
|
78
81
|
await router.loadAllEndpoints(__dirname + '/endpoints/global/*');
|
|
79
82
|
await router.loadAllEndpoints(__dirname + '/endpoints/admin/*');
|
|
83
|
+
await router.loadAllEndpoints(__dirname + '/endpoints/frontend');
|
|
84
|
+
|
|
80
85
|
await router.loadAllEndpoints(__dirname + '/endpoints/auth');
|
|
81
86
|
await router.loadAllEndpoints(__dirname + '/endpoints/organization/dashboard/*');
|
|
82
87
|
await router.loadAllEndpoints(__dirname + '/endpoints/organization/registration');
|
|
@@ -204,24 +209,28 @@ const start = async () => {
|
|
|
204
209
|
}
|
|
205
210
|
|
|
206
211
|
// Should not be needed, but added for security as sometimes a promise hangs somewhere
|
|
207
|
-
|
|
212
|
+
if (options.killProcess) {
|
|
213
|
+
process.exit(0);
|
|
214
|
+
}
|
|
208
215
|
};
|
|
209
216
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
217
|
+
if (options.killProcess) {
|
|
218
|
+
process.on('SIGTERM', () => {
|
|
219
|
+
productionLog('SIGTERM signal received.');
|
|
220
|
+
shutdown().catch((e) => {
|
|
221
|
+
console.error(e);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
|
215
224
|
});
|
|
216
|
-
});
|
|
217
225
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
process.on('SIGINT', () => {
|
|
227
|
+
productionLog('SIGINT signal received.');
|
|
228
|
+
shutdown().catch((e) => {
|
|
229
|
+
console.error(e);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
});
|
|
223
232
|
});
|
|
224
|
-
}
|
|
233
|
+
}
|
|
225
234
|
|
|
226
235
|
// Register crons
|
|
227
236
|
await import('./crons');
|
|
@@ -234,9 +243,6 @@ const start = async () => {
|
|
|
234
243
|
|
|
235
244
|
startCrons();
|
|
236
245
|
seeds().catch(console.error);
|
|
237
|
-
};
|
|
238
246
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
process.exit(1);
|
|
242
|
-
});
|
|
247
|
+
return { shutdown };
|
|
248
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DecodedRequest,
|
|
3
|
+
Endpoint,
|
|
4
|
+
Request,
|
|
5
|
+
Response,
|
|
6
|
+
} from '@simonbackx/simple-endpoints';
|
|
7
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
8
|
+
import { Version } from '@stamhoofd/structures';
|
|
9
|
+
|
|
10
|
+
type Params = Record<string, never>;
|
|
11
|
+
type Query = undefined;
|
|
12
|
+
type Body = undefined;
|
|
13
|
+
type ResponseBody = string;
|
|
14
|
+
|
|
15
|
+
export class FrontendEnvironmentEndpoint extends Endpoint<
|
|
16
|
+
Params,
|
|
17
|
+
Query,
|
|
18
|
+
Body,
|
|
19
|
+
ResponseBody
|
|
20
|
+
> {
|
|
21
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
22
|
+
if (request.method !== 'GET') {
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (STAMHOOFD.environment !== 'test') {
|
|
27
|
+
// Disallow exposing environments outside of tests (even in development!)
|
|
28
|
+
return [false];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const params = Endpoint.parseParameters(
|
|
32
|
+
request.url,
|
|
33
|
+
'/frontend-environment',
|
|
34
|
+
{},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (params) {
|
|
38
|
+
return [true, params as Params];
|
|
39
|
+
}
|
|
40
|
+
return [false];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async handle(_request: DecodedRequest<Params, Query, Body>) {
|
|
44
|
+
const env: SharedEnvironment = {
|
|
45
|
+
environment: STAMHOOFD.environment,
|
|
46
|
+
domains: STAMHOOFD.domains,
|
|
47
|
+
locales: STAMHOOFD.locales,
|
|
48
|
+
userMode: STAMHOOFD.userMode,
|
|
49
|
+
translationNamespace: STAMHOOFD.translationNamespace,
|
|
50
|
+
platformName: STAMHOOFD.platformName,
|
|
51
|
+
fixedCountry: STAMHOOFD.fixedCountry,
|
|
52
|
+
singleOrganization: STAMHOOFD.singleOrganization,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const frontendEnv: FrontendSpecificEnvironment = {
|
|
56
|
+
VERSION: '0.0.0',
|
|
57
|
+
MOLLIE_CLIENT_ID: '',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Load keys that could be defined in STAMHOOFD, but where typescript will complain about because the server
|
|
61
|
+
// normally does not have access to this. Playwright is the exception where it is possible.
|
|
62
|
+
const keys: (keyof FrontendSpecificEnvironment)[] = [
|
|
63
|
+
'PORT',
|
|
64
|
+
'VERSION',
|
|
65
|
+
'NOLT_URL',
|
|
66
|
+
'FEEDBACK_URL',
|
|
67
|
+
'MOLLIE_CLIENT_ID',
|
|
68
|
+
'APP_UPDATE_SERVER_URL',
|
|
69
|
+
'APP_UPDATE_PRODUCTION_SERVER_URL',
|
|
70
|
+
'APP_UPDATE_STAGING_SERVER_URL',
|
|
71
|
+
'APP_UPDATE_DEVELOPMENT_SERVER_URL',
|
|
72
|
+
'CHANGELOG_URL',
|
|
73
|
+
'ILLUSTRATIONS_NAMESPACE',
|
|
74
|
+
'ILLUSTRATIONS_COLORS',
|
|
75
|
+
'PLAUSIBLE_DOMAIN',
|
|
76
|
+
'REDIRECT_LOGIN_DOMAIN',
|
|
77
|
+
'memberDocumentationPage',
|
|
78
|
+
];
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
(frontendEnv as any)[key] = (STAMHOOFD as any)[key];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const code = `window.STAMHOOFD = ${JSON.stringify({ ...env, ...frontendEnv })};`;
|
|
84
|
+
const response = new Response(code);
|
|
85
|
+
response.status = 200;
|
|
86
|
+
response.headers['Content-Type'] = 'application/javascript';
|
|
87
|
+
return response;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { IDRegisterCheckout, RegisterResponse } from '@stamhoofd/structures';
|
|
4
|
+
|
|
5
|
+
type Params = Record<string, never>;
|
|
6
|
+
type Query = undefined;
|
|
7
|
+
type Body = IDRegisterCheckout;
|
|
8
|
+
type ResponseBody = RegisterResponse;
|
|
9
|
+
|
|
10
|
+
export class ActivatePackagesEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
11
|
+
bodyDecoder = Body as Decoder<Body>;
|
|
12
|
+
|
|
13
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
14
|
+
if (request.method != 'POST') {
|
|
15
|
+
return [false];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const params = Endpoint.parseParameters(request.url, '/billing/activate-packages', {});
|
|
19
|
+
|
|
20
|
+
if (params) {
|
|
21
|
+
return [true, params as Params];
|
|
22
|
+
}
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
*/
|
|
@@ -89,11 +89,12 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
89
89
|
if (organization && STAMHOOFD.userMode !== 'platform') {
|
|
90
90
|
member.organizationId = organization.id;
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
const securityCode = struct.details.securityCode; // will get cleared after the filter
|
|
93
|
+
Context.auth.filterMemberPut(member, struct, {asUserManager: false});
|
|
93
94
|
struct.details.cleanData();
|
|
94
95
|
member.details = struct.details;
|
|
95
96
|
|
|
96
|
-
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member,
|
|
97
|
+
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member, securityCode, 'put');
|
|
97
98
|
if (duplicate) {
|
|
98
99
|
// Merge data
|
|
99
100
|
member = duplicate;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, isEmptyPatch, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
-
import { Document, Member, RateLimiter } from '@stamhoofd/models';
|
|
4
|
+
import { Document, Group, Member, RateLimiter, Registration } from '@stamhoofd/models';
|
|
5
5
|
import { MemberDetails, MembersBlob, MemberWithRegistrationsBlob } from '@stamhoofd/structures';
|
|
6
6
|
|
|
7
7
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
@@ -9,6 +9,7 @@ import { Context } from '../../../helpers/Context';
|
|
|
9
9
|
import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
|
|
10
10
|
import { PatchOrganizationMembersEndpoint } from '../../global/members/PatchOrganizationMembersEndpoint';
|
|
11
11
|
import { shouldCheckIfMemberIsDuplicateForPatch } from '../members/shouldCheckIfMemberIsDuplicate';
|
|
12
|
+
import { OneToManyRelation } from '@simonbackx/simple-database';
|
|
12
13
|
type Params = Record<string, never>;
|
|
13
14
|
type Query = undefined;
|
|
14
15
|
type Body = PatchableArrayAutoEncoder<MemberWithRegistrationsBlob>;
|
|
@@ -52,16 +53,20 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
52
53
|
for (const put of request.body.getPuts()) {
|
|
53
54
|
const struct = put.put;
|
|
54
55
|
|
|
55
|
-
const member = new Member()
|
|
56
|
+
const member = new Member()
|
|
57
|
+
.setManyRelation(Member.registrations as any as OneToManyRelation<'registrations', Member, Registration & { group: Group }>, [])
|
|
58
|
+
.setManyRelation(Member.users, []);
|
|
56
59
|
member.id = struct.id;
|
|
57
60
|
member.organizationId = organization?.id ?? null;
|
|
58
61
|
|
|
62
|
+
const securityCode = struct.details.securityCode; // will get cleared after the filter
|
|
63
|
+
Context.auth.filterMemberPut(member, struct, {asUserManager: true});
|
|
59
64
|
struct.details.cleanData();
|
|
60
65
|
member.details = struct.details;
|
|
61
66
|
|
|
62
67
|
this.throwIfInvalidDetails(member.details);
|
|
63
68
|
|
|
64
|
-
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member,
|
|
69
|
+
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member, securityCode, 'put');
|
|
65
70
|
if (duplicate) {
|
|
66
71
|
addedMembers.push(duplicate);
|
|
67
72
|
continue;
|