@stamhoofd/backend 2.4.0 → 2.6.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 (23) hide show
  1. package/package.json +3 -3
  2. package/src/endpoints/admin/invoices/GetInvoicesEndpoint.ts +1 -1
  3. package/src/endpoints/global/events/PatchEventsEndpoint.ts +16 -9
  4. package/src/endpoints/global/members/GetMembersEndpoint.ts +18 -6
  5. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +21 -8
  6. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +3 -2
  7. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -1
  8. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +2 -1
  9. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +6 -0
  10. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +417 -178
  11. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +5 -5
  12. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +88 -11
  13. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +10 -4
  14. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +4 -1
  15. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +6 -0
  16. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +7 -2
  17. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +13 -28
  18. package/src/helpers/AdminPermissionChecker.ts +9 -4
  19. package/src/helpers/AuthenticatedStructures.ts +89 -28
  20. package/src/helpers/MemberUserSyncer.ts +5 -5
  21. package/src/helpers/StripeHelper.ts +83 -40
  22. package/src/helpers/StripePayoutChecker.ts +7 -5
  23. package/src/seeds/1722344160-update-membership.ts +57 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -48,7 +48,7 @@
48
48
  "node-rsa": "1.1.1",
49
49
  "openid-client": "^5.4.0",
50
50
  "postmark": "4.0.2",
51
- "stripe": "^11.5.0"
51
+ "stripe": "^16.6.0"
52
52
  },
53
- "gitHead": "5313c328ca0e90544bf198bc6bebc90d7dced2aa"
53
+ "gitHead": "7a3f9f6c08058dc8b671befbfad73184afdc6d7c"
54
54
  }
@@ -169,7 +169,7 @@ export class GetInvoicesEndpoint extends Endpoint<Params, Query, Body, ResponseB
169
169
  STInvoicePrivate.create({
170
170
  ...invoice,
171
171
  payment: payment ? PaymentStruct.create(payment) : null,
172
- organization: organization ? (await organization.getStructure({emptyGroups: true})) : undefined,
172
+ organization: organization ? organization.getBaseStructure() : undefined,
173
173
  settlement: payment?.settlement ?? null,
174
174
  })
175
175
  )
@@ -1,6 +1,6 @@
1
1
  import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, patchObject, StringDecoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
- import { Event, Organization, Platform, RegistrationPeriod } from '@stamhoofd/models';
3
+ import { Event, Group, Organization, Platform, RegistrationPeriod } from '@stamhoofd/models';
4
4
  import { Event as EventStruct, GroupType, PermissionLevel } from "@stamhoofd/structures";
5
5
 
6
6
  import { SimpleError } from '@simonbackx/simple-errors';
@@ -80,6 +80,12 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
80
80
  event.startDate = put.startDate
81
81
  event.endDate = put.endDate
82
82
  event.meta = put.meta
83
+ event.typeId = await PatchEventsEndpoint.validateEventType(put.typeId)
84
+ await PatchEventsEndpoint.checkEventLimits(event)
85
+
86
+ if (!(await Context.auth.canAccessEvent(event, PermissionLevel.Full))) {
87
+ throw Context.auth.error()
88
+ }
83
89
 
84
90
  if (put.group) {
85
91
  const period = await RegistrationPeriod.getByDate(event.startDate)
@@ -99,14 +105,8 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
99
105
  put.group.organizationId,
100
106
  period.id
101
107
  )
108
+ await event.syncGroupRequirements(group)
102
109
  event.groupId = group.id
103
-
104
- }
105
- event.typeId = await PatchEventsEndpoint.validateEventType(put.typeId)
106
- await PatchEventsEndpoint.checkEventLimits(event)
107
-
108
- if (!(await Context.auth.canAccessEvent(event, PermissionLevel.Full))) {
109
- throw Context.auth.error()
110
110
  }
111
111
 
112
112
  await event.save()
@@ -130,7 +130,6 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
130
130
  event.endDate = patch.endDate ?? event.endDate
131
131
  event.meta = patchObject(event.meta, patch.meta)
132
132
 
133
-
134
133
  if (patch.organizationId !== undefined) {
135
134
  if (organization?.id && patch.organizationId !== organization.id) {
136
135
  throw new SimpleError({
@@ -210,6 +209,14 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
210
209
  }
211
210
 
212
211
  await event.save()
212
+
213
+ if (event.groupId) {
214
+ const group = await Group.getByID(event.groupId)
215
+ if (group) {
216
+ await event.syncGroupRequirements(group)
217
+ }
218
+ }
219
+
213
220
  events.push(event)
214
221
  }
215
222
 
@@ -157,6 +157,15 @@ const filterCompilers: SQLFilterDefinitions = {
157
157
  .where(
158
158
  SQL.column('memberId'),
159
159
  SQL.column('members', 'id'),
160
+ ).whereNot(
161
+ SQL.column('registeredAt'),
162
+ null,
163
+ ).where(
164
+ SQL.column('deactivatedAt'),
165
+ null,
166
+ ).where(
167
+ SQL.column('groups', 'deletedAt'),
168
+ null
160
169
  ),
161
170
  {
162
171
  ...registrationFilterCompilers,
@@ -245,9 +254,12 @@ const filterCompilers: SQLFilterDefinitions = {
245
254
  ).whereNot(
246
255
  SQL.column('registeredAt'),
247
256
  null,
248
- ).whereNot(
249
- SQL.column('groups', 'status'),
250
- GroupStatus.Archived
257
+ ).where(
258
+ SQL.column('deactivatedAt'),
259
+ null,
260
+ ).where(
261
+ SQL.column('groups', 'deletedAt'),
262
+ null
251
263
  ),
252
264
  registrationFilterCompilers
253
265
  ),
@@ -276,9 +288,9 @@ const filterCompilers: SQLFilterDefinitions = {
276
288
  ).whereNot(
277
289
  SQL.column('registeredAt'),
278
290
  null,
279
- ).whereNot(
280
- SQL.column('groups', 'status'),
281
- GroupStatus.Archived
291
+ ).where(
292
+ SQL.column('groups', 'deletedAt'),
293
+ null
282
294
  ),
283
295
  organizationFilterCompilers
284
296
  ),
@@ -106,16 +106,29 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
106
106
  duplicate.details.merge(member.details)
107
107
  member = duplicate
108
108
 
109
- // Only save after checking permissions
109
+ // You need write permissions, because a user can potentially earn write permissions on a member
110
+ // by registering it
111
+ if (!await Context.auth.canAccessMember(duplicate, PermissionLevel.Write)) {
112
+ throw new SimpleError({
113
+ code: "known_member_missing_rights",
114
+ message: "Creating known member without sufficient access rights",
115
+ human: "Dit lid is al bekend in het systeem, maar je hebt er geen toegang tot. Vraag iemand met de juiste toegangsrechten om dit lid voor jou toe te voegen, of vraag het lid om zelf in te schrijven via het ledenportaal.",
116
+ statusCode: 400
117
+ })
118
+ }
110
119
  }
111
120
 
112
121
  if (struct.registrations.length === 0) {
113
- throw new SimpleError({
114
- code: "missing_group",
115
- message: "Missing group",
116
- human: "Schrijf een nieuw lid altijd in voor minstens één groep",
117
- statusCode: 400
118
- })
122
+ // We risk creating a new member without being able to access it manually afterwards
123
+
124
+ if ((organization && !await Context.auth.hasFullAccess(organization.id)) || (!organization && !Context.auth.hasPlatformFullAccess())) {
125
+ throw new SimpleError({
126
+ code: "missing_group",
127
+ message: "Missing group",
128
+ human: "Je moet hoofdbeheerder zijn om een lid toe te voegen zonder inschrijving in het systeem",
129
+ statusCode: 400
130
+ })
131
+ }
119
132
  }
120
133
 
121
134
  // Throw early
@@ -223,7 +236,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
223
236
  // Update registrations
224
237
  for (const patchRegistration of patch.registrations.getPatches()) {
225
238
  const registration = member.registrations.find(r => r.id === patchRegistration.id)
226
- if (!registration || registration.memberId != member.id) {
239
+ if (!registration || registration.memberId != member.id || (!await Context.auth.canAccessRegistration(registration, PermissionLevel.Write))) {
227
240
  throw new SimpleError({
228
241
  code: "permission_denied",
229
242
  message: "You don't have permissions to access this endpoint",
@@ -4,6 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Organization } from '@stamhoofd/models';
5
5
  import { Organization as OrganizationStruct } from "@stamhoofd/structures";
6
6
  import { GoogleTranslateHelper } from "@stamhoofd/utility";
7
+ import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
7
8
  type Params = Record<string, never>;
8
9
 
9
10
  class Query extends AutoEncoder {
@@ -60,7 +61,7 @@ export class GetOrganizationFromDomainEndpoint extends Endpoint<Params, Query, B
60
61
  statusCode: 404
61
62
  })
62
63
  }
63
- return new Response(await organization.getStructure());
64
+ return new Response(await AuthenticatedStructures.organization(organization));
64
65
  }
65
66
  }
66
67
 
@@ -75,6 +76,6 @@ export class GetOrganizationFromDomainEndpoint extends Endpoint<Params, Query, B
75
76
  statusCode: 404
76
77
  })
77
78
  }
78
- return new Response(await organization.getStructure());
79
+ return new Response(await AuthenticatedStructures.organization(organization));
79
80
  }
80
81
  }
@@ -4,6 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Organization } from '@stamhoofd/models';
5
5
  import { Organization as OrganizationStruct } from "@stamhoofd/structures";
6
6
  import { GoogleTranslateHelper } from "@stamhoofd/utility";
7
+ import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
7
8
  type Params = Record<string, never>;
8
9
 
9
10
  class Query extends AutoEncoder {
@@ -44,6 +45,6 @@ export class GetOrganizationFromUriEndpoint extends Endpoint<Params, Query, Body
44
45
  statusCode: 404
45
46
  })
46
47
  }
47
- return new Response(await organization.getStructure());
48
+ return new Response(await AuthenticatedStructures.organization(organization));
48
49
  }
49
50
  }
@@ -2,6 +2,7 @@ import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-e
2
2
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
3
  import { Organization } from "@stamhoofd/models";
4
4
  import { Organization as OrganizationStruct,OrganizationSimple } from "@stamhoofd/structures";
5
+ import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
5
6
 
6
7
  type Params = Record<string, never>;
7
8
 
@@ -57,6 +58,6 @@ export class SearchOrganizationEndpoint extends Endpoint<Params, Query, Body, Re
57
58
  if (request.request.getVersion() < 169) {
58
59
  return new Response(organizations.map(o => OrganizationSimple.create(o)));
59
60
  }
60
- return new Response(await Promise.all(organizations.map(o => o.getStructure())));
61
+ return new Response(await Promise.all(organizations.map(o => AuthenticatedStructures.organization(o))));
61
62
  }
62
63
  }
@@ -18,6 +18,12 @@ class Body extends AutoEncoder {
18
18
  @field({ decoder: StringDecoder })
19
19
  id: string
20
20
 
21
+ /**
22
+ * Events for direct charges
23
+ */
24
+ @field({ decoder: StringDecoder, nullable: true, optional: true })
25
+ account: string|null = null
26
+
21
27
  @field({ decoder: AnyDecoder })
22
28
  data: any
23
29
  }