@stamhoofd/backend 2.3.1 → 2.5.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 (39) hide show
  1. package/index.ts +3 -0
  2. package/package.json +4 -4
  3. package/src/endpoints/admin/invoices/GetInvoicesEndpoint.ts +1 -1
  4. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +63 -2
  5. package/src/endpoints/auth/CreateAdminEndpoint.ts +6 -3
  6. package/src/endpoints/auth/GetOtherUserEndpoint.ts +41 -0
  7. package/src/endpoints/auth/GetUserEndpoint.ts +6 -28
  8. package/src/endpoints/auth/PatchUserEndpoint.ts +25 -6
  9. package/src/endpoints/auth/SignupEndpoint.ts +2 -2
  10. package/src/endpoints/global/email/CreateEmailEndpoint.ts +120 -0
  11. package/src/endpoints/global/email/GetEmailEndpoint.ts +51 -0
  12. package/src/endpoints/global/email/PatchEmailEndpoint.ts +108 -0
  13. package/src/endpoints/global/events/GetEventsEndpoint.ts +223 -0
  14. package/src/endpoints/global/events/PatchEventsEndpoint.ts +319 -0
  15. package/src/endpoints/global/members/GetMembersEndpoint.ts +124 -48
  16. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +107 -117
  17. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +3 -2
  18. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -1
  19. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +2 -1
  20. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +2 -1
  21. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -0
  22. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -2
  23. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +345 -176
  24. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +5 -5
  25. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +1 -1
  26. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +43 -25
  27. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +26 -7
  28. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +23 -22
  29. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -121
  30. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +8 -8
  31. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +2 -1
  32. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +2 -1
  33. package/src/helpers/AdminPermissionChecker.ts +60 -3
  34. package/src/helpers/AuthenticatedStructures.ts +164 -37
  35. package/src/helpers/Context.ts +4 -0
  36. package/src/helpers/EmailResumer.ts +17 -0
  37. package/src/helpers/MemberUserSyncer.ts +221 -0
  38. package/src/seeds/1722256498-group-update-occupancy.ts +52 -0
  39. package/src/seeds/1722344160-update-membership.ts +57 -0
@@ -78,7 +78,7 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
78
78
  }
79
79
 
80
80
  return new Response(GetWebshopFromDomainResult.create({
81
- organization: await organization.getStructure({emptyGroups: true}),
81
+ organization: organization.getBaseStructure(),
82
82
  webshop: WebshopStruct.create(webshop)
83
83
  }));
84
84
  }
@@ -113,14 +113,14 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
113
113
  if (!webshop) {
114
114
  // Return organization, so we know the locale + can do some custom logic
115
115
  return new Response(GetWebshopFromDomainResult.create({
116
- organization: await organization.getStructure({emptyGroups: true}),
116
+ organization: organization.getBaseStructure(),
117
117
  webshop: null,
118
118
  webshops: []
119
119
  }));
120
120
  }
121
121
 
122
122
  return new Response(GetWebshopFromDomainResult.create({
123
- organization: await organization.getStructure({emptyGroups: true}),
123
+ organization: organization.getBaseStructure(),
124
124
  webshop: WebshopStruct.create(webshop)
125
125
  }));
126
126
  }
@@ -156,7 +156,7 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
156
156
 
157
157
  // Return organization, and the known webshops on this domain
158
158
  return new Response(GetWebshopFromDomainResult.create({
159
- organization: await organization.getStructure({emptyGroups: true}),
159
+ organization: organization.getBaseStructure(),
160
160
  webshop: null,
161
161
  webshops: webshops.map(w => WebshopPreview.create(w)).filter(w => w.isClosed(0) === false).sort((a, b) => Sorter.byStringValue(a.meta.name, b.meta.name))
162
162
  }));
@@ -180,7 +180,7 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
180
180
  }
181
181
 
182
182
  return new Response(GetWebshopFromDomainResult.create({
183
- organization: await organization.getStructure({emptyGroups: true}),
183
+ organization: organization.getBaseStructure(),
184
184
  webshop: WebshopStruct.create(webshop)
185
185
  }));
186
186
  }
@@ -55,7 +55,7 @@ export class EmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
55
55
  return [false];
56
56
  }
57
57
 
58
- const params = Endpoint.parseParameters(request.url, "/email", {});
58
+ const params = Endpoint.parseParameters(request.url, "/email/legacy", {});
59
59
 
60
60
  if (params) {
61
61
  return [true, params as Params];
@@ -1,7 +1,6 @@
1
- import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
1
+ import { AutoEncoder, Data, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
- import { SimpleError } from '@simonbackx/simple-errors';
4
- import { EmailTemplate, Token } from '@stamhoofd/models';
3
+ import { EmailTemplate } from '@stamhoofd/models';
5
4
  import { EmailTemplate as EmailTemplateStruct, EmailTemplateType } from '@stamhoofd/structures';
6
5
 
7
6
  import { Context } from '../../../../helpers/Context';
@@ -9,12 +8,29 @@ import { Context } from '../../../../helpers/Context';
9
8
  type Params = Record<string, never>;
10
9
  type Body = undefined;
11
10
 
11
+ export class StringNullableDecoder<T> implements Decoder<T | null> {
12
+ decoder: Decoder<T>;
13
+
14
+ constructor(decoder: Decoder<T>) {
15
+ this.decoder = decoder;
16
+ }
17
+
18
+ decode(data: Data): T | null {
19
+ if (data.value === 'null') {
20
+ return null;
21
+ }
22
+
23
+ return data.decode(this.decoder);
24
+ }
25
+ }
26
+
27
+
12
28
  class Query extends AutoEncoder {
13
- @field({ decoder: StringDecoder, optional: true })
14
- webshopId?: string
29
+ @field({ decoder: new StringNullableDecoder(StringDecoder), optional: true, nullable: true })
30
+ webshopId: string|null = null
15
31
 
16
- @field({ decoder: StringDecoder, optional: true })
17
- groupId?: string
32
+ @field({ decoder: new StringNullableDecoder(StringDecoder), optional: true, nullable: true})
33
+ groupId: string|null = null
18
34
  }
19
35
 
20
36
  type ResponseBody = EmailTemplateStruct[];
@@ -36,27 +52,29 @@ export class GetEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, Res
36
52
  }
37
53
 
38
54
  async handle(request: DecodedRequest<Params, Query, Body>) {
39
- const organization = await Context.setOrganizationScope();
55
+ const organization = await Context.setOptionalOrganizationScope();
40
56
  await Context.authenticate()
41
57
 
42
- if (!await Context.auth.canReadEmailTemplates(organization.id)) {
43
- throw Context.auth.error()
44
- }
45
-
46
- const types = [
47
- EmailTemplateType.OrderConfirmationOnline,
48
- EmailTemplateType.OrderConfirmationTransfer,
49
- EmailTemplateType.OrderConfirmationPOS,
50
- EmailTemplateType.OrderReceivedTransfer,
51
- EmailTemplateType.TicketsConfirmation,
52
- EmailTemplateType.TicketsConfirmationTransfer,
53
- EmailTemplateType.TicketsConfirmationPOS,
54
- EmailTemplateType.TicketsReceivedTransfer,
55
- EmailTemplateType.RegistrationConfirmation
56
- ]
58
+ if (organization) {
59
+ if (!await Context.auth.canReadEmailTemplates(organization.id)) {
60
+ throw Context.auth.error()
61
+ }
62
+ } else {
63
+ if (!Context.auth.hasPlatformFullAccess()) {
64
+ throw Context.auth.error()
65
+ }
66
+ }
67
+
68
+ const types = [...Object.values(EmailTemplateType)].filter(type => {
69
+ if (!organization) {
70
+ return true;
71
+ }
72
+ return EmailTemplateStruct.allowOrganizationLevel(type)
73
+ })
74
+
57
75
 
58
- const templates = await EmailTemplate.where({ organizationId: organization.id, webshopId: request.query.webshopId ?? null, groupId: request.query.groupId ?? null, type: {sign: 'IN', value: types}});
76
+ const templates = organization ? (await EmailTemplate.where({ organizationId: organization.id, webshopId: request.query.webshopId ?? null, groupId: request.query.groupId ?? null, type: {sign: 'IN', value: types}})) : [];
59
77
  const defaultTemplates = await EmailTemplate.where({ organizationId: null, type: {sign: 'IN', value: types} });
60
78
  return new Response([...templates, ...defaultTemplates].map(template => EmailTemplateStruct.create(template)))
61
79
  }
62
- }
80
+ }
@@ -28,13 +28,18 @@ export class PatchEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, R
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
- // Fast throw first (more in depth checking for patches later)
35
- if (!await Context.auth.canReadEmailTemplates(organization.id)) {
36
- throw Context.auth.error()
37
- }
34
+ if (organization) {
35
+ if (!await Context.auth.canReadEmailTemplates(organization.id)) {
36
+ throw Context.auth.error()
37
+ }
38
+ } else {
39
+ if (!Context.auth.hasPlatformFullAccess()) {
40
+ throw Context.auth.error()
41
+ }
42
+ }
38
43
 
39
44
  const templates: EmailTemplate[] = []
40
45
 
@@ -57,9 +62,14 @@ export class PatchEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, R
57
62
 
58
63
  for (const put of request.body.getPuts()) {
59
64
  const struct = put.put
65
+
66
+ if (!EmailTemplateStruct.allowOrganizationLevel(struct.type) && organization) {
67
+ throw Context.auth.error();
68
+ }
69
+
60
70
  const template = new EmailTemplate()
61
71
  template.id = struct.id
62
- template.organizationId = organization.id
72
+ template.organizationId = organization?.id ?? null
63
73
  template.webshopId = struct.webshopId
64
74
  template.groupId = struct.groupId
65
75
 
@@ -79,7 +89,16 @@ export class PatchEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, R
79
89
 
80
90
  templates.push(template)
81
91
  }
92
+
93
+ for (const id of request.body.getDeletes()) {
94
+ const template = await EmailTemplate.getByID(id)
95
+ if (!template || !(await Context.auth.canAccessEmailTemplate(template, PermissionLevel.Write))) {
96
+ throw Context.auth.notFoundOrNoAccess("Je hebt geen toegang om deze emailtemplate te verwijderen")
97
+ }
98
+
99
+ await template.delete()
100
+ }
82
101
 
83
102
  return new Response(templates.map(template => EmailTemplateStruct.create(template)))
84
103
  }
85
- }
104
+ }
@@ -99,6 +99,8 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
99
99
  if (request.body.privateMeta && request.body.privateMeta.isPatch()) {
100
100
  organization.privateMeta.emails = request.body.privateMeta.emails.applyTo(organization.privateMeta.emails)
101
101
  organization.privateMeta.roles = request.body.privateMeta.roles.applyTo(organization.privateMeta.roles)
102
+ organization.privateMeta.responsibilities = request.body.privateMeta.responsibilities.applyTo(organization.privateMeta.responsibilities)
103
+ organization.privateMeta.inheritedResponsibilityRoles = request.body.privateMeta.inheritedResponsibilityRoles.applyTo(organization.privateMeta.inheritedResponsibilityRoles)
102
104
  organization.privateMeta.privateKey = request.body.privateMeta.privateKey ?? organization.privateMeta.privateKey
103
105
  organization.privateMeta.featureFlags = patchObject(organization.privateMeta.featureFlags, request.body.privateMeta.featureFlags);
104
106
 
@@ -181,28 +183,27 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
181
183
 
182
184
  // Allow admin patches (permissions only atm). No put atm
183
185
  if (request.body.admins) {
184
- for (const patch of request.body.admins.getPatches()) {
185
- if (patch.permissions) {
186
- const admin = await User.getByID(patch.id)
187
- if (!admin || !await Context.auth.canAccessUser(admin, PermissionLevel.Full)) {
188
- throw new SimpleError({
189
- code: "invalid_field",
190
- message: "De beheerder die je wilt wijzigen bestaat niet (meer)",
191
- field: "admins"
192
- })
193
- }
194
-
195
- admin.permissions = UserPermissions.limitedPatch(admin.permissions, patch.permissions, organization.id)
196
-
197
- if (admin.id === user.id && (!admin.permissions || !admin.permissions.forOrganization(organization)?.hasFullAccess())) {
198
- throw new SimpleError({
199
- code: "permission_denied",
200
- message: "Je kan jezelf niet verwijderen als hoofdbeheerder"
201
- })
202
- }
203
- await admin.save()
204
- }
205
- }
186
+ throw new Error("Temporary removed to keep code cleaner. Please use different endpoints.")
187
+ // for (const patch of request.body.admins.getPatches()) {
188
+ // if (patch.permissions) {
189
+ // const admin = await User.getByID(patch.id)
190
+ // if (!admin || !await Context.auth.canAccessUser(admin, PermissionLevel.Full)) {
191
+ // throw new SimpleError({
192
+ // code: "invalid_field",
193
+ // message: "De beheerder die je wilt wijzigen bestaat niet (meer)",
194
+ // field: "admins"
195
+ // })
196
+ // }
197
+ // admin.permissions = UserPermissions.limitedPatch(admin.permissions, patch.permissions, organization.id)
198
+ // if (admin.id === user.id && (!admin.permissions || !admin.permissions.forOrganization(organization)?.hasFullAccess())) {
199
+ // throw new SimpleError({
200
+ // code: "permission_denied",
201
+ // message: "Je kan jezelf niet verwijderen als hoofdbeheerder"
202
+ // })
203
+ // }
204
+ // await admin.save()
205
+ // }
206
+ // }
206
207
  }
207
208
 
208
209
  if (request.body.meta) {