@stamhoofd/backend 2.9.0 → 2.13.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.9.0",
3
+ "version": "2.13.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -35,6 +35,14 @@
35
35
  "@simonbackx/simple-database": "1.24.0",
36
36
  "@simonbackx/simple-endpoints": "1.13.0",
37
37
  "@simonbackx/simple-logging": "^1.0.1",
38
+ "@stamhoofd/backend-i18n": "^2.13.0",
39
+ "@stamhoofd/backend-middleware": "^2.13.0",
40
+ "@stamhoofd/email": "^2.13.0",
41
+ "@stamhoofd/models": "^2.13.0",
42
+ "@stamhoofd/queues": "^2.13.0",
43
+ "@stamhoofd/sql": "^2.13.0",
44
+ "@stamhoofd/structures": "^2.13.0",
45
+ "@stamhoofd/utility": "^2.13.0",
38
46
  "aws-sdk": "^2.885.0",
39
47
  "axios": "1.6.8",
40
48
  "cookie": "^0.5.0",
@@ -50,5 +58,5 @@
50
58
  "postmark": "4.0.2",
51
59
  "stripe": "^16.6.0"
52
60
  },
53
- "gitHead": "d1959fa457e81e41797c4f7fe216095505f50aeb"
61
+ "gitHead": "2b130616eebea8cd30feb0f361c7bb0f48b534ba"
54
62
  }
@@ -30,33 +30,23 @@ export const filterCompilers: SQLFilterDefinitions = {
30
30
  ),
31
31
  city: createSQLExpressionFilterCompiler(
32
32
  SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.city'),
33
- undefined,
34
- true,
35
- false
33
+ {isJSONValue: true}
36
34
  ),
37
35
  country: createSQLExpressionFilterCompiler(
38
36
  SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.country'),
39
- undefined,
40
- true,
41
- false
37
+ {isJSONValue: true}
42
38
  ),
43
39
  umbrellaOrganization: createSQLExpressionFilterCompiler(
44
40
  SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.umbrellaOrganization'),
45
- undefined,
46
- true,
47
- false
41
+ {isJSONValue: true}
48
42
  ),
49
43
  type: createSQLExpressionFilterCompiler(
50
44
  SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.type'),
51
- undefined,
52
- true,
53
- false
45
+ {isJSONValue: true}
54
46
  ),
55
47
  tags: createSQLExpressionFilterCompiler(
56
48
  SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.tags'),
57
- undefined,
58
- true,
59
- true
49
+ {isJSONValue: true, isJSONObject: true}
60
50
  ),
61
51
  packages: createSQLRelationFilterCompiler(
62
52
  SQL.select().from(
@@ -103,9 +93,7 @@ export const filterCompilers: SQLFilterDefinitions = {
103
93
  ...baseSQLFilterCompilers,
104
94
  "type": createSQLExpressionFilterCompiler(
105
95
  SQL.jsonValue(SQL.column('meta'), '$.value.type'),
106
- undefined,
107
- true,
108
- false
96
+ {isJSONValue: true}
109
97
  )
110
98
  }
111
99
  ),
@@ -24,21 +24,15 @@ const filterCompilers: SQLFilterDefinitions = {
24
24
  endDate: createSQLColumnFilterCompiler('endDate'),
25
25
  groupIds: createSQLExpressionFilterCompiler(
26
26
  SQL.jsonValue(SQL.column('meta'), '$.value.groups[*].id'),
27
- undefined,
28
- true,
29
- true
27
+ {isJSONValue: true, isJSONObject: true}
30
28
  ),
31
29
  defaultAgeGroupIds: createSQLExpressionFilterCompiler(
32
30
  SQL.jsonValue(SQL.column('meta'), '$.value.defaultAgeGroupIds'),
33
- undefined,
34
- true,
35
- true
31
+ {isJSONValue: true, isJSONObject: true}
36
32
  ),
37
33
  organizationTagIds: createSQLExpressionFilterCompiler(
38
34
  SQL.jsonValue(SQL.column('meta'), '$.value.organizationTagIds'),
39
- undefined,
40
- true,
41
- true
35
+ {isJSONValue: true, isJSONObject: true}
42
36
  )
43
37
  }
44
38
 
@@ -75,25 +75,12 @@ Email.recipientLoaders.set(EmailRecipientFilterType.MemberUnverified, {
75
75
 
76
76
  const registrationFilterCompilers: SQLFilterDefinitions = {
77
77
  ...baseSQLFilterCompilers,
78
- "price": createSQLColumnFilterCompiler('price'),
78
+ "price": createSQLColumnFilterCompiler('price', {nullable: true}),
79
79
  "pricePaid": createSQLColumnFilterCompiler('pricePaid'),
80
- "waitingList": createSQLColumnFilterCompiler('waitingList'),
81
80
  "canRegister": createSQLColumnFilterCompiler('canRegister'),
82
- "cycle": createSQLColumnFilterCompiler('cycle'),
83
-
84
- "cycleOffset": createSQLExpressionFilterCompiler({
85
- getSQL(options) {
86
- return joinSQLQuery([
87
- SQL.column('groups', 'cycle').getSQL(options),
88
- ' - ',
89
- SQL.column('registrations', 'cycle').getSQL(options)
90
- ])
91
- },
92
- }),
93
-
94
81
  "organizationId": createSQLColumnFilterCompiler('organizationId'),
95
82
  "groupId": createSQLColumnFilterCompiler('groupId'),
96
- "registeredAt": createSQLColumnFilterCompiler('registeredAt'),
83
+ "registeredAt": createSQLColumnFilterCompiler('registeredAt', {nullable: true}),
97
84
  "periodId": createSQLColumnFilterCompiler(SQL.column('registrations', 'periodId')),
98
85
 
99
86
  "group": createSQLFilterNamespace({
@@ -105,7 +92,7 @@ const registrationFilterCompilers: SQLFilterDefinitions = {
105
92
  status: createSQLExpressionFilterCompiler(
106
93
  SQL.column('groups', 'status')
107
94
  ),
108
- defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId')),
95
+ defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId'), {nullable: true}),
109
96
  })
110
97
  }
111
98
 
@@ -120,20 +107,21 @@ const filterCompilers: SQLFilterDefinitions = {
120
107
  )
121
108
  ),
122
109
  age: createSQLExpressionFilterCompiler(
123
- new SQLAge(SQL.column('birthDay'))
110
+ new SQLAge(SQL.column('birthDay')),
111
+ {nullable: true}
124
112
  ),
125
113
  gender: createSQLExpressionFilterCompiler(
126
114
  SQL.jsonValue(SQL.column('details'), '$.value.gender'),
127
- undefined,
128
- true,
129
- false
115
+ {isJSONValue: true}
130
116
  ),
131
- birthDay: createSQLColumnFilterCompiler('birthDay', (d) => {
132
- if (typeof d === 'number') {
133
- const date = new Date(d)
134
- return Formatter.dateIso(date);
117
+ birthDay: createSQLColumnFilterCompiler('birthDay', {
118
+ normalizeValue: (d) => {
119
+ if (typeof d === 'number') {
120
+ const date = new Date(d)
121
+ return Formatter.dateIso(date);
122
+ }
123
+ return d;
135
124
  }
136
- return d;
137
125
  }),
138
126
  organizationName: createSQLExpressionFilterCompiler(
139
127
  SQL.column('organizations', 'name')
@@ -141,16 +129,12 @@ const filterCompilers: SQLFilterDefinitions = {
141
129
 
142
130
  email: createSQLExpressionFilterCompiler(
143
131
  SQL.jsonValue(SQL.column('details'), '$.value.email'),
144
- undefined,
145
- true,
146
- false
132
+ {isJSONValue: true}
147
133
  ),
148
134
 
149
135
  parentEmail: createSQLExpressionFilterCompiler(
150
136
  SQL.jsonValue(SQL.column('details'), '$.value.parents[*].email'),
151
- undefined,
152
- true,
153
- true
137
+ {isJSONValue: true, isJSONObject: true}
154
138
  ),
155
139
 
156
140
  registrations: createSQLRelationFilterCompiler(
@@ -6,7 +6,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
6
6
  import { I18n } from '@stamhoofd/backend-i18n';
7
7
  import { Email } from '@stamhoofd/email';
8
8
  import { BalanceItem, BalanceItemPayment, Group, Member, MemberWithRegistrations, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment, Platform, RateLimiter, Registration, User } from '@stamhoofd/models';
9
- import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, IDRegisterCheckout, BalanceItemWithPayments, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PermissionLevel, PlatformFamily, PlatformMember, RegisterItem, RegisterResponse, Version } from "@stamhoofd/structures";
9
+ import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, BalanceItemWithPayments, IDRegisterCheckout, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PermissionLevel, PlatformFamily, PlatformMember, RegisterItem, RegisterResponse, Version } from "@stamhoofd/structures";
10
10
  import { Formatter } from '@stamhoofd/utility';
11
11
 
12
12
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
@@ -678,11 +678,61 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
678
678
  }
679
679
 
680
680
  const payment = new Payment()
681
- payment.userId = user.id
682
681
 
683
682
  // Who will receive this money?
684
683
  payment.organizationId = organization.id
685
684
 
685
+ // Who paid
686
+ payment.payingUserId = user.id
687
+ payment.payingOrganizationId = checkout.asOrganizationId ?? null
688
+
689
+ // Fill in customer:
690
+ payment.customer = PaymentCustomer.create({
691
+ firstName: user.firstName,
692
+ lastName: user.lastName,
693
+ email: user.email,
694
+ })
695
+
696
+ if (checkout.asOrganizationId) {
697
+ if (!checkout.customer) {
698
+ throw new SimpleError({
699
+ code: "missing_fields",
700
+ message: "customer is required when paying as an organization",
701
+ human: "Vul je facturatiegegevens in om verder te gaan."
702
+ })
703
+ }
704
+
705
+ if (!checkout.customer.company) {
706
+ throw new SimpleError({
707
+ code: "missing_fields",
708
+ message: "customer.company is required when paying as an organization",
709
+ human: "Als je een betaling uitvoert in naam van je vereniging, is het noodzakelijk om facturatiegegevens met bedrijfsgegevens in te vullen."
710
+ })
711
+ }
712
+
713
+ const payingOrganization = await Organization.getByID(checkout.asOrganizationId);
714
+ if (!payingOrganization) {
715
+ throw new SimpleError({
716
+ code: "invalid_data",
717
+ message: "Oeps, de organisatie waarvoor je probeert te betalen lijkt niet meer te bestaan. Herlaad de pagina en probeer opnieuw."
718
+ })
719
+ }
720
+
721
+ // Search company id
722
+ // this avoids needing to check the VAT number every time
723
+ const id = checkout.customer.company.id
724
+ const foundCompany = payingOrganization.meta.companies.find(c => c.id === id)
725
+
726
+ if (!foundCompany) {
727
+ throw new SimpleError({
728
+ code: "invalid_data",
729
+ message: "Oeps, de facturatiegegevens die je probeerde te selecteren lijken niet meer te bestaan. Herlaad de pagina en probeer opnieuw."
730
+ })
731
+ }
732
+
733
+ payment.customer.company = foundCompany
734
+ }
735
+
686
736
  payment.method = checkout.paymentMethod
687
737
  payment.status = PaymentStatus.Created
688
738
  payment.price = totalPrice
@@ -1,13 +1,14 @@
1
- import { AutoEncoderPatchType, Decoder, ObjectData, patchObject } from '@simonbackx/simple-encoding';
1
+ import { AutoEncoderPatchType, Decoder, isPatchableArray, ObjectData, PatchableArrayAutoEncoder, patchObject } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
3
  import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
4
4
  import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, Webshop } from '@stamhoofd/models';
5
- import { BuckarooSettings, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel } from "@stamhoofd/structures";
5
+ import { BuckarooSettings, Company, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel } from "@stamhoofd/structures";
6
6
  import { Formatter } from '@stamhoofd/utility';
7
7
 
8
8
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
9
9
  import { BuckarooHelper } from '../../../../helpers/BuckarooHelper';
10
10
  import { Context } from '../../../../helpers/Context';
11
+ import { ViesHelper } from '../../../../helpers/ViesHelper';
11
12
 
12
13
  type Params = Record<string, never>;
13
14
  type Query = undefined;
@@ -208,6 +209,10 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
208
209
  }
209
210
 
210
211
  if (request.body.meta) {
212
+ if (request.body.meta.companies) {
213
+ await this.validateCompanies(organization, request.body.meta.companies)
214
+ }
215
+
211
216
  const savedPackages = organization.meta.packages
212
217
  organization.meta.patchOrPut(request.body.meta)
213
218
  organization.meta.packages = savedPackages
@@ -366,5 +371,52 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
366
371
  errors.throwIfNotEmpty()
367
372
  return new Response(await AuthenticatedStructures.organization(organization));
368
373
  }
374
+
375
+ async validateCompanies(organization: Organization, companies: PatchableArrayAutoEncoder<Company>|Company[]) {
376
+ if (isPatchableArray(companies)) {
377
+ for (const patch of companies.getPatches()) {
378
+ // Changed VAT number
379
+ const original = organization.meta.companies.find(c => c.id === patch.id)
380
+
381
+ if (!original) {
382
+ throw new Error('Could not find company')
383
+ }
384
+
385
+ // Changed VAT number
386
+ const prepatched = original.patch(patch)
387
+ await ViesHelper.checkCompany(prepatched, patch)
388
+ }
389
+
390
+ let c = 0;
391
+ for (const {put} of companies.getPuts()) {
392
+ c++;
393
+
394
+ if ((organization.meta.companies.length + c) > 5) {
395
+ throw new SimpleError({
396
+ code: "invalid_field",
397
+ message: "Too many companies",
398
+ human: "Je kan maximaal 5 bedrijven toevoegen",
399
+ field: "companies"
400
+ })
401
+ }
402
+
403
+ await ViesHelper.checkCompany(put, put)
404
+ }
405
+
406
+ } else {
407
+ if (companies.length > 5) {
408
+ throw new SimpleError({
409
+ code: "invalid_field",
410
+ message: "Too many companies",
411
+ human: "Je kan maximaal 5 bedrijven toevoegen",
412
+ field: "companies"
413
+ })
414
+ }
415
+
416
+ for (const company of companies) {
417
+ await ViesHelper.checkCompany(company, company)
418
+ }
419
+ }
420
+ }
369
421
  }
370
422
 
@@ -0,0 +1,43 @@
1
+ import { Decoder } from '@simonbackx/simple-encoding';
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
4
+
5
+ import { Context } from '../../../../helpers/Context';
6
+ import { GetPaymentsEndpoint } from './GetPaymentsEndpoint';
7
+
8
+ type Params = Record<string, never>;
9
+ type Query = CountFilteredRequest;
10
+ type Body = undefined;
11
+ type ResponseBody = CountResponse;
12
+
13
+ export class GetPaymentsCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
+ queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>
15
+
16
+ protected doesMatch(request: Request): [true, Params] | [false] {
17
+ if (request.method != "GET") {
18
+ return [false];
19
+ }
20
+
21
+ const params = Endpoint.parseParameters(request.url, "/payments/count", {});
22
+
23
+ if (params) {
24
+ return [true, params as Params];
25
+ }
26
+ return [false];
27
+ }
28
+
29
+ async handle(request: DecodedRequest<Params, Query, Body>) {
30
+ await Context.setOrganizationScope();
31
+ await Context.authenticate()
32
+ const query = await GetPaymentsEndpoint.buildQuery(request.query)
33
+
34
+ const count = await query
35
+ .count();
36
+
37
+ return new Response(
38
+ CountResponse.create({
39
+ count
40
+ })
41
+ );
42
+ }
43
+ }