@stamhoofd/backend 2.115.1 → 2.116.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/package.json +10 -10
- package/src/audit-logs/ModelLogger.ts +2 -3
- package/src/boot.ts +3 -4
- package/src/crons/delete-archived-data.ts +47 -0
- package/src/crons/index.ts +1 -0
- package/src/debug.ts +230 -0
- package/src/email-recipient-loaders/documents.ts +1 -1
- package/src/email-recipient-loaders/payments.ts +1 -1
- package/src/endpoints/auth/PatchUserEndpoint.ts +4 -4
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +4 -4
- package/src/endpoints/global/members/GetMembersEndpoint.ts +4 -16
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +8 -7
- package/src/endpoints/global/registration/GetUserDetailedPayableBalanceEndpoint.ts +2 -2
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +2 -2
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -7
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +7 -4
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +12 -12
- package/src/helpers/AdminPermissionChecker.ts +20 -17
- package/src/helpers/AuthenticatedStructures.ts +177 -107
- package/src/helpers/Context.ts +2 -0
- package/src/helpers/PeriodHelper.ts +5 -45
- package/src/helpers/SetupStepUpdater.ts +5 -4
- package/src/helpers/outstandingBalanceJoin.ts +3 -1
- package/src/services/PaymentService.ts +12 -0
- package/src/services/PlatformMembershipService.ts +3 -3
- package/src/sql-filters/members.ts +1 -1
- package/src/sql-sorters/members.ts +2 -2
- package/tests/e2e/register.test.ts +10 -10
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +0 -67
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.116.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -55,14 +55,14 @@
|
|
|
55
55
|
"@simonbackx/simple-encoding": "2.23.1",
|
|
56
56
|
"@simonbackx/simple-endpoints": "1.20.1",
|
|
57
57
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
58
|
-
"@stamhoofd/backend-i18n": "2.
|
|
59
|
-
"@stamhoofd/backend-middleware": "2.
|
|
60
|
-
"@stamhoofd/email": "2.
|
|
61
|
-
"@stamhoofd/models": "2.
|
|
62
|
-
"@stamhoofd/queues": "2.
|
|
63
|
-
"@stamhoofd/sql": "2.
|
|
64
|
-
"@stamhoofd/structures": "2.
|
|
65
|
-
"@stamhoofd/utility": "2.
|
|
58
|
+
"@stamhoofd/backend-i18n": "2.116.0",
|
|
59
|
+
"@stamhoofd/backend-middleware": "2.116.0",
|
|
60
|
+
"@stamhoofd/email": "2.116.0",
|
|
61
|
+
"@stamhoofd/models": "2.116.0",
|
|
62
|
+
"@stamhoofd/queues": "2.116.0",
|
|
63
|
+
"@stamhoofd/sql": "2.116.0",
|
|
64
|
+
"@stamhoofd/structures": "2.116.0",
|
|
65
|
+
"@stamhoofd/utility": "2.116.0",
|
|
66
66
|
"archiver": "^7.0.1",
|
|
67
67
|
"axios": "^1.13.2",
|
|
68
68
|
"cookie": "^0.7.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "842040d2dcbde452fb25178f3586c8d5293b974f"
|
|
84
84
|
}
|
|
@@ -2,8 +2,8 @@ import { Model, ModelEvent } from '@simonbackx/simple-database';
|
|
|
2
2
|
import { AuditLog } from '@stamhoofd/models';
|
|
3
3
|
import { ObjectDiffer } from '@stamhoofd/object-differ';
|
|
4
4
|
import { AuditLogPatchItem, AuditLogPatchItemType, AuditLogReplacement, AuditLogSource, AuditLogType } from '@stamhoofd/structures';
|
|
5
|
-
import { ContextInstance } from '../helpers/Context';
|
|
6
|
-
import { AuditLogService } from '../services/AuditLogService';
|
|
5
|
+
import { ContextInstance } from '../helpers/Context.js';
|
|
6
|
+
import { AuditLogService } from '../services/AuditLogService.js';
|
|
7
7
|
|
|
8
8
|
export type ModelEventLogOptions<D> = {
|
|
9
9
|
type: AuditLogType;
|
|
@@ -197,7 +197,6 @@ export class ModelLogger<ModelType extends typeof Model, M extends InstanceType<
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
else {
|
|
200
|
-
console.log('No changes');
|
|
201
200
|
return false;
|
|
202
201
|
}
|
|
203
202
|
}
|
package/src/boot.ts
CHANGED
|
@@ -16,13 +16,12 @@ import { SetupStepUpdater } from './helpers/SetupStepUpdater.js';
|
|
|
16
16
|
import { ContextMiddleware } from './middleware/ContextMiddleware.js';
|
|
17
17
|
import { AuditLogService } from './services/AuditLogService.js';
|
|
18
18
|
import { BalanceItemService } from './services/BalanceItemService.js';
|
|
19
|
+
import { CpuService } from './services/CpuService.js';
|
|
19
20
|
import { DocumentService } from './services/DocumentService.js';
|
|
20
21
|
import { FileSignService } from './services/FileSignService.js';
|
|
21
22
|
import { PlatformMembershipService } from './services/PlatformMembershipService.js';
|
|
22
23
|
import { UitpasService } from './services/uitpas/UitpasService.js';
|
|
23
24
|
import { UniqueUserService } from './services/UniqueUserService.js';
|
|
24
|
-
import { CpuService } from './services/CpuService.js';
|
|
25
|
-
import { SQLLogger } from '@stamhoofd/sql';
|
|
26
25
|
|
|
27
26
|
process.on('unhandledRejection', (error: Error) => {
|
|
28
27
|
console.error('unhandledRejection');
|
|
@@ -149,8 +148,8 @@ export const boot = async (options: { killProcess: boolean }) => {
|
|
|
149
148
|
CpuService.startMonitoring();
|
|
150
149
|
}
|
|
151
150
|
else if (STAMHOOFD.environment === 'development') {
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
const { loadDebugFunctions } = await import('./debug.js');
|
|
152
|
+
loadDebugFunctions({ routerServer });
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
if (routerServer.server) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* We cannot keep deletedAt data forever. We only keep it that way to debug things or be able to easily restore data.
|
|
3
|
+
* After 1 month of deletion, we should delete it permanently.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { registerCron } from '@stamhoofd/crons';
|
|
7
|
+
import { Group } from '@stamhoofd/models';
|
|
8
|
+
|
|
9
|
+
let lastRunDate: number | null = null;
|
|
10
|
+
const keepDeletedDataForDays = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds
|
|
11
|
+
|
|
12
|
+
registerCron('delete-archived-data', deleteArchivedData);
|
|
13
|
+
|
|
14
|
+
function shouldRun() {
|
|
15
|
+
const now = new Date();
|
|
16
|
+
|
|
17
|
+
if (now.getDate() === lastRunDate) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const hour = now.getHours();
|
|
22
|
+
|
|
23
|
+
// between 4 and 5 AM - except in development
|
|
24
|
+
if (hour !== 4 && STAMHOOFD.environment !== 'development') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function deleteArchivedData() {
|
|
32
|
+
if (!shouldRun()) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const now = new Date();
|
|
37
|
+
lastRunDate = now.getDate();
|
|
38
|
+
|
|
39
|
+
console.log('Deleting archived data...');
|
|
40
|
+
|
|
41
|
+
const result = await Group.delete()
|
|
42
|
+
.where('deletedAt', '<', new Date(now.getTime() - keepDeletedDataForDays))
|
|
43
|
+
.delete();
|
|
44
|
+
console.log('Deleted groups:', result.affectedRows);
|
|
45
|
+
|
|
46
|
+
// For now, we don't delete orders yet (can 'fuck' up pagination because deleted orders are still used in the frontend to remove cached orders)
|
|
47
|
+
}
|
package/src/crons/index.ts
CHANGED
package/src/debug.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Column } from '@simonbackx/simple-database';
|
|
2
|
+
import { EncodedResponse, ResponseMiddleware, Router, RouterServer } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { logger } from '@simonbackx/simple-logging';
|
|
4
|
+
import { requestPrefix } from '@stamhoofd/backend-middleware';
|
|
5
|
+
|
|
6
|
+
import { AutoEncoder } from '@simonbackx/simple-encoding';
|
|
7
|
+
import { Request, Response } from '@simonbackx/simple-endpoints';
|
|
8
|
+
import { SQLLogger } from '@stamhoofd/sql';
|
|
9
|
+
import { ContextInstance } from './helpers/Context.js';
|
|
10
|
+
|
|
11
|
+
export function loadDebugFunctions({ routerServer }: { routerServer: RouterServer }) {
|
|
12
|
+
SQLLogger.slowQueryThresholdMs = 75;
|
|
13
|
+
SQLLogger.explainAllAndLogInefficient = true;
|
|
14
|
+
|
|
15
|
+
if (process.env.LOG_REQUEST_QUERIES) {
|
|
16
|
+
loadRequestQueries({ routerServer });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (process.env.LOG_TIMERS) {
|
|
20
|
+
loadTimers({ routerServer });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function loadRequestQueries({ routerServer }: { routerServer: RouterServer }) {
|
|
25
|
+
SQLLogger.customLoggers.push((query, params, elapsedTimeMs) => {
|
|
26
|
+
const context = ContextInstance.optional;
|
|
27
|
+
if (context) {
|
|
28
|
+
context.queries.push({ query, time: elapsedTimeMs });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
const LogQueriesMiddleware: ResponseMiddleware = {
|
|
32
|
+
handleResponse(request: Request, response: Response, error?: Error) {
|
|
33
|
+
const prefix = !error ? [] : requestPrefix(request, 'error');
|
|
34
|
+
const context = (request as any)._context as ContextInstance | undefined;
|
|
35
|
+
|
|
36
|
+
if (context && request.method !== 'OPTIONS') {
|
|
37
|
+
logger.log(
|
|
38
|
+
...prefix,
|
|
39
|
+
' - Queries ' + context.queries.map(q => `\n- ${q.query} (${q.time?.toFixed(2) ?? '-'}ms)`).join(''),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
routerServer.addResponseMiddleware(LogQueriesMiddleware);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function loadTimers({ routerServer }: { routerServer: RouterServer }) {
|
|
48
|
+
const jsonStringify = JSON.stringify;
|
|
49
|
+
JSON.stringify = function (...args) {
|
|
50
|
+
const startTime = process.hrtime.bigint();
|
|
51
|
+
const result = jsonStringify.apply(this, args);
|
|
52
|
+
const elapsedTime = process.hrtime.bigint() - startTime;
|
|
53
|
+
const elapsedTimeMs = Number(elapsedTime) / 1000 / 1000;
|
|
54
|
+
|
|
55
|
+
if (elapsedTimeMs > 1) {
|
|
56
|
+
console.log('JSON.stringify took ' + elapsedTimeMs.toFixed(2) + 'ms');
|
|
57
|
+
}
|
|
58
|
+
// Count total request
|
|
59
|
+
const context = ContextInstance.optional;
|
|
60
|
+
if (context) {
|
|
61
|
+
context.timers.set('JSON.stringify', (context.timers.get('JSON.stringify') ?? 0) + elapsedTimeMs);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const jsonParse = JSON.parse;
|
|
67
|
+
JSON.parse = function (...args) {
|
|
68
|
+
const startTime = process.hrtime.bigint();
|
|
69
|
+
const result = jsonParse.apply(this, args);
|
|
70
|
+
const elapsedTime = process.hrtime.bigint() - startTime;
|
|
71
|
+
const elapsedTimeMs = Number(elapsedTime) / 1000 / 1000;
|
|
72
|
+
|
|
73
|
+
if (elapsedTimeMs > 1) {
|
|
74
|
+
console.log('JSON.parse took ' + elapsedTimeMs.toFixed(2) + 'ms');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Count total request
|
|
78
|
+
const context = ContextInstance.optional;
|
|
79
|
+
if (context) {
|
|
80
|
+
context.timers.set('JSON.parse', (context.timers.get('JSON.parse') ?? 0) + elapsedTimeMs);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const routerDecode = Router.prototype.decode;
|
|
86
|
+
Router.prototype.decode = function (...args) {
|
|
87
|
+
const startTime = process.hrtime.bigint();
|
|
88
|
+
const result = routerDecode.apply(this, args);
|
|
89
|
+
const elapsedTime = process.hrtime.bigint() - startTime;
|
|
90
|
+
const elapsedTimeMs = Number(elapsedTime) / 1000 / 1000;
|
|
91
|
+
|
|
92
|
+
if (elapsedTimeMs > 1) {
|
|
93
|
+
console.log('Router.decode took ' + elapsedTimeMs.toFixed(2) + 'ms');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Count total request
|
|
97
|
+
const context = ContextInstance.optional;
|
|
98
|
+
if (context) {
|
|
99
|
+
context.timers.set('Router.decode', (context.timers.get('Router.decode') ?? 0) + elapsedTimeMs);
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const encodedResponseEncode = EncodedResponse.encode;
|
|
105
|
+
EncodedResponse.encode = function (...args) {
|
|
106
|
+
const startTime = process.hrtime.bigint();
|
|
107
|
+
const result = encodedResponseEncode.apply(this, args);
|
|
108
|
+
const elapsedTime = process.hrtime.bigint() - startTime;
|
|
109
|
+
const elapsedTimeMs = Number(elapsedTime) / 1000 / 1000;
|
|
110
|
+
|
|
111
|
+
if (elapsedTimeMs > 1) {
|
|
112
|
+
console.log('EncodedResponse.encode took ' + elapsedTimeMs.toFixed(2) + 'ms');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Count total request
|
|
116
|
+
const context = ContextInstance.optional;
|
|
117
|
+
if (context) {
|
|
118
|
+
context.timers.set('EncodedResponse.encode', (context.timers.get('EncodedResponse.encode') ?? 0) + elapsedTimeMs);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Change
|
|
125
|
+
const columnTo = Column.prototype.to;
|
|
126
|
+
Column.prototype.to = function (this: Column, value: any) {
|
|
127
|
+
const startTime = process.hrtime.bigint();
|
|
128
|
+
const result = columnTo.apply(this, [value]);
|
|
129
|
+
const elapsedTime = process.hrtime.bigint() - startTime;
|
|
130
|
+
const elapsedTimeMs = Number(elapsedTime) / 1000 / 1000;
|
|
131
|
+
|
|
132
|
+
if (elapsedTimeMs > 1) {
|
|
133
|
+
console.log(`Column.to for ${this.constructor.name} took ${elapsedTimeMs.toFixed(2)}ms`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Count total request
|
|
137
|
+
const context = ContextInstance.optional;
|
|
138
|
+
if (context) {
|
|
139
|
+
context.timers.set(`Column.to`, (context.timers.get(`Column.to`) ?? 0) + elapsedTimeMs);
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const columnFrom = Column.prototype.from;
|
|
145
|
+
Column.prototype.from = function (this: Column, value: any) {
|
|
146
|
+
const startTime = process.hrtime.bigint();
|
|
147
|
+
const result = columnFrom.apply(this, [value]);
|
|
148
|
+
const elapsedTime = process.hrtime.bigint() - startTime;
|
|
149
|
+
const elapsedTimeMs = Number(elapsedTime) / 1000 / 1000;
|
|
150
|
+
|
|
151
|
+
if (elapsedTimeMs > 1) {
|
|
152
|
+
console.log(`Column.from for ${this.constructor.name} took ${elapsedTimeMs.toFixed(2)}ms`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Count total request
|
|
156
|
+
const context = ContextInstance.optional;
|
|
157
|
+
if (context) {
|
|
158
|
+
context.timers.set(`Column.from`, (context.timers.get(`Column.from`) ?? 0) + elapsedTimeMs);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const decode = AutoEncoder.decode;
|
|
164
|
+
AutoEncoder.decode = function (...args) {
|
|
165
|
+
const c = args[0].context as any;
|
|
166
|
+
if (c._didTrack) {
|
|
167
|
+
return decode.apply(this, args);
|
|
168
|
+
}
|
|
169
|
+
c._didTrack = true;
|
|
170
|
+
|
|
171
|
+
const startTime = process.hrtime.bigint();
|
|
172
|
+
const result = decode.apply(this, args);
|
|
173
|
+
const elapsedTime = process.hrtime.bigint() - startTime;
|
|
174
|
+
const elapsedTimeMs = Number(elapsedTime) / 1000 / 1000;
|
|
175
|
+
|
|
176
|
+
if (elapsedTimeMs > 1) {
|
|
177
|
+
console.log('AutoEncoder.decode took ' + elapsedTimeMs.toFixed(2) + 'ms');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const context = ContextInstance.optional;
|
|
181
|
+
if (context) {
|
|
182
|
+
context.timers.set('AutoEncoder.decode', (context.timers.get('AutoEncoder.decode') ?? 0) + elapsedTimeMs);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const encode = AutoEncoder.prototype.encode;
|
|
189
|
+
AutoEncoder.prototype.encode = function (this: AutoEncoder, ...args) {
|
|
190
|
+
const c = args[0] as any;
|
|
191
|
+
if (c._didTrack) {
|
|
192
|
+
return encode.apply(this, args);
|
|
193
|
+
}
|
|
194
|
+
c._didTrack = true;
|
|
195
|
+
|
|
196
|
+
const startTime = process.hrtime.bigint();
|
|
197
|
+
const result = encode.apply(this, args);
|
|
198
|
+
const elapsedTime = process.hrtime.bigint() - startTime;
|
|
199
|
+
const elapsedTimeMs = Number(elapsedTime) / 1000 / 1000;
|
|
200
|
+
|
|
201
|
+
if (elapsedTimeMs > 1) {
|
|
202
|
+
console.log('AutoEncoder.encode took ' + elapsedTimeMs.toFixed(2) + 'ms');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const context = ContextInstance.optional;
|
|
206
|
+
if (context) {
|
|
207
|
+
context.timers.set('AutoEncoder.encode', (context.timers.get('AutoEncoder.encode') ?? 0) + elapsedTimeMs);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return result;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Add middleware to log timers
|
|
214
|
+
const LogTimersMiddleware: ResponseMiddleware = {
|
|
215
|
+
handleResponse(request: Request, response: Response, error?: Error) {
|
|
216
|
+
const prefix = !error ? [] : requestPrefix(request, 'error');
|
|
217
|
+
const context = (request as any)._context as ContextInstance | undefined;
|
|
218
|
+
|
|
219
|
+
if (context && request.method !== 'OPTIONS') {
|
|
220
|
+
logger.log(
|
|
221
|
+
...prefix,
|
|
222
|
+
' - Timers: ' + Array.from(context.timers.entries()).sort((a, b) => {
|
|
223
|
+
return b[1] - a[1];
|
|
224
|
+
}).map(([key, value]) => `\n- ${key}: ${value.toFixed(2)}ms`).join(''),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
routerServer.addResponseMiddleware(LogTimersMiddleware);
|
|
230
|
+
}
|
|
@@ -9,7 +9,7 @@ async function fetch(query: LimitedFilteredRequest) {
|
|
|
9
9
|
const recipients: EmailRecipient[] = [];
|
|
10
10
|
const memberIds = new Set(result.results.map(doc => doc.memberId).filter(id => id !== null)); // silently skip null memberIds
|
|
11
11
|
|
|
12
|
-
const members = await Member.
|
|
12
|
+
const members = await Member.getByIdsWithUsers(...memberIds);
|
|
13
13
|
for (const member of members) {
|
|
14
14
|
const emails = member.details.getNotificationEmails();
|
|
15
15
|
for (const user of member.users) {
|
|
@@ -254,7 +254,7 @@ async function getMemberRecipients(ids: { memberId: string; payment: PaymentGene
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
const allMemberIds = Formatter.uniqueArray(ids.map(i => i.memberId));
|
|
257
|
-
const members = await Member.
|
|
257
|
+
const members = await Member.getByIdsWithUsers(...allMemberIds);
|
|
258
258
|
|
|
259
259
|
const results: EmailRecipient[] = [];
|
|
260
260
|
|
|
@@ -4,9 +4,9 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
4
4
|
import { EmailVerificationCode, Member, PasswordToken, Platform, Token, User } from '@stamhoofd/models';
|
|
5
5
|
import { LoginMethod, NewUser, PermissionLevel, SignupResponse, UserPermissions, UserWithMembers } from '@stamhoofd/structures';
|
|
6
6
|
|
|
7
|
-
import { Context } from '../../helpers/Context';
|
|
8
|
-
import { MemberUserSyncer } from '../../helpers/MemberUserSyncer';
|
|
9
|
-
import { AuthenticatedStructures } from '../../helpers/AuthenticatedStructures';
|
|
7
|
+
import { Context } from '../../helpers/Context.js';
|
|
8
|
+
import { MemberUserSyncer } from '../../helpers/MemberUserSyncer.js';
|
|
9
|
+
import { AuthenticatedStructures } from '../../helpers/AuthenticatedStructures.js';
|
|
10
10
|
|
|
11
11
|
type Params = { id: string };
|
|
12
12
|
type Query = undefined;
|
|
@@ -49,7 +49,7 @@ export class PatchUserEndpoint extends Endpoint<Params, Query, Body, ResponseBod
|
|
|
49
49
|
|
|
50
50
|
if (await Context.auth.canEditUserName(editUser)) {
|
|
51
51
|
if (editUser.memberId) {
|
|
52
|
-
const member = await Member.
|
|
52
|
+
const member = await Member.getByID(editUser.memberId);
|
|
53
53
|
if (member) {
|
|
54
54
|
member.details.firstName = request.body.firstName ?? member.details.firstName;
|
|
55
55
|
member.details.lastName = request.body.lastName ?? member.details.lastName;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import { Member,
|
|
2
|
+
import { Member, MemberWithUsersRegistrationsAndGroups } from '@stamhoofd/models';
|
|
3
3
|
|
|
4
4
|
import { MembersBlob } from '@stamhoofd/structures';
|
|
5
|
-
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
6
|
-
import { Context } from '../../../helpers/Context';
|
|
5
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
|
|
6
|
+
import { Context } from '../../../helpers/Context.js';
|
|
7
7
|
type Params = { id: string };
|
|
8
8
|
type Query = undefined;
|
|
9
9
|
type Body = undefined;
|
|
@@ -47,7 +47,7 @@ export class GetMemberFamilyEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
47
47
|
|
|
48
48
|
let foundMember = false;
|
|
49
49
|
|
|
50
|
-
const validatedMembers:
|
|
50
|
+
const validatedMembers: MemberWithUsersRegistrationsAndGroups[] = [];
|
|
51
51
|
|
|
52
52
|
for (const member of members) {
|
|
53
53
|
if (member.id === request.params.id) {
|
|
@@ -126,10 +126,7 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
const query =
|
|
130
|
-
.select(
|
|
131
|
-
SQL.column('members', 'id'),
|
|
132
|
-
)
|
|
129
|
+
const query = Member.select()
|
|
133
130
|
.setMaxExecutionTime(15 * 1000)
|
|
134
131
|
.from(
|
|
135
132
|
SQL.table('members'),
|
|
@@ -257,7 +254,7 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
257
254
|
|
|
258
255
|
static async buildData(requestQuery: LimitedFilteredRequest, permissionLevel = PermissionLevel.Read) {
|
|
259
256
|
const query = await GetMembersEndpoint.buildQuery(requestQuery, permissionLevel);
|
|
260
|
-
let data:
|
|
257
|
+
let data: Member[];
|
|
261
258
|
|
|
262
259
|
try {
|
|
263
260
|
data = await query.fetch();
|
|
@@ -273,16 +270,7 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
273
270
|
throw error;
|
|
274
271
|
}
|
|
275
272
|
|
|
276
|
-
const
|
|
277
|
-
if (typeof r.members.id === 'string') {
|
|
278
|
-
return r.members.id;
|
|
279
|
-
}
|
|
280
|
-
throw new Error('Expected string');
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
const _members = await Member.getBlobByIds(...memberIds);
|
|
284
|
-
// Make sure members is in same order as memberIds
|
|
285
|
-
const members = memberIds.map(id => _members.find(m => m.id === id)!);
|
|
273
|
+
const members = await Member.loadRegistrationsAndUsers(data, true);
|
|
286
274
|
|
|
287
275
|
for (const member of members) {
|
|
288
276
|
if (!await Context.auth.canAccessMember(member, permissionLevel)) {
|
|
@@ -293,7 +281,7 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
293
281
|
|
|
294
282
|
let next: LimitedFilteredRequest | undefined;
|
|
295
283
|
|
|
296
|
-
if (
|
|
284
|
+
if (members.length >= requestQuery.limit) {
|
|
297
285
|
const lastObject = members[members.length - 1];
|
|
298
286
|
const nextFilter = getSortFilter(lastObject, sorters, requestQuery.sort);
|
|
299
287
|
|
|
@@ -2,7 +2,7 @@ import { OneToManyRelation } from '@simonbackx/simple-database';
|
|
|
2
2
|
import { AutoEncoderPatchType, ConvertArrayToPatchableArray, Decoder, isEmptyPatch, isPatchableArray, PatchableArray, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
-
import { AuditLog, BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, mergeTwoMembers, Organization, Platform, RateLimiter, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
5
|
+
import { AuditLog, BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, MemberWithUsersAndRegistrations, MemberWithUsersRegistrationsAndGroups, mergeTwoMembers, Organization, Platform, RateLimiter, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
6
6
|
import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, EmergencyContact, GroupType, MemberDetails, MemberResponsibility, MembersBlob, MemberWithRegistrationsBlob, Parent, PermissionLevel, SetupStepType } from '@stamhoofd/structures';
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
@@ -72,7 +72,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const members:
|
|
75
|
+
const members: MemberWithUsersRegistrationsAndGroups[] = [];
|
|
76
76
|
|
|
77
77
|
const platform = await Platform.getShared();
|
|
78
78
|
|
|
@@ -717,7 +717,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
717
717
|
|
|
718
718
|
// Loop all members one by one
|
|
719
719
|
for (const id of ids) {
|
|
720
|
-
const member = await Member.
|
|
720
|
+
const member = await Member.getByIdWithUsersAndRegistrations(id);
|
|
721
721
|
if (!member || !await Context.auth.canDeleteMember(member)) {
|
|
722
722
|
throw Context.auth.error($t(`39f5696c-3755-429f-b0da-a0ca920ed11e`));
|
|
723
723
|
}
|
|
@@ -754,9 +754,10 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
754
754
|
}
|
|
755
755
|
}
|
|
756
756
|
|
|
757
|
-
static async mergeDuplicateRelations(member:
|
|
758
|
-
const
|
|
759
|
-
const
|
|
757
|
+
static async mergeDuplicateRelations(member: Member, patch: AutoEncoderPatchType<MemberDetails> | MemberDetails) {
|
|
758
|
+
const __familyMembers = await Member.getFamily(member.id);
|
|
759
|
+
const _familyMembers = await Member.loadRegistrationsAndUsers(__familyMembers);
|
|
760
|
+
const familyMembers: Member[] = [];
|
|
760
761
|
// Only modify members if we have write access to them (this avoids issues with overriding data)
|
|
761
762
|
for (const member of _familyMembers) {
|
|
762
763
|
if (await Context.auth.canAccessMember(member, PermissionLevel.Write)) {
|
|
@@ -901,7 +902,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
901
902
|
}
|
|
902
903
|
}
|
|
903
904
|
|
|
904
|
-
static async checkSecurityCode(member:
|
|
905
|
+
static async checkSecurityCode(member: MemberWithUsersRegistrationsAndGroups, securityCode: string | null | undefined, type: 'put' | 'patch') {
|
|
905
906
|
if ((type === 'put' && await member.isSafeToMergeDuplicateWithoutSecurityCode()) || await Context.auth.canAccessMember(member, PermissionLevel.Write)) {
|
|
906
907
|
console.log('checkSecurityCode: without security code: allowed for ' + member.id);
|
|
907
908
|
}
|
|
@@ -3,8 +3,8 @@ import { BalanceItem, Member, Organization, Payment } from '@stamhoofd/models';
|
|
|
3
3
|
import { DetailedPayableBalanceCollection, DetailedPayableBalance } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
|
-
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
7
|
-
import { Context } from '../../../helpers/Context';
|
|
6
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
|
|
7
|
+
import { Context } from '../../../helpers/Context.js';
|
|
8
8
|
|
|
9
9
|
type Params = Record<string, never>;
|
|
10
10
|
type Query = undefined;
|
|
@@ -3,7 +3,7 @@ import { Document, DocumentTemplate, Member } from '@stamhoofd/models';
|
|
|
3
3
|
import { Document as DocumentStruct, DocumentStatus } from '@stamhoofd/structures';
|
|
4
4
|
import { Sorter } from '@stamhoofd/utility';
|
|
5
5
|
|
|
6
|
-
import { Context } from '../../../helpers/Context';
|
|
6
|
+
import { Context } from '../../../helpers/Context.js';
|
|
7
7
|
type Params = Record<string, never>;
|
|
8
8
|
type Query = undefined;
|
|
9
9
|
type Body = undefined;
|
|
@@ -31,7 +31,7 @@ export class GetUserDocumentsEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
31
31
|
const organization = await Context.setUserOrganizationScope();
|
|
32
32
|
const { user } = await Context.authenticate();
|
|
33
33
|
|
|
34
|
-
const members = await Member.
|
|
34
|
+
const members = await Member.getMembersForUser(user);
|
|
35
35
|
let templates = organization ? await DocumentTemplate.where({ status: 'Published', organizationId: organization.id }) : null;
|
|
36
36
|
const memberIds = members.map(m => m.id);
|
|
37
37
|
const templateIds = templates ? templates.map(t => t.id) : null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PatchMap } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { EmailMocker } from '@stamhoofd/email';
|
|
4
|
-
import { BalanceItemFactory, Group, GroupFactory, Member, MemberFactory,
|
|
4
|
+
import { BalanceItemFactory, Group, GroupFactory, Member, MemberFactory, MemberWithUsersRegistrationsAndGroups, Organization, OrganizationFactory, OrganizationRegistrationPeriodFactory, Registration, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
5
5
|
import { AccessRight, BalanceItemCartItem, BalanceItemStatus, BalanceItemType, BooleanStatus, Company, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, OrganizationPackages, PaymentCustomer, PaymentMethod, PermissionLevel, Permissions, PermissionsResourceType, ReduceablePrice, RegisterItemOption, ResourcePermissions, STPackageStatus, STPackageType, UitpasNumberDetails, UitpasSocialTariff, UitpasSocialTariffStatus, UserPermissions, Version } from '@stamhoofd/structures';
|
|
6
6
|
import { STExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
7
7
|
import { v4 as uuidv4 } from 'uuid';
|
|
@@ -80,7 +80,7 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
80
80
|
const member = await new MemberFactory({ organization, user: linkMembersToUser ? user : undefined })
|
|
81
81
|
.create();
|
|
82
82
|
|
|
83
|
-
const otherMembers:
|
|
83
|
+
const otherMembers: MemberWithUsersRegistrationsAndGroups[] = [];
|
|
84
84
|
|
|
85
85
|
for (let i = 0; i < otherMemberAmount; i++) {
|
|
86
86
|
otherMembers.push(await new MemberFactory({ organization, user: linkMembersToUser ? user : undefined })
|
|
@@ -4,9 +4,9 @@ import { Decoder } from '@simonbackx/simple-encoding';
|
|
|
4
4
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
5
5
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
6
|
import { Email } from '@stamhoofd/email';
|
|
7
|
-
import { BalanceItem, BalanceItemPayment, CachedBalance, Group, Member,
|
|
7
|
+
import { BalanceItem, BalanceItemPayment, CachedBalance, Group, Member, MemberWithUsersRegistrationsAndGroups, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment, Platform, RateLimiter, Registration, User } from '@stamhoofd/models';
|
|
8
8
|
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItem as BalanceItemStruct, BalanceItemType, IDRegisterCheckout, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PaymentType, PermissionLevel, PlatformFamily, PlatformMember, ReceivableBalanceType, RegisterItem, RegisterResponse, TranslatedString, Version } from '@stamhoofd/structures';
|
|
9
|
-
import { Formatter } from '@stamhoofd/utility';
|
|
9
|
+
import { Formatter, sleep } from '@stamhoofd/utility';
|
|
10
10
|
|
|
11
11
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
|
|
12
12
|
import { BuckarooHelper } from '../../../helpers/BuckarooHelper.js';
|
|
@@ -145,7 +145,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
145
145
|
memberBalanceItemsStructs = balanceItemsModels.map(i => i.getStructure());
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
let members:
|
|
148
|
+
let members: MemberWithUsersRegistrationsAndGroups[] = [];
|
|
149
149
|
if (request.body.asOrganizationId) {
|
|
150
150
|
const memberIds = Formatter.uniqueArray(
|
|
151
151
|
[...request.body.memberIds, ...deleteRegistrationModels.map(i => i.memberId), ...balanceItemsModels.map(i => i.memberId).filter(m => m !== null)],
|
|
@@ -900,21 +900,26 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
900
900
|
}
|
|
901
901
|
}
|
|
902
902
|
|
|
903
|
-
|
|
903
|
+
// Flush caches so data is up to date in response
|
|
904
|
+
await BalanceItemService.flushCaches(organization.id);
|
|
905
|
+
|
|
906
|
+
// Force reload registrations and group data
|
|
907
|
+
Member.unloadRegistrations(members);
|
|
908
|
+
await Member.loadRegistrations(members, true);
|
|
904
909
|
|
|
905
910
|
return new Response(RegisterResponse.create({
|
|
906
911
|
payment: payment ? PaymentStruct.create(payment) : null,
|
|
907
|
-
members: await AuthenticatedStructures.membersBlob(
|
|
912
|
+
members: await AuthenticatedStructures.membersBlob(members),
|
|
908
913
|
registrations: registrations.map(r => Member.getRegistrationWithTinyMemberStructure(r)),
|
|
909
914
|
paymentUrl,
|
|
910
915
|
paymentQRCode,
|
|
911
916
|
}));
|
|
912
917
|
}
|
|
913
918
|
|
|
914
|
-
async createPayment({ balanceItems, organization, user, checkout, members }: { balanceItems: Map<BalanceItem, number>; organization: Organization; user: User; checkout: IDRegisterCheckout; members:
|
|
919
|
+
async createPayment({ balanceItems, organization, user, checkout, members }: { balanceItems: Map<BalanceItem, number>; organization: Organization; user: User; checkout: IDRegisterCheckout; members: MemberWithUsersRegistrationsAndGroups[] }) {
|
|
915
920
|
// Calculate total price to pay
|
|
916
921
|
let totalPrice = 0;
|
|
917
|
-
const payMembers:
|
|
922
|
+
const payMembers: MemberWithUsersRegistrationsAndGroups[] = [];
|
|
918
923
|
let hasNegative = false;
|
|
919
924
|
|
|
920
925
|
for (const [balanceItem, price] of balanceItems) {
|
|
@@ -4,7 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
4
4
|
import { Document, DocumentTemplate, Member, Registration } from '@stamhoofd/models';
|
|
5
5
|
import { DocumentStatus, Document as DocumentStruct, PermissionLevel } from '@stamhoofd/structures';
|
|
6
6
|
|
|
7
|
-
import { Context } from '../../../../helpers/Context';
|
|
7
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
8
8
|
|
|
9
9
|
type Params = Record<string, never>;
|
|
10
10
|
type Query = undefined;
|
|
@@ -104,7 +104,7 @@ export class PatchDocumentEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
104
104
|
put.memberId = registration.memberId;
|
|
105
105
|
}
|
|
106
106
|
if (put.memberId) {
|
|
107
|
-
const member = await Member.
|
|
107
|
+
const member = await Member.getByIdWithUsersAndRegistrations(put.memberId);
|
|
108
108
|
if (!member || !await Context.auth.canAccessMember(member, PermissionLevel.Read)) {
|
|
109
109
|
throw new SimpleError({
|
|
110
110
|
code: 'not_found',
|