@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
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 {
|
|
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) {
|