@stamhoofd/backend 2.57.1 → 2.59.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 +6 -1
- package/package.json +13 -13
- package/src/audit-logs/EventLogger.ts +30 -0
- package/src/audit-logs/GroupLogger.ts +95 -0
- package/src/audit-logs/MemberLogger.ts +24 -0
- package/src/audit-logs/MemberPlatformMembershipLogger.ts +57 -0
- package/src/audit-logs/MemberResponsibilityRecordLogger.ts +69 -0
- package/src/audit-logs/ModelLogger.ts +218 -0
- package/src/audit-logs/OrderLogger.ts +57 -0
- package/src/audit-logs/OrganizationLogger.ts +26 -0
- package/src/audit-logs/OrganizationRegistrationPeriodLogger.ts +77 -0
- package/src/audit-logs/PaymentLogger.ts +43 -0
- package/src/audit-logs/PlatformLogger.ts +13 -0
- package/src/audit-logs/RegistrationLogger.ts +53 -0
- package/src/audit-logs/RegistrationPeriodLogger.ts +21 -0
- package/src/audit-logs/StripeAccountLogger.ts +47 -0
- package/src/audit-logs/WebshopLogger.ts +35 -0
- package/src/crons.ts +2 -1
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +12 -24
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +4 -18
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +6 -3
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +3 -18
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +0 -15
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +5 -2
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +5 -15
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +18 -28
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +6 -3
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +7 -4
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +8 -2
- package/src/helpers/AuthenticatedStructures.ts +16 -1
- package/src/helpers/Context.ts +8 -2
- package/src/helpers/MemberUserSyncer.ts +45 -40
- package/src/helpers/PeriodHelper.ts +31 -27
- package/src/helpers/TagHelper.ts +23 -20
- package/src/seeds/1722344162-update-membership.ts +2 -2
- package/src/seeds/1726572303-schedule-stock-updates.ts +2 -1
- package/src/services/AuditLogService.ts +89 -216
- package/src/services/BalanceItemPaymentService.ts +1 -1
- package/src/services/BalanceItemService.ts +14 -5
- package/src/services/MemberNumberService.ts +120 -0
- package/src/services/PaymentService.ts +199 -193
- package/src/services/PlatformMembershipService.ts +284 -0
- package/src/services/RegistrationService.ts +76 -27
- package/src/services/explainPatch.ts +528 -316
- package/src/helpers/MembershipHelper.ts +0 -54
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AutoEncoderPatchType, Decoder, isPatchableArray, ObjectData, PatchableArrayAutoEncoder, patchObject } from '@simonbackx/simple-encoding';
|
|
1
|
+
import { AutoEncoderPatchType, cloneObject, Decoder, isPatchableArray, ObjectData, PatchableArrayAutoEncoder, patchObject } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
4
4
|
import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, SetupStepUpdater, StripeAccount, Webshop } from '@stamhoofd/models';
|
|
5
|
-
import {
|
|
5
|
+
import { BuckarooSettings, Company, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PlatformConfig } from '@stamhoofd/structures';
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
@@ -10,7 +10,6 @@ import { BuckarooHelper } from '../../../../helpers/BuckarooHelper';
|
|
|
10
10
|
import { Context } from '../../../../helpers/Context';
|
|
11
11
|
import { TagHelper } from '../../../../helpers/TagHelper';
|
|
12
12
|
import { ViesHelper } from '../../../../helpers/ViesHelper';
|
|
13
|
-
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
14
13
|
|
|
15
14
|
type Params = Record<string, never>;
|
|
16
15
|
type Query = undefined;
|
|
@@ -136,7 +135,7 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
136
135
|
|
|
137
136
|
// Apply payconiq patch
|
|
138
137
|
if (request.body.privateMeta.payconiqAccounts !== undefined) {
|
|
139
|
-
organization.privateMeta.payconiqAccounts = patchObject(organization.privateMeta.payconiqAccounts, request.body.privateMeta.payconiqAccounts);
|
|
138
|
+
organization.privateMeta.payconiqAccounts = patchObject(organization.privateMeta.payconiqAccounts, cloneObject(request.body.privateMeta.payconiqAccounts as any));
|
|
140
139
|
|
|
141
140
|
for (const account of organization.privateMeta.payconiqAccounts) {
|
|
142
141
|
if (account.merchantId === null) {
|
|
@@ -214,8 +213,6 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
214
213
|
await this.validateCompanies(organization, request.body.meta.companies);
|
|
215
214
|
shouldUpdateSetupSteps = true;
|
|
216
215
|
}
|
|
217
|
-
const oldMeta = organization.meta.clone();
|
|
218
|
-
|
|
219
216
|
const savedPackages = organization.meta.packages;
|
|
220
217
|
organization.meta.patchOrPut(request.body.meta);
|
|
221
218
|
organization.meta.packages = savedPackages;
|
|
@@ -283,13 +280,6 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
283
280
|
|
|
284
281
|
updateTags = true;
|
|
285
282
|
}
|
|
286
|
-
|
|
287
|
-
await AuditLogService.log({
|
|
288
|
-
type: AuditLogType.OrganizationSettingsChanged,
|
|
289
|
-
organization,
|
|
290
|
-
oldMeta,
|
|
291
|
-
patch: request.body.meta,
|
|
292
|
-
});
|
|
293
283
|
}
|
|
294
284
|
|
|
295
285
|
if (request.body.active !== undefined) {
|
|
@@ -393,8 +383,8 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
393
383
|
if (updateTags) {
|
|
394
384
|
await TagHelper.updateOrganizations();
|
|
395
385
|
}
|
|
396
|
-
|
|
397
|
-
return new Response(
|
|
386
|
+
const struct = await AuthenticatedStructures.organization(organization);
|
|
387
|
+
return new Response(struct);
|
|
398
388
|
}
|
|
399
389
|
|
|
400
390
|
async validateCompanies(organization: Organization, companies: PatchableArrayAutoEncoder<Company> | Company[]) {
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import {
|
|
2
|
+
import { GroupPrivateSettings, Group as GroupStruct, GroupType, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from '@stamhoofd/structures';
|
|
3
3
|
|
|
4
4
|
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
5
5
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
|
-
import { Group,
|
|
6
|
+
import { Group, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod, SetupStepUpdater } from '@stamhoofd/models';
|
|
7
7
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
8
8
|
import { Context } from '../../../../helpers/Context';
|
|
9
|
-
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
10
9
|
|
|
11
10
|
type Params = Record<string, never>;
|
|
12
11
|
type Query = undefined;
|
|
@@ -81,6 +80,20 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
81
80
|
|
|
82
81
|
let deleteUnreachable = false;
|
|
83
82
|
const allowedIds: string[] = [];
|
|
83
|
+
let wasInvalid = false;
|
|
84
|
+
|
|
85
|
+
// Check if have an initial invalid state
|
|
86
|
+
if (patch.settings) {
|
|
87
|
+
// Already clean up the categories (not yet delete the groups)
|
|
88
|
+
const groups = await Group.getAll(organization.id, organizationPeriod.periodId);
|
|
89
|
+
await organizationPeriod.cleanCategories(groups);
|
|
90
|
+
|
|
91
|
+
for (const category of organizationPeriod.settings.categories) {
|
|
92
|
+
if (category.groupIds.length && category.categoryIds.length) {
|
|
93
|
+
wasInvalid = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
84
97
|
|
|
85
98
|
// #region prevent patch category lock if no full platform access
|
|
86
99
|
const originalCategories = organizationPeriod.settings.categories;
|
|
@@ -90,7 +103,7 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
90
103
|
if (patch.settings.categories) {
|
|
91
104
|
deleteUnreachable = true;
|
|
92
105
|
}
|
|
93
|
-
organizationPeriod.settings.
|
|
106
|
+
organizationPeriod.settings = organizationPeriod.settings.patch(patch.settings);
|
|
94
107
|
}
|
|
95
108
|
}
|
|
96
109
|
else {
|
|
@@ -123,7 +136,7 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
// Check if we have a category with groups and categories combined
|
|
126
|
-
if (patch.settings) {
|
|
139
|
+
if (patch.settings && !wasInvalid) {
|
|
127
140
|
for (const category of organizationPeriod.settings.categories) {
|
|
128
141
|
if (category.groupIds.length && category.categoryIds.length) {
|
|
129
142
|
throw new SimpleError({
|
|
@@ -281,12 +294,6 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
281
294
|
|
|
282
295
|
model.deletedAt = new Date();
|
|
283
296
|
await model.save();
|
|
284
|
-
Member.updateMembershipsForGroupId(id);
|
|
285
|
-
|
|
286
|
-
await AuditLogService.log({
|
|
287
|
-
type: AuditLogType.GroupDeleted,
|
|
288
|
-
group: model,
|
|
289
|
-
});
|
|
290
297
|
}
|
|
291
298
|
|
|
292
299
|
static async patchGroup(struct: AutoEncoderPatchType<GroupStruct>, period?: RegistrationPeriod | null) {
|
|
@@ -295,7 +302,6 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
295
302
|
if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
|
|
296
303
|
throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te wijzigen');
|
|
297
304
|
}
|
|
298
|
-
const originalStruct = (await AuthenticatedStructures.group(model)).clone(); // Clone is required for deep changes
|
|
299
305
|
|
|
300
306
|
const previousProperties = {
|
|
301
307
|
deletedAt: model.deletedAt,
|
|
@@ -420,17 +426,6 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
420
426
|
previousProperties,
|
|
421
427
|
});
|
|
422
428
|
await model.save();
|
|
423
|
-
|
|
424
|
-
if (struct.deletedAt !== undefined || struct.defaultAgeGroupId !== undefined) {
|
|
425
|
-
Member.updateMembershipsForGroupId(model.id);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
await AuditLogService.log({
|
|
429
|
-
type: AuditLogType.GroupEdited,
|
|
430
|
-
group: model,
|
|
431
|
-
oldData: originalStruct,
|
|
432
|
-
patch,
|
|
433
|
-
});
|
|
434
429
|
}
|
|
435
430
|
|
|
436
431
|
static async createGroup(struct: GroupStruct, organizationId: string, period: RegistrationPeriod, options?: { allowedIds?: string[] }): Promise<Group> {
|
|
@@ -512,11 +507,6 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
512
507
|
await model.save();
|
|
513
508
|
await model.updateOccupancy({ isNew: true }); // Force update steps
|
|
514
509
|
|
|
515
|
-
await AuditLogService.log({
|
|
516
|
-
type: AuditLogType.GroupAdded,
|
|
517
|
-
group: model,
|
|
518
|
-
});
|
|
519
|
-
|
|
520
510
|
return model;
|
|
521
511
|
}
|
|
522
512
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
3
3
|
import { StripeAccount } from '@stamhoofd/models';
|
|
4
|
-
import { PermissionLevel, StripeAccount as StripeAccountStruct } from '@stamhoofd/structures';
|
|
4
|
+
import { AuditLogType, PermissionLevel, StripeAccount as StripeAccountStruct } from '@stamhoofd/structures';
|
|
5
5
|
import Stripe from 'stripe';
|
|
6
6
|
|
|
7
7
|
import { Context } from '../../../../helpers/Context';
|
|
8
8
|
import { StripeHelper } from '../../../../helpers/StripeHelper';
|
|
9
|
+
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
9
10
|
type Params = Record<string, never>;
|
|
10
11
|
type Body = undefined;
|
|
11
12
|
type Query = undefined;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { StripeAccount } from '@stamhoofd/models';
|
|
3
|
-
import { PermissionLevel } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogType, PermissionLevel } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Context } from '../../../../helpers/Context';
|
|
6
6
|
import { StripeHelper } from '../../../../helpers/StripeHelper';
|
|
7
7
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
8
|
+
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
8
9
|
|
|
9
10
|
type Params = { id: string };
|
|
10
11
|
type Body = undefined;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { StripeAccount } from '@stamhoofd/models';
|
|
3
|
-
import { PermissionLevel, StripeAccount as StripeAccountStruct } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogSource, PermissionLevel, StripeAccount as StripeAccountStruct } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Context } from '../../../../helpers/Context';
|
|
6
6
|
import { StripeHelper } from '../../../../helpers/StripeHelper';
|
|
7
|
+
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
7
8
|
|
|
8
9
|
type Params = { id: string };
|
|
9
10
|
type Body = undefined;
|
|
@@ -43,9 +44,11 @@ export class UpdateStripeAccountEndpoint extends Endpoint<Params, Query, Body, R
|
|
|
43
44
|
// Get account
|
|
44
45
|
const stripe = StripeHelper.getInstance();
|
|
45
46
|
const account = await stripe.accounts.retrieve(model.accountId);
|
|
46
|
-
model.setMetaFromStripeAccount(account);
|
|
47
|
-
await model.save();
|
|
48
47
|
|
|
48
|
+
await AuditLogService.setContext({ userId: null, source: AuditLogSource.System }, async () => {
|
|
49
|
+
model.setMetaFromStripeAccount(account);
|
|
50
|
+
await model.save();
|
|
51
|
+
});
|
|
49
52
|
return new Response(StripeAccountStruct.create(model));
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -3,9 +3,10 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
4
|
import { BalanceItem, BalanceItemPayment, Order, Payment, Token, Webshop } from '@stamhoofd/models';
|
|
5
5
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
6
|
-
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentMethod, PaymentStatus, PermissionLevel, PrivateOrder, PrivatePayment, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
6
|
+
import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentMethod, PaymentStatus, PermissionLevel, PrivateOrder, PrivatePayment, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
8
|
import { Context } from '../../../../helpers/Context';
|
|
9
|
+
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
9
10
|
|
|
10
11
|
type Params = { id: string };
|
|
11
12
|
type Query = undefined;
|
|
@@ -131,7 +132,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
131
132
|
order.data.validate(webshopGetter.struct, organization.meta, request.i18n, true);
|
|
132
133
|
|
|
133
134
|
try {
|
|
134
|
-
await order.updateStock();
|
|
135
|
+
await order.updateStock(null, true);
|
|
135
136
|
const totalPrice = order.data.totalPrice;
|
|
136
137
|
|
|
137
138
|
if (totalPrice == 0) {
|
|
@@ -286,9 +287,11 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
|
|
290
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
291
|
+
await model.setRelation(Order.webshop, webshop).updateStock(previousData, true);
|
|
292
|
+
await model.setRelation(Order.webshop, webshop).updateTickets();
|
|
293
|
+
});
|
|
289
294
|
await model.save();
|
|
290
|
-
await model.setRelation(Order.webshop, webshop).updateStock(previousData);
|
|
291
|
-
await model.setRelation(Order.webshop, webshop).updateTickets();
|
|
292
295
|
}
|
|
293
296
|
|
|
294
297
|
const mapped = orders.map(order => order.setRelation(Order.webshop, webshop));
|
|
@@ -6,12 +6,13 @@ import { I18n } from '@stamhoofd/backend-i18n';
|
|
|
6
6
|
import { Email } from '@stamhoofd/email';
|
|
7
7
|
import { BalanceItem, BalanceItemPayment, MolliePayment, MollieToken, Order, PayconiqPayment, Payment, RateLimiter, Webshop, WebshopDiscountCode } from '@stamhoofd/models';
|
|
8
8
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
9
|
-
import { BalanceItemStatus, Order as OrderStruct, OrderData, OrderResponse, Payment as PaymentStruct, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Version, Webshop as WebshopStruct, WebshopAuthType, BalanceItemType, BalanceItemRelationType, BalanceItemRelation } from '@stamhoofd/structures';
|
|
9
|
+
import { BalanceItemStatus, Order as OrderStruct, OrderData, OrderResponse, Payment as PaymentStruct, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Version, Webshop as WebshopStruct, WebshopAuthType, BalanceItemType, BalanceItemRelationType, BalanceItemRelation, AuditLogSource } from '@stamhoofd/structures';
|
|
10
10
|
import { Formatter } from '@stamhoofd/utility';
|
|
11
11
|
|
|
12
12
|
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
13
13
|
import { Context } from '../../../helpers/Context';
|
|
14
14
|
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
15
|
+
import { AuditLogService } from '../../../services/AuditLogService';
|
|
15
16
|
|
|
16
17
|
type Params = { id: string };
|
|
17
18
|
type Query = undefined;
|
|
@@ -140,7 +141,12 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
140
141
|
order.userId = Context.user?.id ?? null;
|
|
141
142
|
|
|
142
143
|
// Always reserve the stock
|
|
143
|
-
await
|
|
144
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
145
|
+
await order.updateStock(null, true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await order.save();
|
|
149
|
+
|
|
144
150
|
return { webshop, order, organization };
|
|
145
151
|
});
|
|
146
152
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
2
|
import { AuditLog, BalanceItem, CachedBalance, Document, Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
|
|
3
|
-
import { AuditLog as AuditLogStruct, Document as DocumentStruct, Event as EventStruct, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, Document as DocumentStruct, Event as EventStruct, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { Context } from './Context';
|
|
@@ -645,6 +645,8 @@ export class AuthenticatedStructures {
|
|
|
645
645
|
|
|
646
646
|
const userIds = Formatter.uniqueArray(logs.map(l => l.userId).filter(id => id !== null));
|
|
647
647
|
const users = await User.getByIDs(...userIds);
|
|
648
|
+
const organizationsIds = Formatter.uniqueArray(logs.map(l => l.organizationId).filter(id => id !== null));
|
|
649
|
+
const organizations = await Organization.getByIDs(...organizationsIds);
|
|
648
650
|
|
|
649
651
|
for (const log of logs) {
|
|
650
652
|
const user = log.userId ? (users.find(u => u.id === log.userId) ?? null) : null;
|
|
@@ -673,9 +675,22 @@ export class AuthenticatedStructures {
|
|
|
673
675
|
}
|
|
674
676
|
}
|
|
675
677
|
|
|
678
|
+
let replacements = log.replacements;
|
|
679
|
+
|
|
680
|
+
if (log.organizationId && log.organizationId !== Context.organization?.id) {
|
|
681
|
+
replacements = new Map(log.replacements);
|
|
682
|
+
const org = organizations.find(o => o.id === log.organizationId);
|
|
683
|
+
replacements.set('org', AuditLogReplacement.create({
|
|
684
|
+
id: log.organizationId,
|
|
685
|
+
value: org?.name ?? 'verwijderde vereniging',
|
|
686
|
+
type: AuditLogReplacementType.Organization,
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
|
|
676
690
|
structs.push(
|
|
677
691
|
AuditLogStruct.create({
|
|
678
692
|
...log,
|
|
693
|
+
replacements,
|
|
679
694
|
user: userStruct,
|
|
680
695
|
}),
|
|
681
696
|
);
|
package/src/helpers/Context.ts
CHANGED
|
@@ -46,9 +46,15 @@ export class ContextInstance {
|
|
|
46
46
|
|
|
47
47
|
static asyncLocalStorage = new AsyncLocalStorage<ContextInstance>();
|
|
48
48
|
|
|
49
|
-
static get
|
|
49
|
+
static get optional(): ContextInstance | null {
|
|
50
50
|
const c = this.asyncLocalStorage.getStore();
|
|
51
51
|
|
|
52
|
+
return c ?? null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static get current(): ContextInstance {
|
|
56
|
+
const c = this.optional;
|
|
57
|
+
|
|
52
58
|
if (!c) {
|
|
53
59
|
throw new SimpleError({
|
|
54
60
|
code: 'no_context',
|
|
@@ -220,7 +226,7 @@ export class ContextInstance {
|
|
|
220
226
|
export const Context = new Proxy(ContextInstance, {
|
|
221
227
|
get(target, prop, receiver) {
|
|
222
228
|
const c = target.current[prop];
|
|
223
|
-
if (c && typeof c
|
|
229
|
+
if (c && typeof c === 'function') {
|
|
224
230
|
return c.bind(target.current);
|
|
225
231
|
}
|
|
226
232
|
return c;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Member, MemberResponsibilityRecord, MemberWithRegistrations, User } from '@stamhoofd/models';
|
|
2
2
|
import { SQL } from '@stamhoofd/sql';
|
|
3
|
-
import { MemberDetails, Permissions, UserPermissions } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogSource, MemberDetails, Permissions, UserPermissions } from '@stamhoofd/structures';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import basex from 'base-x';
|
|
6
|
+
import { AuditLogService } from '../services/AuditLogService';
|
|
6
7
|
|
|
7
8
|
const ALPHABET = '123456789ABCDEFGHJKMNPQRSTUVWXYZ'; // Note: we removed 0, O, I and l to make it easier for humans
|
|
8
9
|
const customBase = basex(ALPHABET);
|
|
@@ -26,61 +27,63 @@ export class MemberUserSyncerStatic {
|
|
|
26
27
|
* - email addresses have changed
|
|
27
28
|
*/
|
|
28
29
|
async onChangeMember(member: MemberWithRegistrations, unlinkUsers: boolean = false) {
|
|
29
|
-
|
|
30
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
31
|
+
const { userEmails, parentAndUnverifiedEmails } = this.getMemberAccessEmails(member.details);
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
// Make sure all these users have access to the member
|
|
34
|
+
for (const email of userEmails) {
|
|
33
35
|
// Link users that are found with these email addresses.
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
for (const email of parentAndUnverifiedEmails) {
|
|
38
|
-
if (userEmails.includes(email)) {
|
|
39
|
-
continue;
|
|
36
|
+
await this.linkUser(email, member, false, true);
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
for (const email of parentAndUnverifiedEmails) {
|
|
40
|
+
if (userEmails.includes(email)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Link parents and unverified emails
|
|
45
|
+
// Now we add the responsibility permissions to the parent if there are no userEmails
|
|
46
|
+
const asParent = userEmails.length > 0 || !member.details.unverifiedEmails.includes(email) || member.details.defaultAge < 16;
|
|
47
|
+
await this.linkUser(email, member, asParent, true);
|
|
48
|
+
}
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
if (unlinkUsers && !member.details.parentsHaveAccess) {
|
|
49
51
|
// Remove access of users that are not in this list
|
|
50
52
|
// NOTE: we should only do this once a year (preferably on the birthday of the member)
|
|
51
53
|
// only once because otherwise users loose the access to a member during the creation of the member, or when they have changed their email address
|
|
52
54
|
// users can regain access to a member after they have lost control by using the normal verification flow when detecting duplicate members
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
for (const user of member.users) {
|
|
57
|
+
if (!userEmails.includes(user.email) && !parentAndUnverifiedEmails.includes(user.email)) {
|
|
58
|
+
await this.unlinkUser(user, member);
|
|
59
|
+
}
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
|
-
|
|
60
|
-
else {
|
|
62
|
+
else {
|
|
61
63
|
// Only auto unlink users that do not have an account
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
for (const user of member.users) {
|
|
65
|
+
if (!userEmails.includes(user.email) && !parentAndUnverifiedEmails.includes(user.email)) {
|
|
66
|
+
if (!user.hasAccount()) {
|
|
67
|
+
await this.unlinkUser(user, member);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
68
70
|
// Make sure only linked as a parent, not as user self
|
|
69
71
|
// This makes sure we don't inherit permissions and aren't counted as 'being' the member
|
|
70
|
-
|
|
72
|
+
await this.linkUser(user.email, member, true);
|
|
73
|
+
}
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
|
-
}
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
if (member.details.securityCode === null) {
|
|
79
|
+
console.log('Generating security code for member ' + member.id);
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const length = 16;
|
|
82
|
+
const code = customBase.encode(await randomBytes(100)).toUpperCase().substring(0, length);
|
|
83
|
+
member.details.securityCode = code;
|
|
84
|
+
await member.save();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
getMemberAccessEmails(details: MemberDetails) {
|
|
@@ -106,10 +109,12 @@ export class MemberUserSyncerStatic {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
async onDeleteMember(member: MemberWithRegistrations) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
113
|
+
for (const u of member.users) {
|
|
114
|
+
console.log('Unlinking user ' + u.email + ' from deleted member ' + member.id);
|
|
115
|
+
await this.unlinkUser(u, member);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
async getResponsibilitiesForMembers(memberIds: string[]) {
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
2
|
import { Group, Member, MemberResponsibilityRecord, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod, SetupStepUpdater } from '@stamhoofd/models';
|
|
3
3
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
4
|
-
import { Group as GroupStruct, PermissionLevel } from '@stamhoofd/structures';
|
|
4
|
+
import { AuditLogSource, Group as GroupStruct, PermissionLevel } from '@stamhoofd/structures';
|
|
5
5
|
import { PatchOrganizationRegistrationPeriodsEndpoint } from '../endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint';
|
|
6
6
|
import { AuthenticatedStructures } from './AuthenticatedStructures';
|
|
7
7
|
import { MemberUserSyncer } from './MemberUserSyncer';
|
|
8
|
+
import { AuditLogService } from '../services/AuditLogService';
|
|
8
9
|
|
|
9
10
|
export class PeriodHelper {
|
|
10
11
|
static async moveOrganizationToPeriod(organization: Organization, period: RegistrationPeriod) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
13
|
+
console.log('moveOrganizationToPeriod', organization.id, period.id);
|
|
14
|
+
await this.createOrganizationPeriodForPeriod(organization, period);
|
|
15
|
+
organization.periodId = period.id;
|
|
16
|
+
await organization.save();
|
|
17
|
+
});
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
static async stopAllResponsibilities() {
|
|
@@ -169,29 +171,31 @@ export class PeriodHelper {
|
|
|
169
171
|
|
|
170
172
|
const batchSize = 100;
|
|
171
173
|
await QueueHandler.schedule(tag, async () => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
174
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
175
|
+
let lastId = '';
|
|
176
|
+
|
|
177
|
+
while (true) {
|
|
178
|
+
const groups = await Group.where(
|
|
179
|
+
{
|
|
180
|
+
id: { sign: '>', value: lastId },
|
|
181
|
+
periodId: period.id,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
limit: batchSize,
|
|
185
|
+
sort: ['id'],
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
for (const group of groups) {
|
|
190
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(GroupStruct.patch({ id: group.id }), period);
|
|
191
|
+
lastId = group.id;
|
|
192
|
+
}
|
|
190
193
|
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
if (groups.length < batchSize) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
193
197
|
}
|
|
194
|
-
}
|
|
198
|
+
});
|
|
195
199
|
});
|
|
196
200
|
}
|
|
197
201
|
}
|
package/src/helpers/TagHelper.ts
CHANGED
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
import { Organization, Platform } from '@stamhoofd/models';
|
|
2
2
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
3
|
-
import { OrganizationTag, TagHelper as SharedTagHelper } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogSource, OrganizationTag, TagHelper as SharedTagHelper } from '@stamhoofd/structures';
|
|
4
4
|
import { ModelHelper } from './ModelHelper';
|
|
5
|
+
import { AuditLogService } from '../services/AuditLogService';
|
|
5
6
|
|
|
6
7
|
export class TagHelper extends SharedTagHelper {
|
|
7
8
|
static async updateOrganizations() {
|
|
8
9
|
const queueId = 'update-tags-on-organizations';
|
|
9
10
|
QueueHandler.cancel(queueId);
|
|
10
11
|
|
|
11
|
-
await
|
|
12
|
-
|
|
12
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
13
|
+
await QueueHandler.schedule(queueId, async () => {
|
|
14
|
+
let platform = await Platform.getShared();
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const tagCounts = new Map<string, number>();
|
|
17
|
+
await this.loopOrganizations(async (organizations) => {
|
|
18
|
+
for (const organization of organizations) {
|
|
19
|
+
organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, platform.config.tags);
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
for (const tag of organization.meta.tags) {
|
|
22
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
23
|
+
}
|
|
21
24
|
}
|
|
22
|
-
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
await Promise.all(organizations.map(organization => organization.save()));
|
|
27
|
+
});
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
// Reload platform to avoid race conditions
|
|
30
|
+
platform = await Platform.getShared();
|
|
31
|
+
for (const [tag, count] of tagCounts.entries()) {
|
|
32
|
+
const tagObject = platform.config.tags.find(t => t.id === tag);
|
|
33
|
+
if (tagObject) {
|
|
34
|
+
tagObject.organizationCount = count;
|
|
35
|
+
}
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
await platform.save();
|
|
38
|
+
});
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
-
import {
|
|
2
|
+
import { PlatformMembershipService } from '../services/PlatformMembershipService';
|
|
3
3
|
|
|
4
4
|
export default new Migration(async () => {
|
|
5
5
|
if (STAMHOOFD.environment == 'test') {
|
|
@@ -13,5 +13,5 @@ export default new Migration(async () => {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
process.stdout.write('\n');
|
|
16
|
-
await
|
|
16
|
+
await PlatformMembershipService.updateAll();
|
|
17
17
|
});
|