@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.
Files changed (48) hide show
  1. package/index.ts +6 -1
  2. package/package.json +12 -12
  3. package/src/audit-logs/EventLogger.ts +30 -0
  4. package/src/audit-logs/GroupLogger.ts +95 -0
  5. package/src/audit-logs/MemberLogger.ts +24 -0
  6. package/src/audit-logs/MemberPlatformMembershipLogger.ts +57 -0
  7. package/src/audit-logs/MemberResponsibilityRecordLogger.ts +69 -0
  8. package/src/audit-logs/ModelLogger.ts +218 -0
  9. package/src/audit-logs/OrderLogger.ts +57 -0
  10. package/src/audit-logs/OrganizationLogger.ts +26 -0
  11. package/src/audit-logs/OrganizationRegistrationPeriodLogger.ts +77 -0
  12. package/src/audit-logs/PaymentLogger.ts +43 -0
  13. package/src/audit-logs/PlatformLogger.ts +13 -0
  14. package/src/audit-logs/RegistrationLogger.ts +53 -0
  15. package/src/audit-logs/RegistrationPeriodLogger.ts +21 -0
  16. package/src/audit-logs/StripeAccountLogger.ts +47 -0
  17. package/src/audit-logs/WebshopLogger.ts +35 -0
  18. package/src/crons.ts +2 -1
  19. package/src/endpoints/global/events/PatchEventsEndpoint.ts +12 -24
  20. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +4 -18
  21. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +5 -13
  22. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +3 -11
  23. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +0 -15
  24. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +5 -2
  25. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +0 -19
  26. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -12
  27. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +18 -33
  28. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +0 -6
  29. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +0 -6
  30. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +5 -14
  31. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +7 -4
  32. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +8 -2
  33. package/src/helpers/AuthenticatedStructures.ts +16 -1
  34. package/src/helpers/Context.ts +8 -2
  35. package/src/helpers/MemberUserSyncer.ts +45 -40
  36. package/src/helpers/PeriodHelper.ts +3 -4
  37. package/src/helpers/TagHelper.ts +23 -20
  38. package/src/seeds/1722344162-update-membership.ts +2 -2
  39. package/src/seeds/1726572303-schedule-stock-updates.ts +2 -1
  40. package/src/services/AuditLogService.ts +83 -295
  41. package/src/services/BalanceItemPaymentService.ts +1 -1
  42. package/src/services/BalanceItemService.ts +14 -5
  43. package/src/services/MemberNumberService.ts +120 -0
  44. package/src/services/PaymentService.ts +199 -193
  45. package/src/services/PlatformMembershipService.ts +284 -0
  46. package/src/services/RegistrationService.ts +76 -27
  47. package/src/services/explainPatch.ts +110 -41
  48. 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 { AuditLogType, BuckarooSettings, Company, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PlatformConfig } from '@stamhoofd/structures';
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 { AuditLogType, GroupPrivateSettings, Group as GroupStruct, GroupType, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from '@stamhoofd/structures';
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, Member, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod, SetupStepUpdater } from '@stamhoofd/models';
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.patchOrPut(patch.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 { AuditLogType, 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
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 order.updateStock();
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
  );
@@ -46,9 +46,15 @@ export class ContextInstance {
46
46
 
47
47
  static asyncLocalStorage = new AsyncLocalStorage<ContextInstance>();
48
48
 
49
- static get current(): ContextInstance {
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 == 'function') {
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
- const { userEmails, parentAndUnverifiedEmails } = this.getMemberAccessEmails(member.details);
30
+ await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
31
+ const { userEmails, parentAndUnverifiedEmails } = this.getMemberAccessEmails(member.details);
30
32
 
31
- // Make sure all these users have access to the member
32
- for (const email of userEmails) {
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
- await this.linkUser(email, member, false, true);
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
- // Link parents and unverified emails
43
- // Now we add the responsibility permissions to the parent if there are no userEmails
44
- const asParent = userEmails.length > 0 || !member.details.unverifiedEmails.includes(email) || member.details.defaultAge < 16;
45
- await this.linkUser(email, member, asParent, true);
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
- if (unlinkUsers && !member.details.parentsHaveAccess) {
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
- for (const user of member.users) {
55
- if (!userEmails.includes(user.email) && !parentAndUnverifiedEmails.includes(user.email)) {
56
- await this.unlinkUser(user, member);
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
- for (const user of member.users) {
63
- if (!userEmails.includes(user.email) && !parentAndUnverifiedEmails.includes(user.email)) {
64
- if (!user.hasAccount()) {
65
- await this.unlinkUser(user, member);
66
- }
67
- else {
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
- await this.linkUser(user.email, member, true);
72
+ await this.linkUser(user.email, member, true);
73
+ }
71
74
  }
72
75
  }
73
76
  }
74
- }
75
77
 
76
- if (member.details.securityCode === null) {
77
- console.log('Generating security code for member ' + member.id);
78
+ if (member.details.securityCode === null) {
79
+ console.log('Generating security code for member ' + member.id);
78
80
 
79
- const length = 16;
80
- const code = customBase.encode(await randomBytes(100)).toUpperCase().substring(0, length);
81
- member.details.securityCode = code;
82
- await member.save();
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
- for (const u of member.users) {
110
- console.log('Unlinking user ' + u.email + ' from deleted member ' + member.id);
111
- await this.unlinkUser(u, member);
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.disable(async () => {
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.disable(async () => {
174
+ await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
176
175
  let lastId = '';
177
176
 
178
177
  while (true) {
@@ -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 QueueHandler.schedule(queueId, async () => {
12
- let platform = await Platform.getShared();
12
+ await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
13
+ await QueueHandler.schedule(queueId, async () => {
14
+ let platform = await Platform.getShared();
13
15
 
14
- const tagCounts = new Map<string, number>();
15
- await this.loopOrganizations(async (organizations) => {
16
- for (const organization of organizations) {
17
- organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, platform.config.tags);
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
- for (const tag of organization.meta.tags) {
20
- tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
21
+ for (const tag of organization.meta.tags) {
22
+ tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
23
+ }
21
24
  }
22
- }
23
25
 
24
- await Promise.all(organizations.map(organization => organization.save()));
25
- });
26
+ await Promise.all(organizations.map(organization => organization.save()));
27
+ });
26
28
 
27
- // Reload platform to avoid race conditions
28
- platform = await Platform.getShared();
29
- for (const [tag, count] of tagCounts.entries()) {
30
- const tagObject = platform.config.tags.find(t => t.id === tag);
31
- if (tagObject) {
32
- tagObject.organizationCount = count;
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
- await platform.save();
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 { MembershipHelper } from '../helpers/MembershipHelper';
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 MembershipHelper.updateAll();
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
- registration.scheduleStockUpdate();
31
+ RegistrationService.scheduleStockUpdate(registration.id);
31
32
 
32
33
  c++;
33
34