@stamhoofd/backend 2.58.0 → 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 +12 -12
- 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 +5 -13
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +3 -11
- 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 +0 -19
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -12
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +18 -33
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +0 -6
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +0 -6
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +5 -14
- 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 +3 -4
- 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 +83 -295
- 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 +110 -41
- package/src/helpers/MembershipHelper.ts +0 -54
|
@@ -2,7 +2,7 @@ import { AutoEncoderPatchType, cloneObject, Decoder, isPatchableArray, ObjectDat
|
|
|
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;
|
|
@@ -63,8 +62,6 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
63
62
|
});
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
const initialStruct = (await AuthenticatedStructures.organization(organization)).clone();
|
|
67
|
-
|
|
68
65
|
const errors = new SimpleErrors();
|
|
69
66
|
let shouldUpdateSetupSteps = false;
|
|
70
67
|
let updateTags = false;
|
|
@@ -387,14 +384,6 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
387
384
|
await TagHelper.updateOrganizations();
|
|
388
385
|
}
|
|
389
386
|
const struct = await AuthenticatedStructures.organization(organization);
|
|
390
|
-
|
|
391
|
-
await AuditLogService.log({
|
|
392
|
-
type: AuditLogType.OrganizationSettingsChanged,
|
|
393
|
-
organization,
|
|
394
|
-
oldData: initialStruct,
|
|
395
|
-
patch: struct,
|
|
396
|
-
});
|
|
397
|
-
|
|
398
387
|
return new Response(struct);
|
|
399
388
|
}
|
|
400
389
|
|
|
@@ -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,22 +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
|
-
if (Object.keys(struct).length === 1 && struct.id) {
|
|
429
|
-
// Nothing changed
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
await AuditLogService.log({
|
|
434
|
-
type: AuditLogType.GroupEdited,
|
|
435
|
-
group: model,
|
|
436
|
-
oldData: originalStruct,
|
|
437
|
-
patch,
|
|
438
|
-
});
|
|
439
429
|
}
|
|
440
430
|
|
|
441
431
|
static async createGroup(struct: GroupStruct, organizationId: string, period: RegistrationPeriod, options?: { allowedIds?: string[] }): Promise<Group> {
|
|
@@ -517,11 +507,6 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
517
507
|
await model.save();
|
|
518
508
|
await model.updateOccupancy({ isNew: true }); // Force update steps
|
|
519
509
|
|
|
520
|
-
await AuditLogService.log({
|
|
521
|
-
type: AuditLogType.GroupAdded,
|
|
522
|
-
group: model,
|
|
523
|
-
});
|
|
524
|
-
|
|
525
510
|
return model;
|
|
526
511
|
}
|
|
527
512
|
}
|
|
@@ -92,12 +92,6 @@ export class ConnectMollieEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
92
92
|
model.setMetaFromStripeAccount(account);
|
|
93
93
|
await model.save();
|
|
94
94
|
|
|
95
|
-
// Track audit log
|
|
96
|
-
await AuditLogService.log({
|
|
97
|
-
type: AuditLogType.StripeAccountAdded,
|
|
98
|
-
stripeAccount: model,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
95
|
// Return information about the Stripe Account
|
|
102
96
|
|
|
103
97
|
return new Response(StripeAccountStruct.create(model));
|
|
@@ -64,12 +64,6 @@ export class DeleteStripeAccountEndpoint extends Endpoint<Params, Query, Body, R
|
|
|
64
64
|
model.status = 'deleted';
|
|
65
65
|
await model.save();
|
|
66
66
|
|
|
67
|
-
// Track audit log
|
|
68
|
-
await AuditLogService.log({
|
|
69
|
-
type: AuditLogType.StripeAccountDeleted,
|
|
70
|
-
stripeAccount: model,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
67
|
return new Response(undefined);
|
|
74
68
|
}
|
|
75
69
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { StripeAccount } from '@stamhoofd/models';
|
|
3
|
-
import {
|
|
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
7
|
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
8
|
-
import { Model } from '@simonbackx/simple-database';
|
|
9
8
|
|
|
10
9
|
type Params = { id: string };
|
|
11
10
|
type Body = undefined;
|
|
@@ -45,19 +44,11 @@ export class UpdateStripeAccountEndpoint extends Endpoint<Params, Query, Body, R
|
|
|
45
44
|
// Get account
|
|
46
45
|
const stripe = StripeHelper.getInstance();
|
|
47
46
|
const account = await stripe.accounts.retrieve(model.accountId);
|
|
48
|
-
const beforeMeta = model.meta;
|
|
49
|
-
model.setMetaFromStripeAccount(account);
|
|
50
|
-
|
|
51
|
-
if (await model.save()) {
|
|
52
|
-
// Track audit log
|
|
53
|
-
await AuditLogService.log({
|
|
54
|
-
type: AuditLogType.StripeAccountEdited,
|
|
55
|
-
stripeAccount: model,
|
|
56
|
-
oldData: beforeMeta,
|
|
57
|
-
patch: model.meta,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
47
|
|
|
48
|
+
await AuditLogService.setContext({ userId: null, source: AuditLogSource.System }, async () => {
|
|
49
|
+
model.setMetaFromStripeAccount(account);
|
|
50
|
+
await model.save();
|
|
51
|
+
});
|
|
61
52
|
return new Response(StripeAccountStruct.create(model));
|
|
62
53
|
}
|
|
63
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,7 +1,7 @@
|
|
|
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';
|
|
@@ -9,9 +9,8 @@ import { AuditLogService } from '../services/AuditLogService';
|
|
|
9
9
|
|
|
10
10
|
export class PeriodHelper {
|
|
11
11
|
static async moveOrganizationToPeriod(organization: Organization, period: RegistrationPeriod) {
|
|
12
|
-
await AuditLogService.
|
|
12
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
13
13
|
console.log('moveOrganizationToPeriod', organization.id, period.id);
|
|
14
|
-
|
|
15
14
|
await this.createOrganizationPeriodForPeriod(organization, period);
|
|
16
15
|
organization.periodId = period.id;
|
|
17
16
|
await organization.save();
|
|
@@ -172,7 +171,7 @@ export class PeriodHelper {
|
|
|
172
171
|
|
|
173
172
|
const batchSize = 100;
|
|
174
173
|
await QueueHandler.schedule(tag, async () => {
|
|
175
|
-
await AuditLogService.
|
|
174
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
176
175
|
let lastId = '';
|
|
177
176
|
|
|
178
177
|
while (true) {
|
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
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Migration } from '@simonbackx/simple-database';
|
|
2
2
|
import { Registration } from '@stamhoofd/models';
|
|
3
|
+
import { RegistrationService } from '../services/RegistrationService';
|
|
3
4
|
|
|
4
5
|
export default new Migration(async () => {
|
|
5
6
|
if (STAMHOOFD.environment == 'test') {
|
|
@@ -27,7 +28,7 @@ export default new Migration(async () => {
|
|
|
27
28
|
const registrations = await Registration.getByIDs(...rawRegistrations.map(g => g.id));
|
|
28
29
|
|
|
29
30
|
for (const registration of registrations) {
|
|
30
|
-
|
|
31
|
+
RegistrationService.scheduleStockUpdate(registration.id);
|
|
31
32
|
|
|
32
33
|
c++;
|
|
33
34
|
|