@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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
+
import { Event } from '@stamhoofd/models';
|
|
6
|
+
import { SQL, SQLFilterDefinitions, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions, baseSQLFilterCompilers, compileToSQLFilter, compileToSQLSorter, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler } from "@stamhoofd/sql";
|
|
7
|
+
import { CountFilteredRequest, Event as EventStruct, LimitedFilteredRequest, PaginatedResponse, StamhoofdFilter, getSortFilter } from '@stamhoofd/structures';
|
|
8
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
9
|
+
|
|
10
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
11
|
+
import { Context } from '../../../helpers/Context';
|
|
12
|
+
|
|
13
|
+
type Params = Record<string, never>;
|
|
14
|
+
type Query = LimitedFilteredRequest;
|
|
15
|
+
type Body = undefined;
|
|
16
|
+
type ResponseBody = PaginatedResponse<EventStruct[], LimitedFilteredRequest>
|
|
17
|
+
|
|
18
|
+
const filterCompilers: SQLFilterDefinitions = {
|
|
19
|
+
...baseSQLFilterCompilers,
|
|
20
|
+
id: createSQLColumnFilterCompiler('id'),
|
|
21
|
+
name: createSQLColumnFilterCompiler('name'),
|
|
22
|
+
organizationId: createSQLColumnFilterCompiler('organizationId'),
|
|
23
|
+
startDate: createSQLColumnFilterCompiler('startDate'),
|
|
24
|
+
endDate: createSQLColumnFilterCompiler('endDate'),
|
|
25
|
+
groupIds: createSQLExpressionFilterCompiler(
|
|
26
|
+
SQL.jsonValue(SQL.column('meta'), '$.value.groupIds'),
|
|
27
|
+
undefined,
|
|
28
|
+
true,
|
|
29
|
+
true
|
|
30
|
+
),
|
|
31
|
+
defaultAgeGroupIds: createSQLExpressionFilterCompiler(
|
|
32
|
+
SQL.jsonValue(SQL.column('meta'), '$.value.defaultAgeGroupIds'),
|
|
33
|
+
undefined,
|
|
34
|
+
true,
|
|
35
|
+
true
|
|
36
|
+
),
|
|
37
|
+
organizationTagIds: createSQLExpressionFilterCompiler(
|
|
38
|
+
SQL.jsonValue(SQL.column('meta'), '$.value.organizationTagIds')
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sorters: SQLSortDefinitions<Event> = {
|
|
43
|
+
'id': {
|
|
44
|
+
getValue(a) {
|
|
45
|
+
return a.id
|
|
46
|
+
},
|
|
47
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
48
|
+
return new SQLOrderBy({
|
|
49
|
+
column: SQL.column('id'),
|
|
50
|
+
direction
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
'name': {
|
|
55
|
+
getValue(a) {
|
|
56
|
+
return a.name
|
|
57
|
+
},
|
|
58
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
59
|
+
return new SQLOrderBy({
|
|
60
|
+
column: SQL.column('name'),
|
|
61
|
+
direction
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
'startDate': {
|
|
66
|
+
getValue(a) {
|
|
67
|
+
return Formatter.dateTimeIso(a.startDate)
|
|
68
|
+
},
|
|
69
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
70
|
+
return new SQLOrderBy({
|
|
71
|
+
column: SQL.column('startDate'),
|
|
72
|
+
direction
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
'endDate': {
|
|
77
|
+
getValue(a) {
|
|
78
|
+
return Formatter.dateTimeIso(a.endDate)
|
|
79
|
+
},
|
|
80
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
81
|
+
return new SQLOrderBy({
|
|
82
|
+
column: SQL.column('endDate'),
|
|
83
|
+
direction
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class GetEventsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
90
|
+
queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>
|
|
91
|
+
|
|
92
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
93
|
+
if (request.method != "GET") {
|
|
94
|
+
return [false];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const params = Endpoint.parseParameters(request.url, "/events", {});
|
|
98
|
+
|
|
99
|
+
if (params) {
|
|
100
|
+
return [true, params as Params];
|
|
101
|
+
}
|
|
102
|
+
return [false];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static buildQuery(q: CountFilteredRequest|LimitedFilteredRequest) {
|
|
106
|
+
const organization = Context.organization
|
|
107
|
+
let scopeFilter: StamhoofdFilter|undefined = undefined;
|
|
108
|
+
|
|
109
|
+
if (organization) {
|
|
110
|
+
scopeFilter = {
|
|
111
|
+
$or: [
|
|
112
|
+
{
|
|
113
|
+
organizationId: organization.id
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
organizationId: null
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const query = SQL
|
|
123
|
+
.select(
|
|
124
|
+
SQL.wildcard(Event.table)
|
|
125
|
+
)
|
|
126
|
+
.from(
|
|
127
|
+
SQL.table(Event.table)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (scopeFilter) {
|
|
131
|
+
query.where(compileToSQLFilter(scopeFilter, filterCompilers))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (q.filter) {
|
|
135
|
+
query.where(compileToSQLFilter(q.filter, filterCompilers))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (q.search) {
|
|
139
|
+
let searchFilter: StamhoofdFilter|null = null
|
|
140
|
+
|
|
141
|
+
// todo: detect special search patterns and adjust search filter if needed
|
|
142
|
+
searchFilter = {
|
|
143
|
+
name: {
|
|
144
|
+
$contains: q.search
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (searchFilter) {
|
|
149
|
+
query.where(compileToSQLFilter(searchFilter, filterCompilers))
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (q instanceof LimitedFilteredRequest) {
|
|
154
|
+
if (q.pageFilter) {
|
|
155
|
+
query.where(compileToSQLFilter(q.pageFilter, filterCompilers))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
query.orderBy(compileToSQLSorter(q.sort, sorters))
|
|
159
|
+
query.limit(q.limit)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return query
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static async buildData(requestQuery: LimitedFilteredRequest) {
|
|
166
|
+
const query = GetEventsEndpoint.buildQuery(requestQuery)
|
|
167
|
+
const data = await query.fetch()
|
|
168
|
+
|
|
169
|
+
const events = Event.fromRows(data, Event.table);
|
|
170
|
+
|
|
171
|
+
let next: LimitedFilteredRequest|undefined;
|
|
172
|
+
|
|
173
|
+
if (events.length >= requestQuery.limit) {
|
|
174
|
+
const lastObject = events[events.length - 1];
|
|
175
|
+
const nextFilter = getSortFilter(lastObject, sorters, requestQuery.sort);
|
|
176
|
+
|
|
177
|
+
next = new LimitedFilteredRequest({
|
|
178
|
+
filter: requestQuery.filter,
|
|
179
|
+
pageFilter: nextFilter,
|
|
180
|
+
sort: requestQuery.sort,
|
|
181
|
+
limit: requestQuery.limit,
|
|
182
|
+
search: requestQuery.search
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
if (JSON.stringify(nextFilter) === JSON.stringify(requestQuery.pageFilter)) {
|
|
186
|
+
console.error('Found infinite loading loop for', requestQuery);
|
|
187
|
+
next = undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return new PaginatedResponse<EventStruct[], LimitedFilteredRequest>({
|
|
192
|
+
results: await AuthenticatedStructures.events(events),
|
|
193
|
+
next
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
198
|
+
await Context.setOptionalOrganizationScope();
|
|
199
|
+
await Context.authenticate()
|
|
200
|
+
|
|
201
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
202
|
+
|
|
203
|
+
if (request.query.limit > maxLimit) {
|
|
204
|
+
throw new SimpleError({
|
|
205
|
+
code: 'invalid_field',
|
|
206
|
+
field: 'limit',
|
|
207
|
+
message: 'Limit can not be more than ' + maxLimit
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (request.query.limit < 1) {
|
|
212
|
+
throw new SimpleError({
|
|
213
|
+
code: 'invalid_field',
|
|
214
|
+
field: 'limit',
|
|
215
|
+
message: 'Limit can not be less than 1'
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return new Response(
|
|
220
|
+
await GetEventsEndpoint.buildData(request.query)
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, patchObject, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { Event, Organization, Platform, RegistrationPeriod } from '@stamhoofd/models';
|
|
4
|
+
import { Event as EventStruct, GroupType, PermissionLevel } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
7
|
+
import { SQL, SQLWhereSign } from '@stamhoofd/sql';
|
|
8
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
9
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
10
|
+
import { Context } from '../../../helpers/Context';
|
|
11
|
+
import { PatchOrganizationRegistrationPeriodsEndpoint } from '../../organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint';
|
|
12
|
+
|
|
13
|
+
type Params = { id: string };
|
|
14
|
+
type Query = undefined;
|
|
15
|
+
type Body = PatchableArrayAutoEncoder<EventStruct>
|
|
16
|
+
type ResponseBody = EventStruct[]
|
|
17
|
+
|
|
18
|
+
export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
19
|
+
bodyDecoder = new PatchableArrayDecoder(EventStruct as Decoder<EventStruct>, EventStruct.patchType() as Decoder<AutoEncoderPatchType<EventStruct>>, StringDecoder)
|
|
20
|
+
|
|
21
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
22
|
+
if (request.method != "PATCH") {
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const params = Endpoint.parseParameters(request.url, "/events", { id: String });
|
|
27
|
+
|
|
28
|
+
if (params) {
|
|
29
|
+
return [true, params as Params];
|
|
30
|
+
}
|
|
31
|
+
return [false];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
35
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
36
|
+
await Context.authenticate()
|
|
37
|
+
|
|
38
|
+
if (organization) {
|
|
39
|
+
if (!await Context.auth.hasSomeAccess(organization.id)) {
|
|
40
|
+
throw Context.auth.error()
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
if (!Context.auth.hasSomePlatformAccess()) {
|
|
44
|
+
throw Context.auth.error()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const events: Event[] = [];
|
|
49
|
+
|
|
50
|
+
for (const {put} of request.body.getPuts()) {
|
|
51
|
+
if (organization?.id && put.organizationId !== organization.id) {
|
|
52
|
+
throw new SimpleError({
|
|
53
|
+
code: 'invalid_data',
|
|
54
|
+
message: 'Invalid organizationId',
|
|
55
|
+
human: 'Je kan geen activiteiten aanmaken voor een andere organisatie',
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!organization?.id && !Context.auth.hasPlatformFullAccess()) {
|
|
60
|
+
throw new SimpleError({
|
|
61
|
+
code: 'invalid_data',
|
|
62
|
+
message: 'Invalid organizationId',
|
|
63
|
+
human: 'Je kan geen activiteiten voor een specifieke organisatie aanmaken als je geen platform hoofdbeheerder bent',
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const eventOrganization = put.organizationId ? (await Organization.getByID(put.organizationId)) : null
|
|
68
|
+
if (!eventOrganization && put.organizationId) {
|
|
69
|
+
throw new SimpleError({
|
|
70
|
+
code: 'invalid_data',
|
|
71
|
+
message: 'Invalid organizationId',
|
|
72
|
+
human: 'De organisatie werd niet gevonden',
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const event = new Event()
|
|
77
|
+
event.id = put.id
|
|
78
|
+
event.organizationId = put.organizationId
|
|
79
|
+
event.name = put.name
|
|
80
|
+
event.startDate = put.startDate
|
|
81
|
+
event.endDate = put.endDate
|
|
82
|
+
event.meta = put.meta
|
|
83
|
+
|
|
84
|
+
if (put.group) {
|
|
85
|
+
const period = await RegistrationPeriod.getByDate(event.startDate)
|
|
86
|
+
|
|
87
|
+
if (!period) {
|
|
88
|
+
throw new SimpleError({
|
|
89
|
+
code: 'invalid_period',
|
|
90
|
+
message: 'No period found for this start date',
|
|
91
|
+
human: 'Oeps, je kan nog geen evenementen met inschrijvingen aanmaken in deze periode. Dit werkjaar is nog niet aangemaakt in het systeem.',
|
|
92
|
+
field: 'startDate'
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
put.group.type = GroupType.EventRegistration
|
|
97
|
+
const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
|
|
98
|
+
put.group,
|
|
99
|
+
put.group.organizationId,
|
|
100
|
+
period.id
|
|
101
|
+
)
|
|
102
|
+
event.groupId = group.id
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
event.typeId = await PatchEventsEndpoint.validateEventType(put.typeId)
|
|
106
|
+
await PatchEventsEndpoint.checkEventLimits(event)
|
|
107
|
+
|
|
108
|
+
if (!(await Context.auth.canAccessEvent(event, PermissionLevel.Full))) {
|
|
109
|
+
throw Context.auth.error()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await event.save()
|
|
113
|
+
|
|
114
|
+
events.push(event)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const patch of request.body.getPatches()) {
|
|
118
|
+
const event = await Event.getByID(patch.id)
|
|
119
|
+
|
|
120
|
+
if (!event || !(await Context.auth.canAccessEvent(event, PermissionLevel.Full))) {
|
|
121
|
+
throw new SimpleError({
|
|
122
|
+
code: 'not_found',
|
|
123
|
+
message: 'Event not found',
|
|
124
|
+
human: 'De activiteit werd niet gevonden',
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
event.name = patch.name ?? event.name
|
|
129
|
+
event.startDate = patch.startDate ?? event.startDate
|
|
130
|
+
event.endDate = patch.endDate ?? event.endDate
|
|
131
|
+
event.meta = patchObject(event.meta, patch.meta)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if (patch.organizationId !== undefined) {
|
|
135
|
+
if (organization?.id && patch.organizationId !== organization.id) {
|
|
136
|
+
throw new SimpleError({
|
|
137
|
+
code: 'invalid_data',
|
|
138
|
+
message: 'Invalid organizationId',
|
|
139
|
+
human: 'Je kan geen activiteiten aanmaken voor een andere organisatie',
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!organization?.id && !Context.auth.hasPlatformFullAccess()) {
|
|
144
|
+
throw new SimpleError({
|
|
145
|
+
code: 'invalid_data',
|
|
146
|
+
message: 'Invalid organizationId',
|
|
147
|
+
human: 'Je kan geen activiteiten voor een specifieke organisatie aanmaken als je geen platform hoofdbeheerder bent',
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const eventOrganization = patch.organizationId ? (await Organization.getByID(patch.organizationId)) : null
|
|
152
|
+
if (!eventOrganization && patch.organizationId) {
|
|
153
|
+
throw new SimpleError({
|
|
154
|
+
code: 'invalid_data',
|
|
155
|
+
message: 'Invalid organizationId',
|
|
156
|
+
human: 'De organisatie werd niet gevonden',
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
event.organizationId = patch.organizationId
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
event.typeId = patch.typeId ? (await PatchEventsEndpoint.validateEventType(patch.typeId)) : event.typeId
|
|
163
|
+
await PatchEventsEndpoint.checkEventLimits(event)
|
|
164
|
+
|
|
165
|
+
if (patch.group !== undefined) {
|
|
166
|
+
if (patch.group === null) {
|
|
167
|
+
// delete
|
|
168
|
+
if (event.groupId) {
|
|
169
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(event.groupId)
|
|
170
|
+
event.groupId = null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
} else if (patch.group.isPatch()) {
|
|
174
|
+
if (!event.groupId) {
|
|
175
|
+
throw new SimpleError({
|
|
176
|
+
code: 'invalid_field',
|
|
177
|
+
field: 'group',
|
|
178
|
+
message: 'Cannot patch group before it is created'
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
patch.group.id = event.groupId
|
|
182
|
+
patch.group.type = GroupType.EventRegistration
|
|
183
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(patch.group)
|
|
184
|
+
} else {
|
|
185
|
+
if (event.groupId) {
|
|
186
|
+
// need to delete old group first
|
|
187
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(event.groupId)
|
|
188
|
+
event.groupId = null;
|
|
189
|
+
}
|
|
190
|
+
patch.group.type = GroupType.EventRegistration
|
|
191
|
+
|
|
192
|
+
const period = await RegistrationPeriod.getByDate(event.startDate)
|
|
193
|
+
|
|
194
|
+
if (!period) {
|
|
195
|
+
throw new SimpleError({
|
|
196
|
+
code: 'invalid_period',
|
|
197
|
+
message: 'No period found for this start date',
|
|
198
|
+
human: 'Oeps, je kan nog geen evenementen met inschrijvingen aanmaken in deze periode. Dit werkjaar is nog niet aangemaakt in het systeem.',
|
|
199
|
+
field: 'startDate'
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
|
|
204
|
+
patch.group,
|
|
205
|
+
patch.group.organizationId,
|
|
206
|
+
period.id
|
|
207
|
+
)
|
|
208
|
+
event.groupId = group.id
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await event.save()
|
|
213
|
+
events.push(event)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return new Response(
|
|
217
|
+
await AuthenticatedStructures.events(events)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static async validateEventType(typeId: string) {
|
|
222
|
+
return (await this.getEventType(typeId)).id
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static async getEventType(typeId: string) {
|
|
226
|
+
const platform = await Platform.getSharedStruct();
|
|
227
|
+
const type = platform.config.eventTypes.find(t => t.id == typeId)
|
|
228
|
+
if (!type) {
|
|
229
|
+
throw new SimpleError({
|
|
230
|
+
code: 'invalid_field',
|
|
231
|
+
message: 'Invalid typeId',
|
|
232
|
+
human: 'Dit type activiteit wordt niet ondersteund',
|
|
233
|
+
field: 'typeId'
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
return type
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static async checkEventLimits(event: Event) {
|
|
240
|
+
const type = await this.getEventType(event.typeId)
|
|
241
|
+
|
|
242
|
+
if (event.name.length < 2) {
|
|
243
|
+
throw new SimpleError({
|
|
244
|
+
code: 'invalid_field',
|
|
245
|
+
message: 'Name is too short',
|
|
246
|
+
human: 'Vul een naam voor je activiteit in',
|
|
247
|
+
field: 'name'
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (event.endDate < event.startDate) {
|
|
252
|
+
throw new SimpleError({
|
|
253
|
+
code: 'invalid_dates',
|
|
254
|
+
message: 'End date is before start date',
|
|
255
|
+
human: 'De einddatum moet na de startdatum liggen',
|
|
256
|
+
field: 'endDate'
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (type.maximumDays !== null || type.minimumDays !== null) {
|
|
261
|
+
const start = Formatter.luxon(event.startDate).startOf('day')
|
|
262
|
+
const end = Formatter.luxon(event.endDate).startOf('day')
|
|
263
|
+
|
|
264
|
+
const days = end.diff(start, 'days').days + 1;
|
|
265
|
+
|
|
266
|
+
console.log('Detected days:', days)
|
|
267
|
+
|
|
268
|
+
if (type.minimumDays !== null && days < type.minimumDays) {
|
|
269
|
+
throw new SimpleError({
|
|
270
|
+
code: 'minimum_days',
|
|
271
|
+
message: 'An event with this type has a minimum of ' + type.minimumDays + ' days',
|
|
272
|
+
human: 'Een ' + type.name + ' moet minimum ' + Formatter.pluralText(type.minimumDays, 'dag', 'dagen') + ' duren',
|
|
273
|
+
field: 'startDate'
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (type.maximumDays !== null && days > type.maximumDays) {
|
|
278
|
+
throw new SimpleError({
|
|
279
|
+
code: 'maximum_days',
|
|
280
|
+
message: 'An event with this type has a maximum of ' + type.maximumDays + ' days',
|
|
281
|
+
human: 'Een ' + type.name + ' mag maximaal ' + Formatter.pluralText(type.maximumDays, 'dag', 'dagen') + ' duren',
|
|
282
|
+
field: 'startDate'
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (type.maximum && (!event.existsInDatabase || ("typeId" in (await event.getChangedDatabaseProperties()).fields))) {
|
|
288
|
+
const currentPeriod = await RegistrationPeriod.getByDate(event.startDate);
|
|
289
|
+
console.log('event.startDate', event.startDate)
|
|
290
|
+
if (currentPeriod) {
|
|
291
|
+
const count = await SQL.select().from(
|
|
292
|
+
SQL.table(Event.table)
|
|
293
|
+
)
|
|
294
|
+
.where(SQL.column('organizationId'), event.organizationId)
|
|
295
|
+
.where(SQL.column('typeId'), event.typeId)
|
|
296
|
+
.where(SQL.column('id'), SQLWhereSign.NotEqual, event.id)
|
|
297
|
+
.where(SQL.column('startDate'), SQLWhereSign.GreaterEqual, currentPeriod.startDate)
|
|
298
|
+
.where(SQL.column('endDate'), SQLWhereSign.LessEqual, currentPeriod.endDate)
|
|
299
|
+
.count()
|
|
300
|
+
|
|
301
|
+
if (count >= type.maximum) {
|
|
302
|
+
throw new SimpleError({
|
|
303
|
+
code: 'type_maximum_reached',
|
|
304
|
+
message: 'Maximum number of events with this type reached',
|
|
305
|
+
human: 'Het maximum aantal voor ' + type.name + ' is bereikt (' + type.maximum + ')',
|
|
306
|
+
field: 'typeId'
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
throw new SimpleError({
|
|
311
|
+
code: 'invalid_period',
|
|
312
|
+
message: 'No period found for this start date',
|
|
313
|
+
human: 'Oeps, je kan nog geen evenementen van dit type aanmaken in deze periode',
|
|
314
|
+
field: 'startDate'
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|