@stamhoofd/backend 2.115.0 → 2.116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/package.json +10 -10
  2. package/src/audit-logs/ModelLogger.ts +2 -3
  3. package/src/boot.ts +4 -4
  4. package/src/crons/delete-archived-data.ts +47 -0
  5. package/src/crons/index.ts +1 -0
  6. package/src/debug.ts +230 -0
  7. package/src/email-recipient-loaders/documents.ts +1 -1
  8. package/src/email-recipient-loaders/payments.ts +1 -1
  9. package/src/endpoints/auth/PatchUserEndpoint.ts +4 -4
  10. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +4 -4
  11. package/src/endpoints/global/members/GetMembersEndpoint.ts +4 -16
  12. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +8 -7
  13. package/src/endpoints/global/registration/GetUserDetailedPayableBalanceEndpoint.ts +2 -2
  14. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +2 -2
  15. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +2 -2
  16. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -7
  17. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +2 -2
  18. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +7 -4
  19. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +1 -1
  20. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +12 -12
  21. package/src/helpers/AdminPermissionChecker.ts +20 -17
  22. package/src/helpers/AuthenticatedStructures.ts +177 -107
  23. package/src/helpers/Context.ts +2 -0
  24. package/src/helpers/PeriodHelper.ts +5 -45
  25. package/src/helpers/SetupStepUpdater.ts +5 -4
  26. package/src/helpers/outstandingBalanceJoin.ts +3 -1
  27. package/src/services/PaymentService.ts +12 -0
  28. package/src/services/PlatformMembershipService.ts +3 -3
  29. package/src/sql-filters/members.ts +1 -1
  30. package/src/sql-sorters/members.ts +2 -2
  31. package/tests/e2e/register.test.ts +10 -10
  32. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +0 -67
@@ -4,7 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Document, DocumentTemplate, Member, Registration } from '@stamhoofd/models';
5
5
  import { DocumentStatus, Document as DocumentStruct, PermissionLevel } from '@stamhoofd/structures';
6
6
 
7
- import { Context } from '../../../../helpers/Context';
7
+ import { Context } from '../../../../helpers/Context.js';
8
8
 
9
9
  type Params = Record<string, never>;
10
10
  type Query = undefined;
@@ -104,7 +104,7 @@ export class PatchDocumentEndpoint extends Endpoint<Params, Query, Body, Respons
104
104
  put.memberId = registration.memberId;
105
105
  }
106
106
  if (put.memberId) {
107
- const member = await Member.getWithRegistrations(put.memberId);
107
+ const member = await Member.getByIdWithUsersAndRegistrations(put.memberId);
108
108
  if (!member || !await Context.auth.canAccessMember(member, PermissionLevel.Read)) {
109
109
  throw new SimpleError({
110
110
  code: 'not_found',
@@ -4,7 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { EmailAddress } from '@stamhoofd/email';
5
5
  import { EmailInformation } from '@stamhoofd/structures';
6
6
 
7
- import { Context } from '../../../../helpers/Context';
7
+ import { Context } from '../../../../helpers/Context.js';
8
8
 
9
9
  type Params = Record<string, never>;
10
10
  type Query = undefined;
@@ -28,10 +28,13 @@ export class CheckEmailBouncesEndpoint extends Endpoint<Params, Query, Body, Res
28
28
  }
29
29
 
30
30
  async handle(request: DecodedRequest<Params, Query, Body>) {
31
- const organization = await Context.setOrganizationScope();
31
+ const organization = await Context.setOptionalOrganizationScope();
32
32
  await Context.authenticate();
33
33
 
34
- if (!await Context.auth.canAccessEmailBounces(organization.id)) {
34
+ // null if platform
35
+ const organizationId: string | null = organization ? organization.id : null;
36
+
37
+ if (!await Context.auth.canAccessEmailBounces(organizationId)) {
35
38
  throw Context.auth.error();
36
39
  }
37
40
 
@@ -44,7 +47,7 @@ export class CheckEmailBouncesEndpoint extends Endpoint<Params, Query, Body, Res
44
47
  });
45
48
  }
46
49
 
47
- const emails = await EmailAddress.getByEmails(request.body, organization.id);
50
+ const emails = await EmailAddress.getByEmails(request.body, organizationId);
48
51
  return new Response(emails.map(e => EmailInformation.create(e)));
49
52
  }
50
53
  }
@@ -242,7 +242,7 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
242
242
  }
243
243
 
244
244
  async validateMemberId(memberId: string) {
245
- const member = (await Member.getWithRegistrations(memberId));
245
+ const member = (await Member.getByIdWithUsersAndRegistrations(memberId));
246
246
 
247
247
  if (!member || !(await Context.auth.canLinkBalanceItemToMember(member))) {
248
248
  throw new SimpleError({
@@ -2,10 +2,10 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
2
2
  import { DetailedReceivableBalance, PaymentStatus, ReceivableBalanceType } from '@stamhoofd/structures';
3
3
 
4
4
  import { BalanceItem, BalanceItemPayment, CachedBalance, MemberUser, Payment } from '@stamhoofd/models';
5
- import { Context } from '../../../../helpers/Context';
6
- import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
5
+ import { Context } from '../../../../helpers/Context.js';
6
+ import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
7
7
  import { SQL } from '@stamhoofd/sql';
8
- import { BalanceItemService } from '../../../../services/BalanceItemService';
8
+ import { BalanceItemService } from '../../../../services/BalanceItemService.js';
9
9
  import { Formatter } from '@stamhoofd/utility';
10
10
 
11
11
  type Params = { id: string; type: ReceivableBalanceType };
@@ -44,7 +44,7 @@ export class GetReceivableBalanceEndpoint extends Endpoint<Params, Query, Body,
44
44
  switch (request.params.type) {
45
45
  case ReceivableBalanceType.organization: {
46
46
  // Force cache updates, because sometimes the cache could be out of date
47
- BalanceItemService.scheduleOrganizationUpdate(organization.id, request.params.id);
47
+ // BalanceItemService.scheduleOrganizationUpdate(organization.id, request.params.id);
48
48
 
49
49
  paymentModels = await Payment.select()
50
50
  .where('organizationId', organization.id)
@@ -67,7 +67,7 @@ export class GetReceivableBalanceEndpoint extends Endpoint<Params, Query, Body,
67
67
 
68
68
  case ReceivableBalanceType.member: {
69
69
  // Force cache updates, because sometimes the cache could be out of date
70
- BalanceItemService.scheduleMemberUpdate(organization.id, request.params.id);
70
+ // BalanceItemService.scheduleMemberUpdate(organization.id, request.params.id);
71
71
 
72
72
  paymentModels = await Payment.select()
73
73
  .where('organizationId', organization.id)
@@ -93,11 +93,11 @@ export class GetReceivableBalanceEndpoint extends Endpoint<Params, Query, Body,
93
93
  const memberIds = Formatter.uniqueArray(memberUsers.map(mu => mu.membersId));
94
94
 
95
95
  // Force cache updates, because sometimes the cache could be out of date
96
- BalanceItemService.scheduleUserUpdate(organization.id, request.params.id);
97
-
98
- for (const memberId of memberIds) {
99
- BalanceItemService.scheduleMemberUpdate(organization.id, memberId);
100
- }
96
+ // BalanceItemService.scheduleUserUpdate(organization.id, request.params.id);
97
+ //
98
+ // for (const memberId of memberIds) {
99
+ // BalanceItemService.scheduleMemberUpdate(organization.id, memberId);
100
+ // }
101
101
 
102
102
  const q = Payment.select()
103
103
  .where('organizationId', organization.id)
@@ -131,7 +131,7 @@ export class GetReceivableBalanceEndpoint extends Endpoint<Params, Query, Body,
131
131
 
132
132
  case ReceivableBalanceType.userWithoutMembers: {
133
133
  // Force cache updates, because sometimes the cache could be out of date
134
- BalanceItemService.scheduleUserUpdate(organization.id, request.params.id);
134
+ // BalanceItemService.scheduleUserUpdate(organization.id, request.params.id);
135
135
 
136
136
  const q = Payment.select()
137
137
  .where('organizationId', organization.id)
@@ -176,7 +176,7 @@ export class GetReceivableBalanceEndpoint extends Endpoint<Params, Query, Body,
176
176
  }
177
177
 
178
178
  // Flush caches (this makes sure that we do a reload in the frontend after a registration or change, we get the newest balances)
179
- await BalanceItemService.flushCaches(organization.id);
179
+ // await BalanceItemService.flushCaches(organization.id);
180
180
  const balanceItemModels = await CachedBalance.balanceForObjects(organization.id, [request.params.id], request.params.type);
181
181
  const balanceItems = await BalanceItem.getStructureWithPayments(balanceItemModels);
182
182
  const payments = await AuthenticatedStructures.paymentsGeneral(paymentModels, false);
@@ -1,6 +1,6 @@
1
1
  import { AutoEncoderPatchType, PatchMap } from '@simonbackx/simple-encoding';
2
2
  import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
3
- import { BalanceItem, CachedBalance, Document, Email, EmailTemplate, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from '@stamhoofd/models';
3
+ import { BalanceItem, CachedBalance, Document, Email, EmailTemplate, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberWithRegistrations, MemberWithUsers, MemberWithUsersAndRegistrations, MemberWithUsersRegistrationsAndGroups, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from '@stamhoofd/models';
4
4
  import { AccessRight, EmailTemplate as EmailTemplateStruct, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, GroupType, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, ReceivableBalanceType, RecordSettings, ResourcePermissions, UitpasNumberDetails, UitpasSocialTariff, UitpasSocialTariffStatus } from '@stamhoofd/structures';
5
5
  import { Formatter } from '@stamhoofd/utility';
6
6
  import { MemberRecordStore } from '../services/MemberRecordStore.js';
@@ -16,7 +16,7 @@ export class AdminPermissionChecker {
16
16
  /**
17
17
  * The member that is linked to this user = is this user
18
18
  */
19
- member: MemberWithRegistrations | null = null;
19
+ member: MemberWithUsersRegistrationsAndGroups | null = null;
20
20
  platform: PlatformStruct;
21
21
 
22
22
  organizationCache: Map<string, Organization | Promise<Organization | undefined>> = new Map();
@@ -361,7 +361,7 @@ export class AdminPermissionChecker {
361
361
  return await this.hasFullAccess(organizationId);
362
362
  }
363
363
 
364
- async canAccessMember(member: MemberWithRegistrations, permissionLevel: PermissionLevel = PermissionLevel.Read) {
364
+ async canAccessMember(member: MemberWithUsersAndRegistrations, permissionLevel: PermissionLevel = PermissionLevel.Read) {
365
365
  if (permissionLevel !== PermissionLevel.Full && this.isUserManager(member)) {
366
366
  return true;
367
367
  }
@@ -407,7 +407,7 @@ export class AdminPermissionChecker {
407
407
  * The server will temporarily grant the user access to this member, and store this in the server
408
408
  * memory. This is required for adding new members to an organization (first add member -> then patch with registrations, which requires write access).
409
409
  */
410
- async temporarilyGrantMemberAccess(member: MemberWithRegistrations, permissionLevel: PermissionLevel = PermissionLevel.Write) {
410
+ async temporarilyGrantMemberAccess(member: MemberWithUsersRegistrationsAndGroups, permissionLevel: PermissionLevel = PermissionLevel.Write) {
411
411
  console.log('Temporarily granting access to member', member.id, 'for user', this.user.id);
412
412
  addTemporaryMemberAccess(this.user.id, member.id, permissionLevel);
413
413
  }
@@ -415,11 +415,11 @@ export class AdminPermissionChecker {
415
415
  /**
416
416
  * Only full admins can delete members permanently
417
417
  */
418
- async canDeleteMember(member: MemberWithRegistrations) {
418
+ async canDeleteMember(member: MemberWithUsersAndRegistrations) {
419
419
  if (member.registrations.length === 0 && this.isUserManager(member)) {
420
420
  const cachedBalance = await CachedBalance.getForObjects([member.id], null, ReceivableBalanceType.member);
421
421
  if (cachedBalance.length === 0 || (cachedBalance[0].amountOpen === 0 && cachedBalance[0].amountPending === 0)) {
422
- const platformMemberships = await MemberPlatformMembership.where({ memberId: member.id });
422
+ const platformMemberships = await MemberPlatformMembership.select().where('memberId', member.id).fetch();
423
423
  if (platformMemberships.length === 0) {
424
424
  return true;
425
425
  }
@@ -436,7 +436,7 @@ export class AdminPermissionChecker {
436
436
  /**
437
437
  * Note: only checks admin permissions. Users that 'own' this member can also access it but that does not use the AdminPermissionChecker
438
438
  */
439
- async canAccessRegistration(registration: Registration, permissionLevel: PermissionLevel = PermissionLevel.Read, checkMember: boolean | MemberWithRegistrations = true) {
439
+ async canAccessRegistration(registration: Registration, permissionLevel: PermissionLevel = PermissionLevel.Read, checkMember: boolean | MemberWithUsersRegistrationsAndGroups = true) {
440
440
  if (registration.deactivatedAt || !registration.registeredAt) {
441
441
  if (!checkMember) {
442
442
  // We can't grant access to a member because of a deactivated registration
@@ -840,7 +840,7 @@ export class AdminPermissionChecker {
840
840
  return false;
841
841
  }
842
842
 
843
- async canLinkBalanceItemToMember(member: MemberWithRegistrations) {
843
+ async canLinkBalanceItemToMember(member: MemberWithUsersAndRegistrations) {
844
844
  if (!this.checkScope(member.organizationId)) {
845
845
  return false;
846
846
  }
@@ -928,8 +928,11 @@ export class AdminPermissionChecker {
928
928
  return this.hasFullAccess(organizationId);
929
929
  }
930
930
 
931
- async canAccessEmailBounces(organizationId: string) {
932
- return this.hasSomeAccess(organizationId);
931
+ async canAccessEmailBounces(organizationId: string | null) {
932
+ if (organizationId) {
933
+ return this.hasSomeAccess(organizationId);
934
+ }
935
+ return this.hasPlatformFullAccess();
933
936
  }
934
937
 
935
938
  async canSendEmails(organizationId: Organization | string | null) {
@@ -1105,14 +1108,14 @@ export class AdminPermissionChecker {
1105
1108
  return !!organizationPermissions && organizationPermissions.hasAccess(level);
1106
1109
  }
1107
1110
 
1108
- isUserManager(member: MemberWithRegistrations) {
1111
+ isUserManager(member: MemberWithUsers) {
1109
1112
  return !!member.users.find(u => u.id === this.user.id);
1110
1113
  }
1111
1114
 
1112
1115
  /**
1113
1116
  * Return a list of RecordSettings the current user can view or edit
1114
1117
  */
1115
- async hasFinancialMemberAccess(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read, organizationId?: string): Promise<boolean> {
1118
+ async hasFinancialMemberAccess(member: MemberWithUsersAndRegistrations, level: PermissionLevel = PermissionLevel.Read, organizationId?: string): Promise<boolean> {
1116
1119
  const isUserManager = this.isUserManager(member);
1117
1120
 
1118
1121
  if (isUserManager && level === PermissionLevel.Read) {
@@ -1197,7 +1200,7 @@ export class AdminPermissionChecker {
1197
1200
  /**
1198
1201
  * Return a list of RecordSettings the current user can view or edit
1199
1202
  */
1200
- async hasNRNAccess(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<boolean> {
1203
+ async hasNRNAccess(member: MemberWithUsersAndRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<boolean> {
1201
1204
  const isUserManager = this.isUserManager(member);
1202
1205
 
1203
1206
  if (isUserManager) {
@@ -1319,7 +1322,7 @@ export class AdminPermissionChecker {
1319
1322
  };
1320
1323
  }
1321
1324
 
1322
- async checkRecordAccess(member: MemberWithRegistrations, recordId: string, level: PermissionLevel = PermissionLevel.Read): Promise<{ canAccess: false; record: RecordSettings | null } | { canAccess: true; record: RecordSettings }> {
1325
+ async checkRecordAccess(member: MemberWithUsersRegistrationsAndGroups, recordId: string, level: PermissionLevel = PermissionLevel.Read): Promise<{ canAccess: false; record: RecordSettings | null } | { canAccess: true; record: RecordSettings }> {
1323
1326
  const record = await MemberRecordStore.getRecord(recordId);
1324
1327
  if (!record) {
1325
1328
  return {
@@ -1452,7 +1455,7 @@ export class AdminPermissionChecker {
1452
1455
  /**
1453
1456
  * Changes data inline
1454
1457
  */
1455
- async filterMemberData(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob, options?: { forAdminCartCalculation?: boolean }): Promise<MemberWithRegistrationsBlob> {
1458
+ async filterMemberData(member: MemberWithUsersRegistrationsAndGroups, data: MemberWithRegistrationsBlob, options?: { forAdminCartCalculation?: boolean }): Promise<MemberWithRegistrationsBlob> {
1456
1459
  const cloned = data.clone();
1457
1460
 
1458
1461
  for (const [key, value] of cloned.details.recordAnswers.entries()) {
@@ -1511,7 +1514,7 @@ export class AdminPermissionChecker {
1511
1514
  /**
1512
1515
  * Only for creating new members
1513
1516
  */
1514
- filterMemberPut(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob, options: { asUserManager: boolean }) {
1517
+ filterMemberPut(member: MemberWithUsersRegistrationsAndGroups, data: MemberWithRegistrationsBlob, options: { asUserManager: boolean }) {
1515
1518
  if (options.asUserManager || STAMHOOFD.userMode === 'platform') {
1516
1519
  // A user manager cannot choose the member number + in platform mode, nobody can choose the member number
1517
1520
  data.details.memberNumber = null;
@@ -1526,7 +1529,7 @@ export class AdminPermissionChecker {
1526
1529
  }
1527
1530
  }
1528
1531
 
1529
- async filterMemberPatch(member: MemberWithRegistrations, data: AutoEncoderPatchType<MemberWithRegistrationsBlob>): Promise<AutoEncoderPatchType<MemberWithRegistrationsBlob>> {
1532
+ async filterMemberPatch(member: MemberWithUsersRegistrationsAndGroups, data: AutoEncoderPatchType<MemberWithRegistrationsBlob>): Promise<AutoEncoderPatchType<MemberWithRegistrationsBlob>> {
1530
1533
  if (!data.details) {
1531
1534
  return data;
1532
1535
  }