@stamhoofd/backend 2.2.0 → 2.4.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 (33) hide show
  1. package/.env.json +2 -2
  2. package/index.ts +3 -0
  3. package/package.json +4 -4
  4. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +63 -2
  5. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +3 -0
  6. package/src/endpoints/auth/CreateAdminEndpoint.ts +6 -3
  7. package/src/endpoints/auth/GetOtherUserEndpoint.ts +41 -0
  8. package/src/endpoints/auth/GetUserEndpoint.ts +6 -28
  9. package/src/endpoints/auth/PatchUserEndpoint.ts +25 -6
  10. package/src/endpoints/auth/SignupEndpoint.ts +2 -2
  11. package/src/endpoints/global/email/CreateEmailEndpoint.ts +120 -0
  12. package/src/endpoints/global/email/GetEmailEndpoint.ts +51 -0
  13. package/src/endpoints/global/email/PatchEmailEndpoint.ts +108 -0
  14. package/src/endpoints/global/events/GetEventsEndpoint.ts +223 -0
  15. package/src/endpoints/global/events/PatchEventsEndpoint.ts +319 -0
  16. package/src/endpoints/global/members/GetMembersEndpoint.ts +124 -48
  17. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +86 -109
  18. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +2 -1
  19. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -0
  20. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -2
  21. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +1 -1
  22. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +43 -25
  23. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +26 -7
  24. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +23 -22
  25. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +136 -123
  26. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +8 -8
  27. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +2 -1
  28. package/src/helpers/AdminPermissionChecker.ts +54 -3
  29. package/src/helpers/AuthenticatedStructures.ts +88 -23
  30. package/src/helpers/Context.ts +4 -0
  31. package/src/helpers/EmailResumer.ts +17 -0
  32. package/src/helpers/MemberUserSyncer.ts +221 -0
  33. package/src/seeds/1722256498-group-update-occupancy.ts +52 -0
@@ -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) {
@@ -1,9 +1,9 @@
1
1
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
- import { GroupPrivateSettings, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from "@stamhoofd/structures";
2
+ import { Group as GroupStruct, GroupPrivateSettings, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from "@stamhoofd/structures";
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, Platform, RegistrationPeriod } from "@stamhoofd/models";
6
+ import { Group, Member, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from "@stamhoofd/models";
7
7
  import { SimpleError } from "@simonbackx/simple-errors";
8
8
 
9
9
  type Params = Record<string, never>;
@@ -15,7 +15,7 @@ type ResponseBody = OrganizationRegistrationPeriodStruct[]
15
15
  * One endpoint to create, patch and delete members and their registrations and payments
16
16
  */
17
17
 
18
- export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
18
+ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
19
19
  bodyDecoder = new PatchableArrayDecoder(OrganizationRegistrationPeriodStruct as Decoder<OrganizationRegistrationPeriodStruct>, OrganizationRegistrationPeriodStruct.patchType() as Decoder<AutoEncoderPatchType<OrganizationRegistrationPeriodStruct>>, StringDecoder)
20
20
 
21
21
  protected doesMatch(request: Request): [true, Params] | [false] {
@@ -39,25 +39,6 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
39
39
  throw Context.auth.error()
40
40
  }
41
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
- }
60
-
61
42
  const structs: OrganizationRegistrationPeriodStruct[] = [];
62
43
 
63
44
  for (const {put} of request.body.getPuts()) {
@@ -82,23 +63,14 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
82
63
  await organizationPeriod.save();
83
64
 
84
65
  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();
66
+ await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(struct, organization.id, organizationPeriod.periodId)
95
67
  }
96
68
  const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
97
69
 
98
70
  // Delete unreachable categories first
99
71
  await organizationPeriod.cleanCategories(groups);
100
72
  await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
101
- structs.push(organizationPeriod.getStructure(period, groups));
73
+ structs.push(organizationPeriod.getPrivateStructure(period, groups));
102
74
  }
103
75
 
104
76
  for (const patch of request.body.getPatches()) {
@@ -152,103 +124,17 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
152
124
  const deleteGroups = patch.groups.getDeletes()
153
125
  if (deleteGroups.length > 0) {
154
126
  for (const id of deleteGroups) {
155
- const model = await Group.getByID(id)
156
- if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
157
- throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te verwijderen')
158
- }
159
-
160
- model.deletedAt = new Date()
161
- await model.save()
127
+ await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(id)
162
128
  deleteUnreachable = true
163
129
  }
164
130
  }
165
131
 
166
132
  for (const groupPut of patch.groups.getPuts()) {
167
- if (!await Context.auth.hasFullAccess(organization.id) && !allowedIds.includes(groupPut.put.id)) {
168
- throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen')
169
- }
170
-
171
- const struct = groupPut.put
172
- const model = new Group()
173
- model.id = struct.id
174
- model.organizationId = organization.id
175
- model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
176
- model.periodId = organizationPeriod.periodId
177
- model.settings = struct.settings
178
- model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
179
- model.status = struct.status
180
-
181
- if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
182
- // Create a temporary permission role for this user
183
- const organizationPermissions = user.permissions?.organizationPermissions?.get(organization.id)
184
- if (!organizationPermissions) {
185
- throw new Error('Unexpected missing permissions')
186
- }
187
- const resourcePermissions = ResourcePermissions.create({
188
- resourceName: model.settings.name,
189
- level: PermissionLevel.Full
190
- })
191
- const patch = resourcePermissions.createInsertPatch(PermissionsResourceType.Groups, model.id, organizationPermissions)
192
- user.permissions!.organizationPermissions.set(organization.id, organizationPermissions.patch(patch))
193
- console.log('Automatically granted author full permissions to resource', 'group', model.id, 'user', user.id, 'patch', patch.encode({version: Version}))
194
- await user.save()
195
- }
196
-
197
- // Check if current user has permissions to this new group -> else fail with error
198
- if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
199
- throw new SimpleError({
200
- code: "missing_permissions",
201
- message: "You cannot restrict your own permissions",
202
- human: "Je kan geen inschrijvingsgroep maken zonder dat je zelf volledige toegang hebt tot de nieuwe groep"
203
- })
204
- continue;
205
- }
206
-
207
- await model.updateOccupancy()
208
- await model.save();
133
+ await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(groupPut.put, organization.id, organizationPeriod.periodId, {allowedIds})
209
134
  }
210
135
 
211
136
  for (const struct of patch.groups.getPatches()) {
212
- const model = await Group.getByID(struct.id)
213
-
214
- if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
215
- throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te wijzigen')
216
- }
217
-
218
- if (struct.settings) {
219
- model.settings.patchOrPut(struct.settings)
220
- }
221
-
222
- if (struct.status) {
223
- model.status = struct.status
224
- }
225
-
226
- if (struct.privateSettings) {
227
- model.privateSettings.patchOrPut(struct.privateSettings)
228
-
229
- if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
230
- throw new SimpleError({
231
- code: "missing_permissions",
232
- message: "You cannot restrict your own permissions",
233
- human: "Je kan je eigen volledige toegang tot deze inschrijvingsgroep niet verwijderen. Vraag aan een hoofdbeheerder om jouw toegang te verwijderen."
234
- })
235
- }
236
- }
237
-
238
- if (struct.cycle !== undefined) {
239
- model.cycle = struct.cycle
240
- }
241
-
242
- if (struct.deletedAt !== undefined) {
243
- model.deletedAt = struct.deletedAt
244
- }
245
-
246
- if (struct.defaultAgeGroupId !== undefined) {
247
- model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
248
- }
249
-
250
- await model.updateOccupancy()
251
- await model.save();
137
+ await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(struct)
252
138
  }
253
139
 
254
140
  const period = await RegistrationPeriod.getByID(organizationPeriod.periodId);
@@ -261,7 +147,7 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
261
147
  }
262
148
 
263
149
  if (period) {
264
- structs.push(organizationPeriod.getStructure(period, groups));
150
+ structs.push(organizationPeriod.getPrivateStructure(period, groups));
265
151
  }
266
152
  }
267
153
 
@@ -270,4 +156,131 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
270
156
  );
271
157
  }
272
158
 
159
+ static async validateDefaultGroupId(id: string|null): Promise<string|null> {
160
+ if (id === null) {
161
+ return id;
162
+ }
163
+ const platform = await Platform.getSharedStruct()
164
+
165
+ if (platform.config.defaultAgeGroups.find(g => g.id === id)) {
166
+ return id;
167
+ }
168
+
169
+ throw new SimpleError({
170
+ code: "invalid_default_age_group",
171
+ message: "Invalid default age group",
172
+ human: "De standaard leeftijdsgroep is ongeldig",
173
+ statusCode: 400
174
+ })
175
+ }
176
+
177
+ static async deleteGroup(id: string) {
178
+ const model = await Group.getByID(id)
179
+ if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
180
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te verwijderen')
181
+ }
182
+
183
+ model.deletedAt = new Date()
184
+ await model.save()
185
+ Member.updateMembershipsForGroupId(id)
186
+ }
187
+
188
+ static async patchGroup(struct: AutoEncoderPatchType<GroupStruct>) {
189
+ const model = await Group.getByID(struct.id)
190
+
191
+ if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
192
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te wijzigen')
193
+ }
194
+
195
+ if (struct.settings) {
196
+ model.settings.patchOrPut(struct.settings)
197
+ }
198
+
199
+ if (struct.status) {
200
+ model.status = struct.status
201
+ }
202
+
203
+ if (struct.privateSettings) {
204
+ model.privateSettings.patchOrPut(struct.privateSettings)
205
+
206
+ if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
207
+ throw new SimpleError({
208
+ code: "missing_permissions",
209
+ message: "You cannot restrict your own permissions",
210
+ human: "Je kan je eigen volledige toegang tot deze inschrijvingsgroep niet verwijderen. Vraag aan een hoofdbeheerder om jouw toegang te verwijderen."
211
+ })
212
+ }
213
+ }
214
+
215
+ if (struct.cycle !== undefined) {
216
+ model.cycle = struct.cycle
217
+ }
218
+
219
+ if (struct.deletedAt !== undefined) {
220
+ model.deletedAt = struct.deletedAt
221
+ }
222
+
223
+ if (struct.defaultAgeGroupId !== undefined) {
224
+ model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId)
225
+ }
226
+
227
+ await model.updateOccupancy()
228
+ await model.save();
229
+ Member.updateMembershipsForGroupId(model.id)
230
+ }
231
+
232
+
233
+ static async createGroup(struct: GroupStruct, organizationId: string, periodId: string, options?: {allowedIds?: string[]}): Promise<Group> {
234
+ const allowedIds = options?.allowedIds ?? []
235
+
236
+ if (!await Context.auth.hasFullAccess(organizationId)) {
237
+ if (allowedIds.includes(struct.id)) {
238
+ // Ok
239
+ } else {
240
+ throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen')
241
+ }
242
+ }
243
+
244
+ const user = Context.auth.user
245
+
246
+ const model = new Group()
247
+ model.id = struct.id
248
+ model.organizationId = organizationId
249
+ model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId)
250
+ model.periodId = periodId
251
+ model.settings = struct.settings
252
+ model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
253
+ model.status = struct.status
254
+ model.type = struct.type
255
+
256
+ if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
257
+ // Create a temporary permission role for this user
258
+ const organizationPermissions = user.permissions?.organizationPermissions?.get(organizationId)
259
+ if (!organizationPermissions) {
260
+ throw new Error('Unexpected missing permissions')
261
+ }
262
+ const resourcePermissions = ResourcePermissions.create({
263
+ resourceName: model.settings.name,
264
+ level: PermissionLevel.Full
265
+ })
266
+ const patch = resourcePermissions.createInsertPatch(PermissionsResourceType.Groups, model.id, organizationPermissions)
267
+ user.permissions!.organizationPermissions.set(organizationId, organizationPermissions.patch(patch))
268
+ console.log('Automatically granted author full permissions to resource', 'group', model.id, 'user', user.id, 'patch', patch.encode({version: Version}))
269
+ await user.save()
270
+ }
271
+
272
+ // Check if current user has permissions to this new group -> else fail with error
273
+ if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
274
+ throw new SimpleError({
275
+ code: "missing_permissions",
276
+ message: "You cannot restrict your own permissions",
277
+ human: "Je kan geen inschrijvingsgroep maken zonder dat je zelf volledige toegang hebt tot de nieuwe groep"
278
+ })
279
+ }
280
+
281
+ await model.updateOccupancy()
282
+ await model.save();
283
+ return model;
284
+ }
285
+
273
286
  }
@@ -1,10 +1,10 @@
1
1
 
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
- import { SimpleError } from '@simonbackx/simple-errors';
4
3
  import { StripeAccount } from '@stamhoofd/models';
5
4
  import { PermissionLevel } from '@stamhoofd/structures';
6
5
 
7
6
  import { Context } from '../../../../helpers/Context';
7
+ import { StripeHelper } from '../../../../helpers/StripeHelper';
8
8
 
9
9
  type Params = { id: string };
10
10
  type Body = undefined;
@@ -42,13 +42,13 @@ export class DeleteStripeAccountEndpoint extends Endpoint<Params, Query, Body, R
42
42
  }
43
43
 
44
44
  // For now we don't delete them in Stripe because this causes issues with data access
45
- // const stripe = StripeHelper.getInstance()
46
- //
47
- // try {
48
- // await stripe.accounts.del(model.accountId);
49
- // } catch (e) {
50
- // console.error('Tried deleting account but failed', e)
51
- // }
45
+ const stripe = StripeHelper.getInstance()
46
+
47
+ try {
48
+ await stripe.accounts.del(model.accountId);
49
+ } catch (e) {
50
+ console.error('Tried deleting account but failed', e)
51
+ }
52
52
 
53
53
  // If that succeeded
54
54
  model.status = "deleted"
@@ -3,6 +3,7 @@ import { User } from '@stamhoofd/models';
3
3
  import { OrganizationAdmins, User as UserStruct } from "@stamhoofd/structures";
4
4
 
5
5
  import { Context } from "../../../../helpers/Context";
6
+ import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
6
7
  type Params = Record<string, never>;
7
8
  type Query = undefined;
8
9
  type Body = undefined
@@ -35,7 +36,7 @@ export class GetOrganizationAdminsEndpoint extends Endpoint<Params, Query, Body,
35
36
  const admins = await User.getAdmins([organization.id])
36
37
 
37
38
  return new Response(OrganizationAdmins.create({
38
- users: admins.map(a => UserStruct.create({...a, hasAccount: a.hasAccount()})),
39
+ users: await AuthenticatedStructures.usersWithMembers(admins)
39
40
  }));
40
41
  }
41
42
  }