@stamhoofd/backend 2.113.0 → 2.114.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.113.0",
3
+ "version": "2.114.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -54,14 +54,14 @@
54
54
  "@simonbackx/simple-encoding": "2.23.1",
55
55
  "@simonbackx/simple-endpoints": "1.20.1",
56
56
  "@simonbackx/simple-logging": "^1.0.1",
57
- "@stamhoofd/backend-i18n": "2.113.0",
58
- "@stamhoofd/backend-middleware": "2.113.0",
59
- "@stamhoofd/email": "2.113.0",
60
- "@stamhoofd/models": "2.113.0",
61
- "@stamhoofd/queues": "2.113.0",
62
- "@stamhoofd/sql": "2.113.0",
63
- "@stamhoofd/structures": "2.113.0",
64
- "@stamhoofd/utility": "2.113.0",
57
+ "@stamhoofd/backend-i18n": "2.114.0",
58
+ "@stamhoofd/backend-middleware": "2.114.0",
59
+ "@stamhoofd/email": "2.114.0",
60
+ "@stamhoofd/models": "2.114.0",
61
+ "@stamhoofd/queues": "2.114.0",
62
+ "@stamhoofd/sql": "2.114.0",
63
+ "@stamhoofd/structures": "2.114.0",
64
+ "@stamhoofd/utility": "2.114.0",
65
65
  "archiver": "^7.0.1",
66
66
  "axios": "^1.13.2",
67
67
  "cookie": "^0.7.0",
@@ -79,5 +79,5 @@
79
79
  "publishConfig": {
80
80
  "access": "public"
81
81
  },
82
- "gitHead": "e90b56b0e379e4bc7f7138cef336c267fb5e9bcf"
82
+ "gitHead": "89f437d82d2c21b1741c9c7efdbdafd6c8b98318"
83
83
  }
@@ -1,8 +1,8 @@
1
1
  import { Email, Member } from '@stamhoofd/models';
2
2
  import { SQL } from '@stamhoofd/sql';
3
3
  import { EmailRecipient, EmailRecipientFilterType, LimitedFilteredRequest, PaginatedResponse, RegistrationsBlob, mergeFilters } from '@stamhoofd/structures';
4
- import { GetRegistrationsEndpoint } from '../endpoints/global/registration/GetRegistrationsEndpoint';
5
- import { memberJoin } from '../sql-filters/registrations';
4
+ import { GetRegistrationsEndpoint } from '../endpoints/global/registration/GetRegistrationsEndpoint.js';
5
+ import { memberJoin } from '../sql-filters/registrations.js';
6
6
 
7
7
  async function getRecipients(result: PaginatedResponse<RegistrationsBlob, LimitedFilteredRequest>, type: 'member' | 'parents' | 'unverified') {
8
8
  const recipients: EmailRecipient[] = [];
@@ -81,7 +81,7 @@ Email.recipientLoaders.set(EmailRecipientFilterType.RegistrationUnverified, {
81
81
  const q = (await GetRegistrationsEndpoint.buildQuery(query)).join(memberJoin);
82
82
 
83
83
  return await q.sum(
84
- SQL.jsonLength(SQL.column('details'), '$.value.unverifiedEmails'),
84
+ SQL.jsonLength(SQL.column(Member.table, 'details'), '$.value.unverifiedEmails'),
85
85
  );
86
86
  },
87
87
  });
@@ -3,7 +3,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
3
3
  import { Email, Platform, RateLimiter } from '@stamhoofd/models';
4
4
  import { EmailPreview, EmailStatus, Email as EmailStruct, EmailTemplate as EmailTemplateStruct } from '@stamhoofd/structures';
5
5
 
6
- import { Context } from '../../../helpers/Context';
6
+ import { Context } from '../../../helpers/Context.js';
7
7
  import { SimpleError } from '@simonbackx/simple-errors';
8
8
 
9
9
  type Params = Record<string, never>;
@@ -128,7 +128,7 @@ export class CreateEmailEndpoint extends Endpoint<Params, Query, Body, ResponseB
128
128
 
129
129
  await model.save();
130
130
  await model.buildExampleRecipient();
131
- model.updateCount();
131
+ await model.updateCount();
132
132
 
133
133
  if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent || request.body.status === EmailStatus.Queued) {
134
134
  if (!await Context.auth.canSendEmail(model)) {
@@ -1,10 +1,10 @@
1
1
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
2
  import { Email, Platform } from '@stamhoofd/models';
3
- import { EmailPreview, EmailStatus, Email as EmailStruct, PermissionLevel } from '@stamhoofd/structures';
3
+ import { EmailPreview, EmailRecipientsStatus, EmailStatus, Email as EmailStruct, PermissionLevel } from '@stamhoofd/structures';
4
4
 
5
5
  import { AutoEncoderPatchType, Decoder, patchObject } from '@simonbackx/simple-encoding';
6
6
  import { SimpleError } from '@simonbackx/simple-errors';
7
- import { Context } from '../../../helpers/Context';
7
+ import { Context } from '../../../helpers/Context.js';
8
8
 
9
9
  type Params = { id: string };
10
10
  type Query = undefined;
@@ -124,6 +124,15 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
124
124
  });
125
125
  }
126
126
 
127
+ if (model.recipientsStatus === EmailRecipientsStatus.Created) {
128
+ throw new SimpleError({
129
+ code: 'already_created',
130
+ message: 'Recipients already created',
131
+ human: $t(`457ecdaf-d1de-4136-9e82-682c18c5fa76`),
132
+ statusCode: 400,
133
+ });
134
+ }
135
+
127
136
  model.recipientFilter = patchObject(model.recipientFilter, request.body.recipientFilter);
128
137
  rebuild = true;
129
138
  }
@@ -168,10 +177,7 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
168
177
 
169
178
  if (rebuild) {
170
179
  await model.buildExampleRecipient();
171
-
172
- // Force null - because we have stale data
173
- model.emailRecipientsCount = null;
174
- model.updateCount();
180
+ await model.updateCount();
175
181
  }
176
182
 
177
183
  if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent || request.body.status === EmailStatus.Queued) {
@@ -2,7 +2,7 @@ import { MolliePayment, MollieToken, Order, Organization, PayconiqPayment, Payme
2
2
  import { Settlement } from '@stamhoofd/structures';
3
3
  import axios from 'axios';
4
4
 
5
- import { StripePayoutChecker } from './StripePayoutChecker';
5
+ import { StripePayoutChecker } from './StripePayoutChecker.js';
6
6
 
7
7
  type MollieSettlement = {
8
8
  id: string;
@@ -179,7 +179,7 @@ async function updateSettlement(token: string, settlement: MollieSettlement, fro
179
179
  id: settlement.id,
180
180
  reference: settlement.reference,
181
181
  settledAt: new Date(settlement.settledAt),
182
- amount: Math.round(parseFloat(settlement.amount.value) * 100),
182
+ amount: Math.round(parseFloat(settlement.amount.value) * 100) * 100,
183
183
  });
184
184
  const saved = await payment.save();
185
185
 
@@ -171,8 +171,8 @@ export class StripePayoutChecker {
171
171
  return;
172
172
  }
173
173
 
174
- if (payment.price !== balanceItem.amount) {
175
- console.log('Amount mismatch for payment ' + payment.id + ': ' + payment.price + ' !== ' + balanceItem.amount);
174
+ if (payment.price !== balanceItem.amount * 100) {
175
+ console.log('Amount mismatch for payment ' + payment.id + ': ' + payment.price + ' !== ' + (balanceItem.amount * 100));
176
176
  return;
177
177
  }
178
178
 
@@ -180,13 +180,13 @@ export class StripePayoutChecker {
180
180
  id: payout.id,
181
181
  reference: payout.statement_descriptor ?? '',
182
182
  settledAt: new Date(payout.arrival_date * 1000),
183
- amount: payout.amount,
183
+ amount: payout.amount * 100,
184
184
  // Set only if application fee is witheld
185
- fee: totalFees,
185
+ fee: totalFees * 100,
186
186
  });
187
187
 
188
188
  payment.settlement = settlement;
189
- payment.transferFee = totalFees - payment.serviceFeePayout;
189
+ payment.transferFee = totalFees * 100 - payment.serviceFeePayout;
190
190
 
191
191
  // Force an updatedAt timestamp of the related order
192
192
  // Mark order as 'updated', or the frontend won't pull in the updates
@@ -91,14 +91,9 @@ async function migrateMember(member: Member) {
91
91
  if (error.hasCode('https://api.publiq.be/probs/uitpas/pass-not-found') || error.hasCode('https://api.publiq.be/probs/uitpas/invalid-uitpas-number')) {
92
92
  console.log(`Uitpas number ${member.details.uitpasNumberDetails?.uitpasNumber} is not known by the uitpas api for member with id ${member.id}.`);
93
93
 
94
- // set updated at
95
- if (member.details.uitpasNumberDetails) {
96
- member.details.uitpasNumberDetails.socialTariff = UitpasSocialTariff.create({
97
- status: UitpasSocialTariffStatus.Unknown,
98
- });
99
- }
100
-
101
- // remove review
94
+ // remove the uitpas number
95
+ member.details.uitpasNumberDetails = null;
96
+ member.details.cleanData();
102
97
  member.details.reviewTimes.removeReview('uitpasNumber');
103
98
  await member.save();
104
99
 
@@ -113,6 +108,9 @@ async function migrateMember(member: Member) {
113
108
  // remove the uitpas number
114
109
  member.details.uitpasNumberDetails = null;
115
110
  member.details.cleanData();
111
+
112
+ // remove review
113
+ member.details.reviewTimes.removeReview('uitpasNumber');
116
114
  await member.save();
117
115
 
118
116
  // do not throw
@@ -68,34 +68,20 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
68
68
  type: SQLValueType.String,
69
69
  nullable: false,
70
70
  }),
71
+ 'details.uitpasNumberDetails.socialTariff.status': createColumnFilter({
72
+ expression: SQL.jsonExtract(SQL.column(membersTable, 'details'), '$.value.uitpasNumberDetails.socialTariff.status'),
73
+ type: SQLValueType.JSONString,
74
+ nullable: true,
75
+ checkPermission: async () => {
76
+ await throwIfNoFinancialReadAccess();
77
+ },
78
+ }),
71
79
  'details.requiresFinancialSupport': createColumnFilter({
72
80
  expression: SQL.jsonExtract(SQL.column(membersTable, 'details'), '$.value.requiresFinancialSupport.value'),
73
81
  type: SQLValueType.JSONBoolean,
74
82
  nullable: true,
75
83
  checkPermission: async () => {
76
- const organization = Context.organization;
77
- if (!organization) {
78
- if (!Context.auth.hasPlatformFullAccess()) {
79
- throw new SimpleError({
80
- code: 'permission_denied',
81
- message: 'No permissions for financial support filter.',
82
- human: $t(`64d658fa-0727-4924-9448-b243fe8e10a1`),
83
- statusCode: 400,
84
- });
85
- }
86
- return;
87
- }
88
-
89
- const permissions = await Context.auth.getOrganizationPermissions(organization);
90
-
91
- if (!permissions || !permissions.hasAccessRight(AccessRight.MemberReadFinancialData)) {
92
- throw new SimpleError({
93
- code: 'permission_denied',
94
- message: 'No permissions for financial support filter (organization scope).',
95
- human: $t(`64d658fa-0727-4924-9448-b243fe8e10a1`),
96
- statusCode: 400,
97
- });
98
- }
84
+ await throwIfNoFinancialReadAccess();
99
85
  },
100
86
  }),
101
87
  'email': createColumnFilter({
@@ -512,3 +498,29 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
512
498
  ),
513
499
  },
514
500
  };
501
+
502
+ async function throwIfNoFinancialReadAccess() {
503
+ const organization = Context.organization;
504
+ if (!organization) {
505
+ if (!Context.auth.hasPlatformFullAccess()) {
506
+ throw new SimpleError({
507
+ code: 'permission_denied',
508
+ message: 'No permissions for financial support filter.',
509
+ human: $t(`64d658fa-0727-4924-9448-b243fe8e10a1`),
510
+ statusCode: 400,
511
+ });
512
+ }
513
+ return;
514
+ }
515
+
516
+ const permissions = await Context.auth.getOrganizationPermissions(organization);
517
+
518
+ if (!permissions || !permissions.hasAccessRight(AccessRight.MemberReadFinancialData)) {
519
+ throw new SimpleError({
520
+ code: 'permission_denied',
521
+ message: 'No permissions for financial support filter (organization scope).',
522
+ human: $t(`64d658fa-0727-4924-9448-b243fe8e10a1`),
523
+ statusCode: 400,
524
+ });
525
+ }
526
+ }