@stamhoofd/backend 2.3.1 → 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.
- package/index.ts +3 -0
- package/package.json +4 -4
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +63 -2
- package/src/endpoints/auth/CreateAdminEndpoint.ts +6 -3
- package/src/endpoints/auth/GetOtherUserEndpoint.ts +41 -0
- package/src/endpoints/auth/GetUserEndpoint.ts +6 -28
- package/src/endpoints/auth/PatchUserEndpoint.ts +25 -6
- package/src/endpoints/auth/SignupEndpoint.ts +2 -2
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +120 -0
- package/src/endpoints/global/email/GetEmailEndpoint.ts +51 -0
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +108 -0
- package/src/endpoints/global/events/GetEventsEndpoint.ts +223 -0
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +319 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +124 -48
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +86 -109
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +2 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -2
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +43 -25
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +26 -7
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +23 -22
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +136 -123
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +2 -1
- package/src/helpers/AdminPermissionChecker.ts +54 -3
- package/src/helpers/AuthenticatedStructures.ts +88 -23
- package/src/helpers/Context.ts +4 -0
- package/src/helpers/EmailResumer.ts +17 -0
- package/src/helpers/MemberUserSyncer.ts +221 -0
- 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 {
|
|
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
|
|
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
|
|
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.
|
|
55
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
40
56
|
await Context.authenticate()
|
|
41
57
|
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
31
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
32
32
|
await Context.authenticate()
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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:
|
|
39
|
+
users: await AuthenticatedStructures.usersWithMembers(admins)
|
|
39
40
|
}));
|
|
40
41
|
}
|
|
41
42
|
}
|