@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.
- package/index.ts +3 -0
- package/package.json +4 -4
- package/src/endpoints/admin/invoices/GetInvoicesEndpoint.ts +1 -1
- 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 +107 -117
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +3 -2
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -1
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +2 -1
- 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/global/registration/RegisterMembersEndpoint.ts +345 -176
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +5 -5
- 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 +210 -121
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +2 -1
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +2 -1
- package/src/helpers/AdminPermissionChecker.ts +60 -3
- package/src/helpers/AuthenticatedStructures.ts +164 -37
- 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
- package/src/seeds/1722344160-update-membership.ts +57 -0
|
@@ -1,10 +1,11 @@
|
|
|
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, GroupType } 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
|
+
import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
|
|
8
9
|
|
|
9
10
|
type Params = Record<string, never>;
|
|
10
11
|
type Query = undefined;
|
|
@@ -15,7 +16,7 @@ type ResponseBody = OrganizationRegistrationPeriodStruct[]
|
|
|
15
16
|
* One endpoint to create, patch and delete members and their registrations and payments
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
export class
|
|
19
|
+
export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
19
20
|
bodyDecoder = new PatchableArrayDecoder(OrganizationRegistrationPeriodStruct as Decoder<OrganizationRegistrationPeriodStruct>, OrganizationRegistrationPeriodStruct.patchType() as Decoder<AutoEncoderPatchType<OrganizationRegistrationPeriodStruct>>, StringDecoder)
|
|
20
21
|
|
|
21
22
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
@@ -33,32 +34,13 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
33
34
|
|
|
34
35
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
35
36
|
const organization = await Context.setOrganizationScope();
|
|
36
|
-
|
|
37
|
+
await Context.authenticate()
|
|
37
38
|
|
|
38
39
|
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
39
40
|
throw Context.auth.error()
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const
|
|
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
|
-
const structs: OrganizationRegistrationPeriodStruct[] = [];
|
|
43
|
+
const periods: OrganizationRegistrationPeriod[] = [];
|
|
62
44
|
|
|
63
45
|
for (const {put} of request.body.getPuts()) {
|
|
64
46
|
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
@@ -82,23 +64,14 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
82
64
|
await organizationPeriod.save();
|
|
83
65
|
|
|
84
66
|
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();
|
|
67
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(struct, organization.id, organizationPeriod.periodId)
|
|
95
68
|
}
|
|
96
69
|
const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
|
|
97
70
|
|
|
98
71
|
// Delete unreachable categories first
|
|
99
72
|
await organizationPeriod.cleanCategories(groups);
|
|
100
73
|
await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
|
|
101
|
-
|
|
74
|
+
periods.push(organizationPeriod);
|
|
102
75
|
}
|
|
103
76
|
|
|
104
77
|
for (const patch of request.body.getPatches()) {
|
|
@@ -152,122 +125,238 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
152
125
|
const deleteGroups = patch.groups.getDeletes()
|
|
153
126
|
if (deleteGroups.length > 0) {
|
|
154
127
|
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()
|
|
128
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(id)
|
|
162
129
|
deleteUnreachable = true
|
|
163
130
|
}
|
|
164
131
|
}
|
|
165
132
|
|
|
166
133
|
for (const groupPut of patch.groups.getPuts()) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
134
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(groupPut.put, organization.id, organizationPeriod.periodId, {allowedIds})
|
|
135
|
+
}
|
|
170
136
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
}
|
|
137
|
+
for (const struct of patch.groups.getPatches()) {
|
|
138
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(struct)
|
|
139
|
+
}
|
|
196
140
|
|
|
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
141
|
|
|
207
|
-
|
|
208
|
-
await
|
|
142
|
+
if (deleteUnreachable) {
|
|
143
|
+
const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
|
|
144
|
+
|
|
145
|
+
// Delete unreachable categories first
|
|
146
|
+
await organizationPeriod.cleanCategories(groups);
|
|
147
|
+
await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
|
|
209
148
|
}
|
|
210
149
|
|
|
211
|
-
|
|
212
|
-
|
|
150
|
+
periods.push(organizationPeriod);
|
|
151
|
+
}
|
|
213
152
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
153
|
+
return new Response(
|
|
154
|
+
await AuthenticatedStructures.organizationRegistrationPeriods(periods),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
static async validateDefaultGroupId(id: string|null): Promise<string|null> {
|
|
159
|
+
if (id === null) {
|
|
160
|
+
return id;
|
|
161
|
+
}
|
|
162
|
+
const platform = await Platform.getSharedStruct()
|
|
163
|
+
|
|
164
|
+
if (platform.config.defaultAgeGroups.find(g => g.id === id)) {
|
|
165
|
+
return id;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
throw new SimpleError({
|
|
169
|
+
code: "invalid_default_age_group",
|
|
170
|
+
message: "Invalid default age group",
|
|
171
|
+
human: "De standaard leeftijdsgroep is ongeldig",
|
|
172
|
+
statusCode: 400
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static async deleteGroup(id: string) {
|
|
177
|
+
const model = await Group.getByID(id)
|
|
178
|
+
if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
|
|
179
|
+
throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te verwijderen')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
model.deletedAt = new Date()
|
|
183
|
+
await model.save()
|
|
184
|
+
Member.updateMembershipsForGroupId(id)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static async patchGroup(struct: AutoEncoderPatchType<GroupStruct>) {
|
|
188
|
+
const model = await Group.getByID(struct.id)
|
|
189
|
+
|
|
190
|
+
if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
|
|
191
|
+
throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te wijzigen')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (struct.settings) {
|
|
195
|
+
model.settings.patchOrPut(struct.settings)
|
|
196
|
+
}
|
|
217
197
|
|
|
218
|
-
|
|
219
|
-
|
|
198
|
+
if (struct.status) {
|
|
199
|
+
model.status = struct.status
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (struct.privateSettings) {
|
|
203
|
+
model.privateSettings.patchOrPut(struct.privateSettings)
|
|
204
|
+
|
|
205
|
+
if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
|
|
206
|
+
throw new SimpleError({
|
|
207
|
+
code: "missing_permissions",
|
|
208
|
+
message: "You cannot restrict your own permissions",
|
|
209
|
+
human: "Je kan je eigen volledige toegang tot deze inschrijvingsgroep niet verwijderen. Vraag aan een hoofdbeheerder om jouw toegang te verwijderen."
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (struct.cycle !== undefined) {
|
|
215
|
+
model.cycle = struct.cycle
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (struct.deletedAt !== undefined) {
|
|
219
|
+
model.deletedAt = struct.deletedAt
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (struct.defaultAgeGroupId !== undefined) {
|
|
223
|
+
model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const patch = struct;
|
|
227
|
+
if (patch.waitingList !== undefined) {
|
|
228
|
+
if (patch.waitingList === null) {
|
|
229
|
+
// delete
|
|
230
|
+
if (model.waitingListId) {
|
|
231
|
+
// for now don't delete, as waiting lists can be shared between multiple groups
|
|
232
|
+
// await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(model.waitingListId)
|
|
233
|
+
model.waitingListId = null;
|
|
220
234
|
}
|
|
221
235
|
|
|
222
|
-
|
|
223
|
-
|
|
236
|
+
} else if (patch.waitingList.isPatch()) {
|
|
237
|
+
if (!model.waitingListId) {
|
|
238
|
+
throw new SimpleError({
|
|
239
|
+
code: 'invalid_field',
|
|
240
|
+
field: 'waitingList',
|
|
241
|
+
message: 'Cannot patch waiting list before it is created'
|
|
242
|
+
})
|
|
224
243
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
244
|
+
patch.waitingList.id = model.waitingListId
|
|
245
|
+
patch.waitingList.type = GroupType.WaitingList
|
|
246
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(patch.waitingList)
|
|
247
|
+
} else {
|
|
248
|
+
if (model.waitingListId) {
|
|
249
|
+
// for now don't delete, as waiting lists can be shared between multiple groups
|
|
250
|
+
// await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(model.waitingListId)
|
|
251
|
+
model.waitingListId = null;
|
|
252
|
+
}
|
|
253
|
+
patch.waitingList.type = GroupType.WaitingList
|
|
228
254
|
|
|
229
|
-
|
|
255
|
+
const existing = await Group.getByID(patch.waitingList.id)
|
|
256
|
+
if (existing) {
|
|
257
|
+
if (existing.organizationId !== model.organizationId) {
|
|
230
258
|
throw new SimpleError({
|
|
231
|
-
code:
|
|
232
|
-
|
|
233
|
-
|
|
259
|
+
code: 'invalid_field',
|
|
260
|
+
field: 'waitingList',
|
|
261
|
+
message: 'Waiting list group is already used in another organization'
|
|
234
262
|
})
|
|
235
263
|
}
|
|
236
|
-
}
|
|
237
264
|
|
|
238
|
-
|
|
239
|
-
|
|
265
|
+
model.waitingListId = existing.id
|
|
266
|
+
} else {
|
|
267
|
+
const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
|
|
268
|
+
patch.waitingList,
|
|
269
|
+
model.organizationId,
|
|
270
|
+
model.periodId
|
|
271
|
+
)
|
|
272
|
+
model.waitingListId = group.id
|
|
240
273
|
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
await model.updateOccupancy()
|
|
278
|
+
await model.save();
|
|
241
279
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
280
|
+
if (struct.deletedAt !== undefined || struct.defaultAgeGroupId !== undefined) {
|
|
281
|
+
Member.updateMembershipsForGroupId(model.id)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
245
284
|
|
|
246
|
-
if (struct.defaultAgeGroupId !== undefined) {
|
|
247
|
-
model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
await model.updateOccupancy()
|
|
251
|
-
await model.save();
|
|
252
|
-
}
|
|
253
285
|
|
|
254
|
-
|
|
255
|
-
|
|
286
|
+
static async createGroup(struct: GroupStruct, organizationId: string, periodId: string, options?: {allowedIds?: string[]}): Promise<Group> {
|
|
287
|
+
const allowedIds = options?.allowedIds ?? []
|
|
256
288
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
289
|
+
if (!await Context.auth.hasFullAccess(organizationId)) {
|
|
290
|
+
if (allowedIds.includes(struct.id)) {
|
|
291
|
+
// Ok
|
|
292
|
+
} else {
|
|
293
|
+
throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen')
|
|
261
294
|
}
|
|
295
|
+
}
|
|
262
296
|
|
|
263
|
-
|
|
264
|
-
|
|
297
|
+
const user = Context.auth.user
|
|
298
|
+
|
|
299
|
+
const model = new Group()
|
|
300
|
+
model.id = struct.id
|
|
301
|
+
model.organizationId = organizationId
|
|
302
|
+
model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId)
|
|
303
|
+
model.periodId = periodId
|
|
304
|
+
model.settings = struct.settings
|
|
305
|
+
model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
|
|
306
|
+
model.status = struct.status
|
|
307
|
+
model.type = struct.type
|
|
308
|
+
|
|
309
|
+
if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
|
|
310
|
+
// Create a temporary permission role for this user
|
|
311
|
+
const organizationPermissions = user.permissions?.organizationPermissions?.get(organizationId)
|
|
312
|
+
if (!organizationPermissions) {
|
|
313
|
+
throw new Error('Unexpected missing permissions')
|
|
265
314
|
}
|
|
315
|
+
const resourcePermissions = ResourcePermissions.create({
|
|
316
|
+
resourceName: model.settings.name,
|
|
317
|
+
level: PermissionLevel.Full
|
|
318
|
+
})
|
|
319
|
+
const patch = resourcePermissions.createInsertPatch(PermissionsResourceType.Groups, model.id, organizationPermissions)
|
|
320
|
+
user.permissions!.organizationPermissions.set(organizationId, organizationPermissions.patch(patch))
|
|
321
|
+
console.log('Automatically granted author full permissions to resource', 'group', model.id, 'user', user.id, 'patch', patch.encode({version: Version}))
|
|
322
|
+
await user.save()
|
|
266
323
|
}
|
|
267
324
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
325
|
+
// Check if current user has permissions to this new group -> else fail with error
|
|
326
|
+
if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
|
|
327
|
+
throw new SimpleError({
|
|
328
|
+
code: "missing_permissions",
|
|
329
|
+
message: "You cannot restrict your own permissions",
|
|
330
|
+
human: "Je kan geen inschrijvingsgroep maken zonder dat je zelf volledige toegang hebt tot de nieuwe groep"
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (struct.waitingList) {
|
|
335
|
+
const existing = await Group.getByID(struct.waitingList.id)
|
|
336
|
+
if (existing) {
|
|
337
|
+
if (existing.organizationId !== model.organizationId) {
|
|
338
|
+
throw new SimpleError({
|
|
339
|
+
code: 'invalid_field',
|
|
340
|
+
field: 'waitingList',
|
|
341
|
+
message: 'Waiting list group is already used in another organization'
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
model.waitingListId = existing.id
|
|
346
|
+
} else {
|
|
347
|
+
struct.waitingList.type = GroupType.WaitingList
|
|
348
|
+
const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
|
|
349
|
+
struct.waitingList,
|
|
350
|
+
model.organizationId,
|
|
351
|
+
model.periodId
|
|
352
|
+
)
|
|
353
|
+
model.waitingListId = group.id
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
await model.updateOccupancy()
|
|
358
|
+
await model.save();
|
|
359
|
+
return model;
|
|
271
360
|
}
|
|
272
361
|
|
|
273
362
|
}
|
|
@@ -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
|
}
|
|
@@ -101,8 +101,9 @@ export class ExchangePaymentEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
101
101
|
|
|
102
102
|
// Prevent concurrency issues
|
|
103
103
|
await QueueHandler.schedule("balance-item-update/"+organization.id, async () => {
|
|
104
|
+
const unloaded = (await BalanceItemPayment.where({paymentId: payment.id})).map(r => r.setRelation(BalanceItemPayment.payment, payment))
|
|
104
105
|
const balanceItemPayments = await BalanceItemPayment.balanceItem.load(
|
|
105
|
-
|
|
106
|
+
unloaded
|
|
106
107
|
);
|
|
107
108
|
|
|
108
109
|
for (const balanceItemPayment of balanceItemPayments) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, PatchMap } from "@simonbackx/simple-encoding"
|
|
2
2
|
import { SimpleError } from "@simonbackx/simple-errors"
|
|
3
|
-
import { BalanceItem, Document, DocumentTemplate, EmailTemplate, Group, Member, MemberWithRegistrations, Order, Organization, Payment, Registration, User, Webshop } from "@stamhoofd/models"
|
|
3
|
+
import { BalanceItem, Document, DocumentTemplate, EmailTemplate, Event, Group, Member, MemberWithRegistrations, Order, Organization, Payment, Registration, User, Webshop } from "@stamhoofd/models"
|
|
4
4
|
import { AccessRight, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory } from "@stamhoofd/structures"
|
|
5
5
|
import { Formatter } from "@stamhoofd/utility"
|
|
6
6
|
|
|
@@ -11,6 +11,10 @@ import { Formatter } from "@stamhoofd/utility"
|
|
|
11
11
|
export class AdminPermissionChecker {
|
|
12
12
|
organization: Organization|null
|
|
13
13
|
user: User
|
|
14
|
+
/**
|
|
15
|
+
* The member that is linked to this user = is this user
|
|
16
|
+
*/
|
|
17
|
+
member: MemberWithRegistrations|null = null
|
|
14
18
|
platform: PlatformStruct
|
|
15
19
|
|
|
16
20
|
organizationCache: Map<string, Organization|Promise<Organization|undefined>> = new Map()
|
|
@@ -143,6 +147,13 @@ export class AdminPermissionChecker {
|
|
|
143
147
|
if (!this.checkScope(group.organizationId)) {
|
|
144
148
|
return false
|
|
145
149
|
}
|
|
150
|
+
const organization = await this.getOrganization(group.organizationId)
|
|
151
|
+
|
|
152
|
+
if (group.periodId !== organization.periodId) {
|
|
153
|
+
if (!await this.hasFullAccess(group.organizationId)) {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
}
|
|
146
157
|
|
|
147
158
|
if (group.deletedAt || group.status === GroupStatus.Archived) {
|
|
148
159
|
return await this.canAccessArchivedGroups(group.organizationId);
|
|
@@ -160,7 +171,6 @@ export class AdminPermissionChecker {
|
|
|
160
171
|
}
|
|
161
172
|
|
|
162
173
|
// Check parent categories
|
|
163
|
-
const organization = await this.getOrganization(group.organizationId)
|
|
164
174
|
const parentCategories = group.getParentCategories(organization.meta.categories)
|
|
165
175
|
for (const category of parentCategories) {
|
|
166
176
|
if (organizationPermissions.hasResourceAccess(PermissionsResourceType.GroupCategories, category.id, permissionLevel)) {
|
|
@@ -171,6 +181,28 @@ export class AdminPermissionChecker {
|
|
|
171
181
|
return false;
|
|
172
182
|
}
|
|
173
183
|
|
|
184
|
+
async canAccessEvent(event: Event, permissionLevel: PermissionLevel = PermissionLevel.Read): Promise<boolean> {
|
|
185
|
+
// Check permissions aren't scoped to a specific organization, and they mismatch
|
|
186
|
+
if (!this.checkScope(event.organizationId)) {
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (permissionLevel !== PermissionLevel.Read) {
|
|
191
|
+
if (event.organizationId) {
|
|
192
|
+
// Need full access for now
|
|
193
|
+
if (!await this.hasFullAccess(event.organizationId)) {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
if (!this.hasPlatformFullAccess()) {
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
174
206
|
async canAccessArchivedGroups(organizationId: string) {
|
|
175
207
|
return await this.hasFullAccess(organizationId)
|
|
176
208
|
}
|
|
@@ -192,6 +224,12 @@ export class AdminPermissionChecker {
|
|
|
192
224
|
return true
|
|
193
225
|
}
|
|
194
226
|
|
|
227
|
+
if (member.registrations.length === 0 && permissionLevel !== PermissionLevel.Full && (this.organization && await this.hasFullAccess(this.organization.id, PermissionLevel.Full))) {
|
|
228
|
+
// Everyone with at least full access to at least one organization can access this member
|
|
229
|
+
// This allows organizations to register new members themselves
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
195
233
|
for (const registration of member.registrations) {
|
|
196
234
|
if (await this.canAccessRegistration(registration, permissionLevel)) {
|
|
197
235
|
return true;
|
|
@@ -221,7 +259,8 @@ export class AdminPermissionChecker {
|
|
|
221
259
|
return false;
|
|
222
260
|
}
|
|
223
261
|
|
|
224
|
-
if (organizationPermissions.hasAccess(
|
|
262
|
+
if (organizationPermissions.hasAccess(PermissionLevel.Full)) {
|
|
263
|
+
// Only full permissions; because non-full doesn't have access to other periods
|
|
225
264
|
return true;
|
|
226
265
|
}
|
|
227
266
|
|
|
@@ -853,6 +892,24 @@ export class AdminPermissionChecker {
|
|
|
853
892
|
return set
|
|
854
893
|
}
|
|
855
894
|
|
|
895
|
+
|
|
896
|
+
async getAccessibleGroups(organizationId: string, level: PermissionLevel = PermissionLevel.Read): Promise<string[] | 'all'> {
|
|
897
|
+
if (await this.hasFullAccess(organizationId)) {
|
|
898
|
+
return 'all'
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const groups = await this.getOrganizationGroups(organizationId)
|
|
902
|
+
const accessibleGroups: string[] = []
|
|
903
|
+
|
|
904
|
+
for (const group of groups) {
|
|
905
|
+
if (await this.canAccessGroup(group, level)) {
|
|
906
|
+
accessibleGroups.push(group.id)
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return accessibleGroups
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
|
|
856
913
|
/**
|
|
857
914
|
* Changes data inline
|
|
858
915
|
*/
|