@stamhoofd/backend 2.1.2 → 2.1.3

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/.env.json CHANGED
@@ -23,14 +23,14 @@
23
23
  "demoApi": "api.stamhoofd",
24
24
  "rendererApi": "renderer.stamhoofd"
25
25
  },
26
- "translationNamespace": "stamhoofd",
26
+ "translationNamespace": "digit",
27
27
  "userMode": "organization",
28
28
 
29
29
  "PORT": 9091,
30
30
  "DB_HOST": "127.0.0.1",
31
31
  "DB_USER": "root",
32
32
  "DB_PASS": "root",
33
- "DB_DATABASE": "stamhoofd1",
33
+ "DB_DATABASE": "stamhoofd",
34
34
 
35
35
  "SMTP_HOST": "0.0.0.0",
36
36
  "SMTP_USERNAME": "username",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -50,5 +50,5 @@
50
50
  "postmark": "4.0.2",
51
51
  "stripe": "^11.5.0"
52
52
  },
53
- "gitHead": "2332fe7ce5922e32a7e9c5c947fdde572aadcc03"
53
+ "gitHead": "b5773c547dceaae52b9c2a098b222417fcd42e4f"
54
54
  }
@@ -48,6 +48,7 @@ const registrationFilterCompilers: SQLFilterDefinitions = {
48
48
  status: createSQLExpressionFilterCompiler(
49
49
  SQL.column('groups', 'status')
50
50
  ),
51
+ defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId')),
51
52
  })
52
53
  }
53
54
 
@@ -125,6 +126,25 @@ const filterCompilers: SQLFilterDefinitions = {
125
126
  }
126
127
  ),
127
128
 
129
+ responsibilities: createSQLRelationFilterCompiler(
130
+ SQL.select()
131
+ .from(
132
+ SQL.table('member_responsibility_records')
133
+ )
134
+ .where(
135
+ SQL.column('memberId'),
136
+ SQL.column('members', 'id'),
137
+ ),
138
+ {
139
+ // Alias for responsibilityId
140
+ "id": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'responsibilityId')),
141
+ "responsibilityId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'responsibilityId')),
142
+ "organizationId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'organizationId')),
143
+ "startDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'startDate')),
144
+ "endDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'endDate')),
145
+ }
146
+ ),
147
+
128
148
  /**
129
149
  * @deprecated?
130
150
  */
@@ -270,6 +290,11 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
270
290
  periodId: platform.periodId,
271
291
  registeredAt: {
272
292
  $neq: null
293
+ },
294
+ group: {
295
+ defaultAgeGroupId: {
296
+ $neq: null
297
+ }
273
298
  }
274
299
  }
275
300
  }
@@ -2,7 +2,7 @@ import { OneToManyRelation } from '@simonbackx/simple-database';
2
2
  import { ConvertArrayToPatchableArray, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
3
3
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
4
4
  import { SimpleError } from "@simonbackx/simple-errors";
5
- import { BalanceItem, BalanceItemPayment, Document, Group, Member, MemberFactory, MemberResponsibilityRecord, MemberWithRegistrations, Organization, Payment, Platform, Registration, User } from '@stamhoofd/models';
5
+ import { BalanceItem, BalanceItemPayment, Document, Group, Member, MemberFactory, MemberResponsibilityRecord, MemberWithRegistrations, Organization, Payment, Platform, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
6
6
  import { BalanceItemStatus, MemberWithRegistrationsBlob, MembersBlob, PaymentMethod, PaymentStatus, PermissionLevel, Registration as RegistrationStruct, User as UserStruct } from "@stamhoofd/structures";
7
7
  import { Formatter } from '@stamhoofd/utility';
8
8
 
@@ -121,6 +121,14 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
121
121
  throw Context.auth.notFoundOrNoAccess("Je hebt niet voldoende rechten om leden toe te voegen in deze groep")
122
122
  }
123
123
 
124
+ const period = await RegistrationPeriod.getByID(group.periodId)
125
+ if (!period || period.locked) {
126
+ throw new SimpleError({
127
+ code: "period_locked",
128
+ message: "Deze inschrijvingsperiode is afgesloten en staat geen wijzigingen meer toe.",
129
+ })
130
+ }
131
+
124
132
  // Set organization id of member based on registrations
125
133
  if (!organization && STAMHOOFD.userMode !== 'platform' && !member.organizationId) {
126
134
  member.organizationId = group.organizationId
@@ -263,6 +271,14 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
263
271
  })
264
272
  }
265
273
 
274
+ const period = await RegistrationPeriod.getByID(group.periodId)
275
+ if (!period || period.locked) {
276
+ throw new SimpleError({
277
+ code: "period_locked",
278
+ message: "Deze inschrijvingsperiode is afgesloten en staat geen wijzigingen meer toe.",
279
+ })
280
+ }
281
+
266
282
  // TODO: allow group changes
267
283
  registration.waitingList = patchRegistration.waitingList ?? registration.waitingList
268
284
 
@@ -337,13 +353,20 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
337
353
  if (!await Context.auth.canAccessRegistration(registration, PermissionLevel.Write)) {
338
354
  throw Context.auth.error("Je hebt niet voldoende rechten om deze inschrijving te verwijderen")
339
355
  }
356
+ const oldGroup = await getGroup(registration.groupId)
357
+ const period = oldGroup && await RegistrationPeriod.getByID(oldGroup.periodId)
358
+ if (!period || period.locked) {
359
+ throw new SimpleError({
360
+ code: "period_locked",
361
+ message: "Deze inschrijvingsperiode is afgesloten en staat geen wijzigingen meer toe.",
362
+ })
363
+ }
340
364
 
341
365
  balanceItemMemberIds.push(member.id)
342
366
  await BalanceItem.deleteForDeletedRegistration(registration.id)
343
367
  await registration.delete()
344
368
  member.registrations = member.registrations.filter(r => r.id !== deleteId)
345
369
 
346
- const oldGroup = await getGroup(registration.groupId)
347
370
  if (oldGroup) {
348
371
  // We need to update this group occupancy because we moved one member away from it
349
372
  updateGroups.set(oldGroup.id, oldGroup)
@@ -358,6 +381,13 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
358
381
  if (!group || group.organizationId !== struct.organizationId || !await Context.auth.canAccessGroup(group, PermissionLevel.Write)) {
359
382
  throw Context.auth.error("Je hebt niet voldoende rechten om inschrijvingen in deze groep te maken")
360
383
  }
384
+ const period = await RegistrationPeriod.getByID(group.periodId)
385
+ if (!period || period.locked) {
386
+ throw new SimpleError({
387
+ code: "period_locked",
388
+ message: "Deze inschrijvingsperiode is afgesloten en staat geen wijzigingen meer toe.",
389
+ })
390
+ }
361
391
 
362
392
  const reg = await this.addRegistration(member, struct, group)
363
393
  balanceItemMemberIds.push(member.id)
@@ -1,9 +1,10 @@
1
1
  import { AutoEncoderPatchType, Decoder, patchObject } from "@simonbackx/simple-encoding";
2
2
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
- import { Platform } from "@stamhoofd/models";
3
+ import { Platform, RegistrationPeriod } from "@stamhoofd/models";
4
4
  import { Platform as PlatformStruct } from "@stamhoofd/structures";
5
5
 
6
6
  import { Context } from "../../../helpers/Context";
7
+ import { SimpleError } from "@simonbackx/simple-errors";
7
8
 
8
9
  type Params = Record<string, never>;
9
10
  type Query = undefined;
@@ -57,6 +58,17 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
57
58
  platform.config = patchObject(platform.config, request.body.config)
58
59
  }
59
60
 
61
+ if (request.body.period && request.body.period.id !== platform.periodId) {
62
+ const period = await RegistrationPeriod.getByID(request.body.period.id)
63
+ if (!period || period.organizationId) {
64
+ throw new SimpleError({
65
+ code: "invalid_period",
66
+ message: "Invalid period"
67
+ })
68
+ }
69
+ platform.periodId = period.id
70
+ }
71
+
60
72
  await platform.save()
61
73
  return new Response(await Platform.getSharedPrivateStruct());
62
74
  }
@@ -4,7 +4,7 @@ import { RegistrationPeriod as RegistrationPeriodStruct } from "@stamhoofd/struc
4
4
 
5
5
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
6
6
  import { Context } from '../../../helpers/Context';
7
- import { RegistrationPeriod } from '@stamhoofd/models';
7
+ import { Platform, RegistrationPeriod } from '@stamhoofd/models';
8
8
  import { SimpleError } from '@simonbackx/simple-errors';
9
9
 
10
10
  type Params = Record<string, never>;
@@ -107,6 +107,9 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
107
107
  await model.delete();
108
108
  }
109
109
 
110
+ // Clear platform cache
111
+ Platform.clearCache()
112
+
110
113
  return new Response(
111
114
  periods.map(p => p.getStructure())
112
115
  );
@@ -1,8 +1,8 @@
1
1
  import { AutoEncoderPatchType, Decoder, ObjectData, 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
- import { Group, Organization,PayconiqPayment, Platform, StripeAccount, Token, User, Webshop } from '@stamhoofd/models';
5
- import { BuckarooSettings, GroupPrivateSettings, Organization as OrganizationStruct, OrganizationPatch, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, Permissions, PermissionsResourceType,ResourcePermissions, UserPermissions, Version, OrganizationMetaData } from "@stamhoofd/structures";
4
+ import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, User, Webshop } from '@stamhoofd/models';
5
+ import { BuckarooSettings, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, UserPermissions } from "@stamhoofd/structures";
6
6
  import { Formatter } from '@stamhoofd/utility';
7
7
 
8
8
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
@@ -297,6 +297,36 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
297
297
  organization.uri = request.body.uri
298
298
  }
299
299
 
300
+ if (request.body.period && request.body.period.id !== organization.periodId) {
301
+ const organizationPeriod = await OrganizationRegistrationPeriod.getByID(request.body.period.id)
302
+ if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
303
+ throw new SimpleError({
304
+ code: "invalid_field",
305
+ message: "De periode die je wilt instellen bestaat niet (meer)",
306
+ field: "period"
307
+ })
308
+ }
309
+
310
+ const period = await RegistrationPeriod.getByID(organizationPeriod.periodId)
311
+ if (!period || (period.organizationId && period.organizationId !== organization.id)) {
312
+ throw new SimpleError({
313
+ code: "invalid_field",
314
+ message: "De periode die je wilt instellen bestaat niet (meer)",
315
+ field: "period"
316
+ })
317
+ }
318
+
319
+ if (period.locked) {
320
+ throw new SimpleError({
321
+ code: "invalid_field",
322
+ message: "De periode die je wilt instellen is reeds afgesloten",
323
+ field: "period"
324
+ })
325
+ }
326
+
327
+ organization.periodId = period.id
328
+ }
329
+
300
330
  // Save the organization
301
331
  await organization.save()
302
332
  } else {
@@ -3,7 +3,7 @@ import { GroupPrivateSettings, OrganizationRegistrationPeriod as OrganizationReg
3
3
 
4
4
  import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from "@simonbackx/simple-encoding";
5
5
  import { Context } from "../../../../helpers/Context";
6
- import { Group, OrganizationRegistrationPeriod, RegistrationPeriod } from "@stamhoofd/models";
6
+ import { Group, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from "@stamhoofd/models";
7
7
  import { SimpleError } from "@simonbackx/simple-errors";
8
8
 
9
9
  type Params = Record<string, never>;
@@ -37,10 +37,70 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
37
37
 
38
38
  if (!await Context.auth.hasFullAccess(organization.id)) {
39
39
  throw Context.auth.error()
40
- }
40
+ }
41
+
42
+ const platform = await Platform.getShared()
43
+
44
+ function validateDefaultGroupId(id: string|null): string|null {
45
+ if (id === null) {
46
+ return id;
47
+ }
48
+
49
+ if (platform.config.defaultAgeGroups.find(g => g.id === id)) {
50
+ return id;
51
+ }
52
+
53
+ throw new SimpleError({
54
+ code: "invalid_default_age_group",
55
+ message: "Invalid default age group",
56
+ human: "De standaard leeftijdsgroep is ongeldig",
57
+ statusCode: 400
58
+ })
59
+ }
41
60
 
42
61
  const structs: OrganizationRegistrationPeriodStruct[] = [];
43
62
 
63
+ for (const {put} of request.body.getPuts()) {
64
+ if (!await Context.auth.hasFullAccess(organization.id)) {
65
+ throw Context.auth.error()
66
+ }
67
+ const period = await RegistrationPeriod.getByID(put.period.id);
68
+
69
+ if (!period) {
70
+ throw new SimpleError({
71
+ code: "not_found",
72
+ message: "Period not found",
73
+ statusCode: 404
74
+ })
75
+ }
76
+
77
+ const organizationPeriod = new OrganizationRegistrationPeriod();
78
+ organizationPeriod.id = put.id;
79
+ organizationPeriod.organizationId = organization.id;
80
+ organizationPeriod.periodId = put.period.id;
81
+ organizationPeriod.settings = put.settings;
82
+ await organizationPeriod.save();
83
+
84
+ for (const struct of put.groups) {
85
+ const model = new Group()
86
+ model.id = struct.id
87
+ model.organizationId = organization.id
88
+ model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
89
+ model.periodId = organizationPeriod.periodId
90
+ model.settings = struct.settings
91
+ model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
92
+ model.status = struct.status
93
+ await model.updateOccupancy()
94
+ await model.save();
95
+ }
96
+ const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
97
+
98
+ // Delete unreachable categories first
99
+ await organizationPeriod.cleanCategories(groups);
100
+ await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
101
+ structs.push(organizationPeriod.getStructure(period, groups));
102
+ }
103
+
44
104
  for (const patch of request.body.getPatches()) {
45
105
  const organizationPeriod = await OrganizationRegistrationPeriod.getByID(patch.id);
46
106
  if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
@@ -112,6 +172,7 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
112
172
  const model = new Group()
113
173
  model.id = struct.id
114
174
  model.organizationId = organization.id
175
+ model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
115
176
  model.periodId = organizationPeriod.periodId
116
177
  model.settings = struct.settings
117
178
  model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
@@ -181,6 +242,10 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
181
242
  if (struct.deletedAt !== undefined) {
182
243
  model.deletedAt = struct.deletedAt
183
244
  }
245
+
246
+ if (struct.defaultAgeGroupId !== undefined) {
247
+ model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
248
+ }
184
249
 
185
250
  await model.updateOccupancy()
186
251
  await model.save();
@@ -191,15 +256,13 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
191
256
 
192
257
  if (deleteUnreachable) {
193
258
  // Delete unreachable categories first
194
- await organization.cleanCategories(groups);
259
+ await organizationPeriod.cleanCategories(groups);
195
260
  await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
196
261
  }
197
262
 
198
263
  if (period) {
199
-
200
264
  structs.push(organizationPeriod.getStructure(period, groups));
201
265
  }
202
-
203
266
  }
204
267
 
205
268
  return new Response(
@@ -144,7 +144,7 @@ export class AuthenticatedStructures {
144
144
  }
145
145
 
146
146
  // Load responsibilities
147
- const responsibilities = await MemberResponsibilityRecord.where({ memberId: { sign: 'IN', value: members.map(m => m.id) } })
147
+ const responsibilities = members.length > 0 ? await MemberResponsibilityRecord.where({ memberId: { sign: 'IN', value: members.map(m => m.id) } }) : []
148
148
 
149
149
  for (const blob of memberBlobs) {
150
150
  blob.responsibilities = responsibilities.filter(r => r.memberId == blob.id).map(r => MemberResponsibilityRecordStruct.create(r))