@stamhoofd/backend 2.55.1 → 2.56.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 +4 -0
- package/package.json +11 -10
- package/src/crons.ts +4 -3
- package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +150 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +27 -9
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +2 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +17 -1
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -9
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +5 -3
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +4 -306
- package/src/helpers/AdminPermissionChecker.ts +102 -1
- package/src/helpers/AuthenticatedStructures.ts +46 -2
- package/src/helpers/EmailResumer.ts +8 -3
- package/src/seeds/1732117645-move-rrn.ts +77 -0
- package/src/services/AuditLogService.ts +681 -0
- package/src/services/BalanceItemPaymentService.ts +45 -0
- package/src/services/BalanceItemService.ts +88 -0
- package/src/services/GroupService.ts +13 -0
- package/src/services/PaymentService.ts +308 -0
- package/src/services/RegistrationService.ts +78 -0
- package/src/sql-filters/audit-logs.ts +10 -0
- package/src/sql-sorters/audit-logs.ts +35 -0
package/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { sleep } from '@stamhoofd/utility';
|
|
|
13
13
|
import { stopCrons, startCrons, waitForCrons } from '@stamhoofd/crons';
|
|
14
14
|
import { resumeEmails } from './src/helpers/EmailResumer';
|
|
15
15
|
import { ContextMiddleware } from './src/middleware/ContextMiddleware';
|
|
16
|
+
import { Platform } from '@stamhoofd/models';
|
|
16
17
|
|
|
17
18
|
process.on('unhandledRejection', (error: Error) => {
|
|
18
19
|
console.error('unhandledRejection');
|
|
@@ -83,6 +84,9 @@ const start = async () => {
|
|
|
83
84
|
// Add CORS headers
|
|
84
85
|
routerServer.addResponseMiddleware(CORSMiddleware);
|
|
85
86
|
|
|
87
|
+
// Init platform shared struct: otherwise permissions won't work with missing responsibilities
|
|
88
|
+
await Platform.getSharedStruct();
|
|
89
|
+
|
|
86
90
|
// Register Excel loaders
|
|
87
91
|
await import('./src/excel-loaders/members');
|
|
88
92
|
await import('./src/excel-loaders/payments');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.56.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -31,19 +31,20 @@
|
|
|
31
31
|
"sinon": "^18.0.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@bwip-js/node": "^4.5.1",
|
|
34
35
|
"@mollie/api-client": "3.7.0",
|
|
35
36
|
"@simonbackx/simple-database": "1.25.0",
|
|
36
37
|
"@simonbackx/simple-encoding": "2.16.6",
|
|
37
38
|
"@simonbackx/simple-endpoints": "1.14.0",
|
|
38
39
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
39
|
-
"@stamhoofd/backend-i18n": "2.
|
|
40
|
-
"@stamhoofd/backend-middleware": "2.
|
|
41
|
-
"@stamhoofd/email": "2.
|
|
42
|
-
"@stamhoofd/models": "2.
|
|
43
|
-
"@stamhoofd/queues": "2.
|
|
44
|
-
"@stamhoofd/sql": "2.
|
|
45
|
-
"@stamhoofd/structures": "2.
|
|
46
|
-
"@stamhoofd/utility": "2.
|
|
40
|
+
"@stamhoofd/backend-i18n": "2.56.0",
|
|
41
|
+
"@stamhoofd/backend-middleware": "2.56.0",
|
|
42
|
+
"@stamhoofd/email": "2.56.0",
|
|
43
|
+
"@stamhoofd/models": "2.56.0",
|
|
44
|
+
"@stamhoofd/queues": "2.56.0",
|
|
45
|
+
"@stamhoofd/sql": "2.56.0",
|
|
46
|
+
"@stamhoofd/structures": "2.56.0",
|
|
47
|
+
"@stamhoofd/utility": "2.56.0",
|
|
47
48
|
"archiver": "^7.0.1",
|
|
48
49
|
"aws-sdk": "^2.885.0",
|
|
49
50
|
"axios": "1.6.8",
|
|
@@ -63,5 +64,5 @@
|
|
|
63
64
|
"publishConfig": {
|
|
64
65
|
"access": "public"
|
|
65
66
|
},
|
|
66
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "39e791f29d992c918b83871d9dc6cf72c418b2c4"
|
|
67
68
|
}
|
package/src/crons.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { endFunctionsOfUsersWithoutRegistration } from './crons/endFunctionsOfUs
|
|
|
13
13
|
import { ExchangePaymentEndpoint } from './endpoints/organization/shared/ExchangePaymentEndpoint';
|
|
14
14
|
import { checkSettlements } from './helpers/CheckSettlements';
|
|
15
15
|
import { ForwardHandler } from './helpers/ForwardHandler';
|
|
16
|
+
import { PaymentService } from './services/PaymentService';
|
|
16
17
|
|
|
17
18
|
// Importing postmark returns undefined (this is a bug, so we need to use require)
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -469,7 +470,7 @@ async function checkPayments() {
|
|
|
469
470
|
if (payment.organizationId) {
|
|
470
471
|
const organization = await Organization.getByID(payment.organizationId);
|
|
471
472
|
if (organization) {
|
|
472
|
-
await
|
|
473
|
+
await PaymentService.pollStatus(payment.id, organization);
|
|
473
474
|
continue;
|
|
474
475
|
}
|
|
475
476
|
}
|
|
@@ -478,7 +479,7 @@ async function checkPayments() {
|
|
|
478
479
|
}
|
|
479
480
|
|
|
480
481
|
// Check expired
|
|
481
|
-
if (
|
|
482
|
+
if (PaymentService.isManualExpired(payment.status, payment)) {
|
|
482
483
|
console.error('[DELAYED PAYMENTS] Could not resolve handler for expired payment, marking as failed', payment.id);
|
|
483
484
|
payment.status = PaymentStatus.Failed;
|
|
484
485
|
await payment.save();
|
|
@@ -542,7 +543,7 @@ async function checkFailedBuckarooPayments() {
|
|
|
542
543
|
if (payment.organizationId) {
|
|
543
544
|
const organization = await Organization.getByID(payment.organizationId);
|
|
544
545
|
if (organization) {
|
|
545
|
-
await
|
|
546
|
+
await PaymentService.pollStatus(payment.id, organization);
|
|
546
547
|
continue;
|
|
547
548
|
}
|
|
548
549
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { AuditLog } from '@stamhoofd/models';
|
|
5
|
+
import { SQL, SQLFilterDefinitions, SQLSortDefinitions, compileToSQLFilter, compileToSQLSorter } from '@stamhoofd/sql';
|
|
6
|
+
import { AuditLog as AuditLogStruct, CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
|
|
7
|
+
|
|
8
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
9
|
+
import { Context } from '../../../helpers/Context';
|
|
10
|
+
import { auditLogFilterCompilers } from '../../../sql-filters/audit-logs';
|
|
11
|
+
import { auditLogSorters } from '../../../sql-sorters/audit-logs';
|
|
12
|
+
|
|
13
|
+
type Params = Record<string, never>;
|
|
14
|
+
type Query = LimitedFilteredRequest;
|
|
15
|
+
type Body = undefined;
|
|
16
|
+
type ResponseBody = PaginatedResponse<AuditLogStruct[], LimitedFilteredRequest>;
|
|
17
|
+
|
|
18
|
+
const filterCompilers: SQLFilterDefinitions = auditLogFilterCompilers;
|
|
19
|
+
const sorters: SQLSortDefinitions<AuditLog> = auditLogSorters;
|
|
20
|
+
|
|
21
|
+
export class GetAuditLogsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
22
|
+
queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>;
|
|
23
|
+
|
|
24
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
25
|
+
if (request.method !== 'GET') {
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const params = Endpoint.parseParameters(request.url, '/audit-logs', {});
|
|
30
|
+
|
|
31
|
+
if (params) {
|
|
32
|
+
return [true, params as Params];
|
|
33
|
+
}
|
|
34
|
+
return [false];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static async buildQuery(q: CountFilteredRequest | LimitedFilteredRequest) {
|
|
38
|
+
const organization = Context.organization;
|
|
39
|
+
let scopeFilter: StamhoofdFilter | undefined = undefined;
|
|
40
|
+
|
|
41
|
+
if (organization) {
|
|
42
|
+
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
43
|
+
throw Context.auth.error();
|
|
44
|
+
}
|
|
45
|
+
scopeFilter = {
|
|
46
|
+
organizationId: organization.id,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
51
|
+
throw Context.auth.error();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const query = SQL
|
|
56
|
+
.select(
|
|
57
|
+
SQL.wildcard(AuditLog.table),
|
|
58
|
+
)
|
|
59
|
+
.from(
|
|
60
|
+
SQL.table(AuditLog.table),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (scopeFilter) {
|
|
64
|
+
query.where(compileToSQLFilter(scopeFilter, filterCompilers));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (q.filter) {
|
|
68
|
+
query.where(compileToSQLFilter(q.filter, filterCompilers));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (q.search) {
|
|
72
|
+
throw new SimpleError({
|
|
73
|
+
code: 'not_supported',
|
|
74
|
+
message: 'Search is not possible in audit logs',
|
|
75
|
+
human: 'Zoeken is niet mogelijk in audit logs',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (q instanceof LimitedFilteredRequest) {
|
|
80
|
+
if (q.pageFilter) {
|
|
81
|
+
query.where(compileToSQLFilter(q.pageFilter, filterCompilers));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
q.sort = assertSort(q.sort, [{ key: 'id' }]);
|
|
85
|
+
query.orderBy(compileToSQLSorter(q.sort, sorters));
|
|
86
|
+
query.limit(q.limit);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return query;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static async buildData(requestQuery: LimitedFilteredRequest) {
|
|
93
|
+
const query = await GetAuditLogsEndpoint.buildQuery(requestQuery);
|
|
94
|
+
const data = await query.fetch();
|
|
95
|
+
|
|
96
|
+
const logs = AuditLog.fromRows(data, AuditLog.table);
|
|
97
|
+
|
|
98
|
+
let next: LimitedFilteredRequest | undefined;
|
|
99
|
+
|
|
100
|
+
if (logs.length >= requestQuery.limit) {
|
|
101
|
+
const lastObject = logs[logs.length - 1];
|
|
102
|
+
const nextFilter = getSortFilter(lastObject, sorters, requestQuery.sort);
|
|
103
|
+
|
|
104
|
+
next = new LimitedFilteredRequest({
|
|
105
|
+
filter: requestQuery.filter,
|
|
106
|
+
pageFilter: nextFilter,
|
|
107
|
+
sort: requestQuery.sort,
|
|
108
|
+
limit: requestQuery.limit,
|
|
109
|
+
search: requestQuery.search,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (JSON.stringify(nextFilter) === JSON.stringify(requestQuery.pageFilter)) {
|
|
113
|
+
console.error('Found infinite loading loop for', requestQuery);
|
|
114
|
+
next = undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return new PaginatedResponse<AuditLogStruct[], LimitedFilteredRequest>({
|
|
119
|
+
results: await AuthenticatedStructures.auditLogs(logs),
|
|
120
|
+
next,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
125
|
+
await Context.setOptionalOrganizationScope();
|
|
126
|
+
await Context.authenticate();
|
|
127
|
+
|
|
128
|
+
const maxLimit = 100;
|
|
129
|
+
|
|
130
|
+
if (request.query.limit > maxLimit) {
|
|
131
|
+
throw new SimpleError({
|
|
132
|
+
code: 'invalid_field',
|
|
133
|
+
field: 'limit',
|
|
134
|
+
message: 'Limit can not be more than ' + maxLimit,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (request.query.limit < 1) {
|
|
139
|
+
throw new SimpleError({
|
|
140
|
+
code: 'invalid_field',
|
|
141
|
+
field: 'limit',
|
|
142
|
+
message: 'Limit can not be less than 1',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return new Response(
|
|
147
|
+
await GetAuditLogsEndpoint.buildData(request.query),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -3,7 +3,7 @@ import { ConvertArrayToPatchableArray, Decoder, PatchableArrayAutoEncoder, Patch
|
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
5
|
import { BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, mergeTwoMembers, Organization, Platform, RateLimiter, Registration, SetupStepUpdater, User } from '@stamhoofd/models';
|
|
6
|
-
import { GroupType, MembersBlob, MemberWithRegistrationsBlob, PermissionLevel } from '@stamhoofd/structures';
|
|
6
|
+
import { AuditLogType, GroupType, MembersBlob, MemberWithRegistrationsBlob, PermissionLevel } from '@stamhoofd/structures';
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
9
|
import { Email } from '@stamhoofd/email';
|
|
@@ -13,6 +13,7 @@ import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructure
|
|
|
13
13
|
import { Context } from '../../../helpers/Context';
|
|
14
14
|
import { MembershipCharger } from '../../../helpers/MembershipCharger';
|
|
15
15
|
import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
|
|
16
|
+
import { AuditLogService } from '../../../services/AuditLogService';
|
|
16
17
|
|
|
17
18
|
type Params = Record<string, never>;
|
|
18
19
|
type Query = undefined;
|
|
@@ -142,6 +143,13 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
142
143
|
|
|
143
144
|
// Auto link users based on data
|
|
144
145
|
await MemberUserSyncer.onChangeMember(member);
|
|
146
|
+
|
|
147
|
+
if (!duplicate) {
|
|
148
|
+
await AuditLogService.log({
|
|
149
|
+
type: AuditLogType.MemberAdded,
|
|
150
|
+
member: member,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
let shouldUpdateSetupSteps = false;
|
|
@@ -161,6 +169,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
patch = await Context.auth.filterMemberPatch(member, patch);
|
|
172
|
+
const originalDetails = member.details.clone();
|
|
164
173
|
|
|
165
174
|
if (patch.details) {
|
|
166
175
|
if (patch.details.isPut()) {
|
|
@@ -196,6 +205,15 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
196
205
|
|
|
197
206
|
await member.save();
|
|
198
207
|
|
|
208
|
+
if (patch.details) {
|
|
209
|
+
await AuditLogService.log({
|
|
210
|
+
type: AuditLogType.MemberEdited,
|
|
211
|
+
member: member,
|
|
212
|
+
oldMemberDetails: originalDetails,
|
|
213
|
+
memberDetailsPatch: patch.details,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
199
217
|
// Update documents
|
|
200
218
|
await Document.updateForMember(member.id);
|
|
201
219
|
|
|
@@ -450,14 +468,14 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
450
468
|
SQL.where('startDate', SQLWhereSign.LessEqual, put.startDate)
|
|
451
469
|
.and('endDate', SQLWhereSign.GreaterEqual, put.startDate),
|
|
452
470
|
)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
471
|
+
.or(
|
|
472
|
+
SQL.where('startDate', SQLWhereSign.LessEqual, put.endDate)
|
|
473
|
+
.and('endDate', SQLWhereSign.GreaterEqual, put.endDate),
|
|
474
|
+
)
|
|
475
|
+
.or(
|
|
476
|
+
SQL.where('startDate', SQLWhereSign.GreaterEqual, put.startDate)
|
|
477
|
+
.and('endDate', SQLWhereSign.LessEqual, put.endDate),
|
|
478
|
+
),
|
|
461
479
|
)
|
|
462
480
|
.first(false);
|
|
463
481
|
|
|
@@ -6,6 +6,7 @@ import { isDebouncedError, QueueHandler } from '@stamhoofd/queues';
|
|
|
6
6
|
|
|
7
7
|
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
8
8
|
import { ExchangePaymentEndpoint } from '../../organization/shared/ExchangePaymentEndpoint';
|
|
9
|
+
import { PaymentService } from '../../../services/PaymentService';
|
|
9
10
|
|
|
10
11
|
type Params = Record<string, never>;
|
|
11
12
|
class Body extends AutoEncoder {
|
|
@@ -159,7 +160,7 @@ export class StripeWebookEndpoint extends Endpoint<Params, Query, Body, Response
|
|
|
159
160
|
await QueueHandler.debounce('stripe-webhook/payment-' + paymentId, async () => {
|
|
160
161
|
const organization = await Organization.getByID(organizationId);
|
|
161
162
|
if (organization) {
|
|
162
|
-
await
|
|
163
|
+
await PaymentService.pollStatus(paymentId, organization);
|
|
163
164
|
}
|
|
164
165
|
else {
|
|
165
166
|
console.warn('Could not find organization with id', organizationId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, isPatchableArray, patchObject } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { Organization, Platform, RegistrationPeriod, SetupStepUpdater } from '@stamhoofd/models';
|
|
4
|
-
import { MemberResponsibility, PlatformConfig, PlatformPremiseType, Platform as PlatformStruct } from '@stamhoofd/structures';
|
|
4
|
+
import { AuditLogType, MemberResponsibility, PlatformConfig, PlatformPremiseType, Platform as PlatformStruct } from '@stamhoofd/structures';
|
|
5
5
|
|
|
6
6
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
7
7
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
@@ -10,6 +10,7 @@ import { MembershipCharger } from '../../../helpers/MembershipCharger';
|
|
|
10
10
|
import { MembershipHelper } from '../../../helpers/MembershipHelper';
|
|
11
11
|
import { PeriodHelper } from '../../../helpers/PeriodHelper';
|
|
12
12
|
import { TagHelper } from '../../../helpers/TagHelper';
|
|
13
|
+
import { AuditLogService } from '../../../services/AuditLogService';
|
|
13
14
|
|
|
14
15
|
type Params = Record<string, never>;
|
|
15
16
|
type Query = undefined;
|
|
@@ -90,9 +91,9 @@ export class PatchPlatformEndpoint extends Endpoint<
|
|
|
90
91
|
|
|
91
92
|
// Update config
|
|
92
93
|
if (newConfig) {
|
|
94
|
+
const oldConfig: PlatformConfig = platform.config.clone();
|
|
93
95
|
const shouldCheckSteps = newConfig.premiseTypes || newConfig.responsibilities;
|
|
94
96
|
if (shouldCheckSteps) {
|
|
95
|
-
const oldConfig: PlatformConfig = platform.config.clone();
|
|
96
97
|
platform.config = patchObject(platform.config, newConfig);
|
|
97
98
|
const currentConfig: PlatformConfig = platform.config;
|
|
98
99
|
|
|
@@ -107,6 +108,12 @@ export class PatchPlatformEndpoint extends Endpoint<
|
|
|
107
108
|
else {
|
|
108
109
|
platform.config = patchObject(platform.config, newConfig);
|
|
109
110
|
}
|
|
111
|
+
|
|
112
|
+
await AuditLogService.log({
|
|
113
|
+
type: AuditLogType.PlatformSettingChanged,
|
|
114
|
+
oldConfig,
|
|
115
|
+
patch: newConfig,
|
|
116
|
+
});
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
if (newConfig.tags && isPatchableArray(newConfig.tags) && newConfig.tags.changes.length > 0) {
|
|
@@ -2,12 +2,13 @@ import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArra
|
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
4
|
import { Document, Member, RateLimiter } from '@stamhoofd/models';
|
|
5
|
-
import { MemberDetails, MembersBlob, MemberWithRegistrationsBlob } from '@stamhoofd/structures';
|
|
5
|
+
import { AuditLogType, MemberDetails, MembersBlob, MemberWithRegistrationsBlob } from '@stamhoofd/structures';
|
|
6
6
|
|
|
7
7
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
8
8
|
import { Context } from '../../../helpers/Context';
|
|
9
9
|
import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
|
|
10
10
|
import { PatchOrganizationMembersEndpoint } from '../../global/members/PatchOrganizationMembersEndpoint';
|
|
11
|
+
import { AuditLogService } from '../../../services/AuditLogService';
|
|
11
12
|
type Params = Record<string, never>;
|
|
12
13
|
type Query = undefined;
|
|
13
14
|
type Body = PatchableArrayAutoEncoder<MemberWithRegistrationsBlob>;
|
|
@@ -68,6 +69,11 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
68
69
|
|
|
69
70
|
await member.save();
|
|
70
71
|
addedMembers.push(member);
|
|
72
|
+
|
|
73
|
+
await AuditLogService.log({
|
|
74
|
+
type: AuditLogType.MemberAdded,
|
|
75
|
+
member: member,
|
|
76
|
+
});
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
// Modify members
|
|
@@ -84,6 +90,7 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
84
90
|
}
|
|
85
91
|
const securityCode = struct.details?.securityCode; // will get cleared after the filter
|
|
86
92
|
struct = await Context.auth.filterMemberPatch(member, struct);
|
|
93
|
+
const originalDetails = member.details.clone();
|
|
87
94
|
|
|
88
95
|
if (struct.details) {
|
|
89
96
|
if (struct.details.isPut()) {
|
|
@@ -122,6 +129,15 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
122
129
|
await member.save();
|
|
123
130
|
await MemberUserSyncer.onChangeMember(member);
|
|
124
131
|
|
|
132
|
+
if (struct.details) {
|
|
133
|
+
await AuditLogService.log({
|
|
134
|
+
type: AuditLogType.MemberEdited,
|
|
135
|
+
member: member,
|
|
136
|
+
oldMemberDetails: originalDetails,
|
|
137
|
+
memberDetailsPatch: struct.details,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
// Update documents
|
|
126
142
|
await Document.updateForMember(member.id);
|
|
127
143
|
}
|
|
@@ -12,6 +12,8 @@ import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructure
|
|
|
12
12
|
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
13
13
|
import { Context } from '../../../helpers/Context';
|
|
14
14
|
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
15
|
+
import { BalanceItemService } from '../../../services/BalanceItemService';
|
|
16
|
+
import { RegistrationService } from '../../../services/RegistrationService';
|
|
15
17
|
type Params = Record<string, never>;
|
|
16
18
|
type Query = undefined;
|
|
17
19
|
type Body = IDRegisterCheckout;
|
|
@@ -354,16 +356,17 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
354
356
|
await BalanceItem.deleteForDeletedRegistration(existingRegistration.id);
|
|
355
357
|
|
|
356
358
|
// Clear the registration
|
|
357
|
-
|
|
358
|
-
deactivatedRegistrationGroupIds.push(existingRegistration.groupId);
|
|
359
|
-
|
|
360
|
-
const group = groups.find(g => g.id === existingRegistration.groupId);
|
|
359
|
+
let group = groups.find(g => g.id === existingRegistration.groupId);
|
|
361
360
|
if (!group) {
|
|
362
|
-
|
|
363
|
-
if (
|
|
364
|
-
groups.push(
|
|
361
|
+
group = await Group.getByID(existingRegistration.groupId);
|
|
362
|
+
if (group) {
|
|
363
|
+
groups.push(group);
|
|
365
364
|
}
|
|
366
365
|
}
|
|
366
|
+
const member = members.find(m => m.id === existingRegistration.memberId);
|
|
367
|
+
|
|
368
|
+
await RegistrationService.deactivate(existingRegistration, group, member);
|
|
369
|
+
deactivatedRegistrationGroupIds.push(existingRegistration.groupId);
|
|
367
370
|
}
|
|
368
371
|
|
|
369
372
|
async function createBalanceItem({ registration, amount, unitPrice, description, type, relations }: { amount?: number; registration: RegistrationWithMemberAndGroup; unitPrice: number; description: string; relations: Map<BalanceItemRelationType, BalanceItemRelation>; type: BalanceItemType }) {
|
|
@@ -584,8 +587,8 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
584
587
|
async function markValidIfNeeded() {
|
|
585
588
|
if (shouldMarkValid) {
|
|
586
589
|
for (const balanceItem of [...createdBalanceItems, ...unrelatedCreatedBalanceItems]) {
|
|
587
|
-
// Mark
|
|
588
|
-
await
|
|
590
|
+
// Mark valid
|
|
591
|
+
await BalanceItemService.markPaid(balanceItem, payment, organization);
|
|
589
592
|
}
|
|
590
593
|
}
|
|
591
594
|
}
|
|
@@ -8,6 +8,8 @@ import { Payment as PaymentStruct, PaymentGeneral, PaymentMethod, PaymentStatus,
|
|
|
8
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
9
9
|
import { Context } from '../../../../helpers/Context';
|
|
10
10
|
import { ExchangePaymentEndpoint } from '../../shared/ExchangePaymentEndpoint';
|
|
11
|
+
import { PaymentService } from '../../../../services/PaymentService';
|
|
12
|
+
import { BalanceItemService } from '../../../../services/BalanceItemService';
|
|
11
13
|
|
|
12
14
|
type Params = Record<string, never>;
|
|
13
15
|
type Query = undefined;
|
|
@@ -143,7 +145,7 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
143
145
|
|
|
144
146
|
// Mark paid or failed
|
|
145
147
|
if (put.status !== PaymentStatus.Created && put.status !== PaymentStatus.Pending) {
|
|
146
|
-
await
|
|
148
|
+
await PaymentService.handlePaymentStatusUpdate(payment, organization, put.status);
|
|
147
149
|
|
|
148
150
|
if (put.status === PaymentStatus.Succeeded) {
|
|
149
151
|
payment.paidAt = put.paidAt;
|
|
@@ -152,7 +154,7 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
152
154
|
}
|
|
153
155
|
else {
|
|
154
156
|
for (const balanceItem of balanceItems) {
|
|
155
|
-
await
|
|
157
|
+
await BalanceItemService.markUpdated(balanceItem, payment, organization);
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
await BalanceItem.updateOutstanding(balanceItems);
|
|
@@ -222,7 +224,7 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
222
224
|
await payment.save();
|
|
223
225
|
|
|
224
226
|
if (patch.status) {
|
|
225
|
-
await
|
|
227
|
+
await PaymentService.handlePaymentStatusUpdate(payment, organization, patch.status);
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
changedPayments.push(
|