@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
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { Decoder } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
-
import { Member, MemberWithRegistrations, Platform } from '@stamhoofd/models';
|
|
6
|
-
import { SQL, SQLAge, SQLConcat, SQLFilterDefinitions, SQLOrderBy, SQLOrderByDirection, SQLScalar, SQLSortDefinitions, baseSQLFilterCompilers, compileToSQLFilter, compileToSQLSorter, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler, joinSQLQuery } from "@stamhoofd/sql";
|
|
7
|
-
import { CountFilteredRequest, GroupStatus, LimitedFilteredRequest, MembersBlob, PaginatedResponse, PermissionLevel, StamhoofdFilter, getSortFilter } from '@stamhoofd/structures';
|
|
5
|
+
import { Email, Member, MemberWithRegistrations, Platform } from '@stamhoofd/models';
|
|
6
|
+
import { SQL, SQLAge, SQLConcat, SQLFilterDefinitions, SQLJSONValue, SQLOrderBy, SQLOrderByDirection, SQLScalar, SQLSortDefinitions, baseSQLFilterCompilers, compileToSQLFilter, compileToSQLSorter, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler, joinSQLQuery } from "@stamhoofd/sql";
|
|
7
|
+
import { CountFilteredRequest, EmailRecipientFilterType, GroupStatus, LimitedFilteredRequest, MembersBlob, PaginatedResponse, PermissionLevel, StamhoofdFilter, getSortFilter, mergeFilters } from '@stamhoofd/structures';
|
|
8
8
|
import { DataValidator, Formatter } from '@stamhoofd/utility';
|
|
9
9
|
|
|
10
10
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
@@ -16,6 +16,44 @@ type Query = LimitedFilteredRequest;
|
|
|
16
16
|
type Body = undefined;
|
|
17
17
|
type ResponseBody = PaginatedResponse<MembersBlob, LimitedFilteredRequest>
|
|
18
18
|
|
|
19
|
+
Email.recipientLoaders.set(EmailRecipientFilterType.Members, {
|
|
20
|
+
fetch: async (query: LimitedFilteredRequest) => {
|
|
21
|
+
const result = await GetMembersEndpoint.buildData(query)
|
|
22
|
+
|
|
23
|
+
return new PaginatedResponse({
|
|
24
|
+
results: result.results.members.flatMap(m => m.getEmailRecipients(['member'])),
|
|
25
|
+
next: result.next
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
count: async (query: LimitedFilteredRequest) => {
|
|
30
|
+
query.filter = mergeFilters([query.filter, {
|
|
31
|
+
'email': {
|
|
32
|
+
$neq: null
|
|
33
|
+
}
|
|
34
|
+
}])
|
|
35
|
+
const q = await GetMembersEndpoint.buildQuery(query)
|
|
36
|
+
return await q.count();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
Email.recipientLoaders.set(EmailRecipientFilterType.MemberParents, {
|
|
40
|
+
fetch: async (query: LimitedFilteredRequest) => {
|
|
41
|
+
const result = await GetMembersEndpoint.buildData(query)
|
|
42
|
+
|
|
43
|
+
return new PaginatedResponse({
|
|
44
|
+
results: result.results.members.flatMap(m => m.getEmailRecipients(['parents'])),
|
|
45
|
+
next: result.next
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
count: async (query: LimitedFilteredRequest) => {
|
|
50
|
+
const q = await GetMembersEndpoint.buildQuery(query)
|
|
51
|
+
return await q.sum(
|
|
52
|
+
SQL.jsonLength(SQL.column('details'), '$.value.parents[*].email')
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
19
57
|
const registrationFilterCompilers: SQLFilterDefinitions = {
|
|
20
58
|
...baseSQLFilterCompilers,
|
|
21
59
|
"price": createSQLColumnFilterCompiler('price'),
|
|
@@ -131,6 +169,14 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
131
169
|
.from(
|
|
132
170
|
SQL.table('member_responsibility_records')
|
|
133
171
|
)
|
|
172
|
+
.join(
|
|
173
|
+
SQL.leftJoin(
|
|
174
|
+
SQL.table('groups')
|
|
175
|
+
).where(
|
|
176
|
+
SQL.column('groups', 'id'),
|
|
177
|
+
SQL.column('member_responsibility_records', 'groupId')
|
|
178
|
+
)
|
|
179
|
+
)
|
|
134
180
|
.where(
|
|
135
181
|
SQL.column('memberId'),
|
|
136
182
|
SQL.column('members', 'id'),
|
|
@@ -143,6 +189,11 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
143
189
|
"organizationId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'organizationId')),
|
|
144
190
|
"startDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'startDate')),
|
|
145
191
|
"endDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'endDate')),
|
|
192
|
+
"group": createSQLFilterNamespace({
|
|
193
|
+
...baseSQLFilterCompilers,
|
|
194
|
+
id: createSQLColumnFilterCompiler(SQL.column('groups', 'id')),
|
|
195
|
+
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId')),
|
|
196
|
+
})
|
|
146
197
|
}
|
|
147
198
|
),
|
|
148
199
|
|
|
@@ -154,6 +205,10 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
154
205
|
.where(
|
|
155
206
|
SQL.column('memberId'),
|
|
156
207
|
SQL.column('members', 'id'),
|
|
208
|
+
)
|
|
209
|
+
.where(
|
|
210
|
+
SQL.column('deletedAt'),
|
|
211
|
+
null,
|
|
157
212
|
),
|
|
158
213
|
{
|
|
159
214
|
...baseSQLFilterCompilers,
|
|
@@ -297,7 +352,6 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
297
352
|
if (tags != 'all' && tags.length === 0) {
|
|
298
353
|
throw Context.auth.error()
|
|
299
354
|
}
|
|
300
|
-
|
|
301
355
|
|
|
302
356
|
if (tags !== 'all') {
|
|
303
357
|
const platform = await Platform.getShared()
|
|
@@ -328,17 +382,35 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
328
382
|
|
|
329
383
|
if (organization) {
|
|
330
384
|
// Add organization scope filter
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
385
|
+
const groups = await Context.auth.getAccessibleGroups(organization.id)
|
|
386
|
+
|
|
387
|
+
if (groups === 'all') {
|
|
388
|
+
scopeFilter = {
|
|
389
|
+
registrations: {
|
|
390
|
+
$elemMatch: {
|
|
391
|
+
organizationId: organization.id,
|
|
392
|
+
registeredAt: {
|
|
393
|
+
$neq: null
|
|
394
|
+
}
|
|
338
395
|
}
|
|
339
396
|
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
397
|
+
};
|
|
398
|
+
} else {
|
|
399
|
+
scopeFilter = {
|
|
400
|
+
registrations: {
|
|
401
|
+
$elemMatch: {
|
|
402
|
+
organizationId: organization.id,
|
|
403
|
+
periodId: organization.periodId,
|
|
404
|
+
groupId: {
|
|
405
|
+
$in: groups
|
|
406
|
+
},
|
|
407
|
+
registeredAt: {
|
|
408
|
+
$neq: null
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
342
414
|
}
|
|
343
415
|
|
|
344
416
|
const query = SQL
|
|
@@ -408,29 +480,8 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
408
480
|
return query
|
|
409
481
|
}
|
|
410
482
|
|
|
411
|
-
async
|
|
412
|
-
await
|
|
413
|
-
await Context.authenticate()
|
|
414
|
-
|
|
415
|
-
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
416
|
-
|
|
417
|
-
if (request.query.limit > maxLimit) {
|
|
418
|
-
throw new SimpleError({
|
|
419
|
-
code: 'invalid_field',
|
|
420
|
-
field: 'limit',
|
|
421
|
-
message: 'Limit can not be more than ' + maxLimit
|
|
422
|
-
})
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (request.query.limit < 1) {
|
|
426
|
-
throw new SimpleError({
|
|
427
|
-
code: 'invalid_field',
|
|
428
|
-
field: 'limit',
|
|
429
|
-
message: 'Limit can not be less than 1'
|
|
430
|
-
})
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const query = await GetMembersEndpoint.buildQuery(request.query)
|
|
483
|
+
static async buildData(requestQuery: LimitedFilteredRequest) {
|
|
484
|
+
const query = await GetMembersEndpoint.buildQuery(requestQuery)
|
|
434
485
|
const data = await query.fetch()
|
|
435
486
|
|
|
436
487
|
const memberIds = data.map((r) => {
|
|
@@ -452,29 +503,54 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
452
503
|
|
|
453
504
|
let next: LimitedFilteredRequest|undefined;
|
|
454
505
|
|
|
455
|
-
if (memberIds.length >=
|
|
506
|
+
if (memberIds.length >= requestQuery.limit) {
|
|
456
507
|
const lastObject = members[members.length - 1];
|
|
457
|
-
const nextFilter = getSortFilter(lastObject, sorters,
|
|
508
|
+
const nextFilter = getSortFilter(lastObject, sorters, requestQuery.sort);
|
|
458
509
|
|
|
459
510
|
next = new LimitedFilteredRequest({
|
|
460
|
-
filter:
|
|
511
|
+
filter: requestQuery.filter,
|
|
461
512
|
pageFilter: nextFilter,
|
|
462
|
-
sort:
|
|
463
|
-
limit:
|
|
464
|
-
search:
|
|
513
|
+
sort: requestQuery.sort,
|
|
514
|
+
limit: requestQuery.limit,
|
|
515
|
+
search: requestQuery.search
|
|
465
516
|
})
|
|
466
517
|
|
|
467
|
-
if (JSON.stringify(nextFilter) === JSON.stringify(
|
|
468
|
-
console.error('Found infinite loading loop for',
|
|
518
|
+
if (JSON.stringify(nextFilter) === JSON.stringify(requestQuery.pageFilter)) {
|
|
519
|
+
console.error('Found infinite loading loop for', requestQuery);
|
|
469
520
|
next = undefined;
|
|
470
521
|
}
|
|
471
522
|
}
|
|
472
523
|
|
|
473
|
-
return new
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
524
|
+
return new PaginatedResponse<MembersBlob, LimitedFilteredRequest>({
|
|
525
|
+
results: await AuthenticatedStructures.membersBlob(members),
|
|
526
|
+
next
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
531
|
+
await Context.setOptionalOrganizationScope();
|
|
532
|
+
await Context.authenticate()
|
|
533
|
+
|
|
534
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
535
|
+
|
|
536
|
+
if (request.query.limit > maxLimit) {
|
|
537
|
+
throw new SimpleError({
|
|
538
|
+
code: 'invalid_field',
|
|
539
|
+
field: 'limit',
|
|
540
|
+
message: 'Limit can not be more than ' + maxLimit
|
|
477
541
|
})
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (request.query.limit < 1) {
|
|
545
|
+
throw new SimpleError({
|
|
546
|
+
code: 'invalid_field',
|
|
547
|
+
field: 'limit',
|
|
548
|
+
message: 'Limit can not be less than 1'
|
|
549
|
+
})
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return new Response(
|
|
553
|
+
await GetMembersEndpoint.buildData(request.query)
|
|
478
554
|
);
|
|
479
555
|
}
|
|
480
556
|
}
|
|
@@ -2,12 +2,13 @@ import { OneToManyRelation } from '@simonbackx/simple-database';
|
|
|
2
2
|
import { ConvertArrayToPatchableArray, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
4
4
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
5
|
-
import { BalanceItem,
|
|
6
|
-
import { BalanceItemStatus,
|
|
5
|
+
import { BalanceItem, BalanceItemPayment, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Organization, Payment, Platform, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
6
|
+
import { BalanceItemStatus, MemberWithRegistrationsBlob, MembersBlob, PaymentMethod, PaymentStatus, PermissionLevel, Registration as RegistrationStruct, User as UserStruct } from "@stamhoofd/structures";
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
9
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
10
10
|
import { Context } from '../../../helpers/Context';
|
|
11
|
+
import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
|
|
11
12
|
|
|
12
13
|
type Params = Record<string, never>;
|
|
13
14
|
type Query = undefined;
|
|
@@ -105,16 +106,29 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
105
106
|
duplicate.details.merge(member.details)
|
|
106
107
|
member = duplicate
|
|
107
108
|
|
|
108
|
-
//
|
|
109
|
+
// You need write permissions, because a user can potentially earn write permissions on a member
|
|
110
|
+
// by registering it
|
|
111
|
+
if (!await Context.auth.canAccessMember(duplicate, PermissionLevel.Write)) {
|
|
112
|
+
throw new SimpleError({
|
|
113
|
+
code: "known_member_missing_rights",
|
|
114
|
+
message: "Creating known member without sufficient access rights",
|
|
115
|
+
human: "Dit lid is al bekend in het systeem, maar je hebt er geen toegang tot. Vraag iemand met de juiste toegangsrechten om dit lid voor jou toe te voegen, of vraag het lid om zelf in te schrijven via het ledenportaal.",
|
|
116
|
+
statusCode: 400
|
|
117
|
+
})
|
|
118
|
+
}
|
|
109
119
|
}
|
|
110
120
|
|
|
111
121
|
if (struct.registrations.length === 0) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
// We risk creating a new member without being able to access it manually afterwards
|
|
123
|
+
|
|
124
|
+
if ((organization && !await Context.auth.hasFullAccess(organization.id)) || (!organization && !Context.auth.hasPlatformFullAccess())) {
|
|
125
|
+
throw new SimpleError({
|
|
126
|
+
code: "missing_group",
|
|
127
|
+
message: "Missing group",
|
|
128
|
+
human: "Je moet hoofdbeheerder zijn om een lid toe te voegen zonder inschrijving in het systeem",
|
|
129
|
+
statusCode: 400
|
|
130
|
+
})
|
|
131
|
+
}
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
// Throw early
|
|
@@ -188,13 +202,8 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
188
202
|
updateGroups.set(group.id, group)
|
|
189
203
|
}
|
|
190
204
|
|
|
191
|
-
// Add users if they don't exist (only placeholders allowed)
|
|
192
|
-
for (const placeholder of struct.users) {
|
|
193
|
-
await PatchOrganizationMembersEndpoint.linkUser(placeholder, member)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
205
|
// Auto link users based on data
|
|
197
|
-
await
|
|
206
|
+
await MemberUserSyncer.onChangeMember(member)
|
|
198
207
|
}
|
|
199
208
|
|
|
200
209
|
// Loop all members one by one
|
|
@@ -227,7 +236,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
227
236
|
// Update registrations
|
|
228
237
|
for (const patchRegistration of patch.registrations.getPatches()) {
|
|
229
238
|
const registration = member.registrations.find(r => r.id === patchRegistration.id)
|
|
230
|
-
if (!registration || registration.memberId != member.id) {
|
|
239
|
+
if (!registration || registration.memberId != member.id || (!await Context.auth.canAccessRegistration(registration, PermissionLevel.Write))) {
|
|
231
240
|
throw new SimpleError({
|
|
232
241
|
code: "permission_denied",
|
|
233
242
|
message: "You don't have permissions to access this endpoint",
|
|
@@ -428,7 +437,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
428
437
|
const platform = await Platform.getShared()
|
|
429
438
|
const responsibility = platform.config.responsibilities.find(r => r.id === patchResponsibility.responsibilityId)
|
|
430
439
|
|
|
431
|
-
if (responsibility && !responsibility.
|
|
440
|
+
if (responsibility && !responsibility.organizationBased && !Context.auth.hasPlatformFullAccess()) {
|
|
432
441
|
throw Context.auth.error("Je hebt niet voldoende rechten om deze functie aan te passen")
|
|
433
442
|
}
|
|
434
443
|
|
|
@@ -458,38 +467,88 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
458
467
|
await responsibilityRecord.save()
|
|
459
468
|
}
|
|
460
469
|
|
|
461
|
-
//
|
|
470
|
+
// Create responsibilities
|
|
462
471
|
for (const {put} of patch.responsibilities.getPuts()) {
|
|
463
472
|
if (!Context.auth.hasPlatformFullAccess() && !(organization && await Context.auth.hasFullAccess(organization.id))) {
|
|
464
473
|
throw Context.auth.error("Je hebt niet voldoende rechten om functies van leden aan te passen")
|
|
465
474
|
}
|
|
466
475
|
|
|
467
476
|
const platform = await Platform.getShared()
|
|
468
|
-
const
|
|
477
|
+
const platformResponsibility = platform.config.responsibilities.find(r => r.id === put.responsibilityId)
|
|
478
|
+
const org = organization ?? (put.organizationId ? await Organization.getByID(put.organizationId) : null)
|
|
479
|
+
|
|
480
|
+
if (!org && put.organizationId) {
|
|
481
|
+
throw new SimpleError({
|
|
482
|
+
code: "invalid_field",
|
|
483
|
+
message: "Invalid organization",
|
|
484
|
+
human: "Deze vereniging bestaat niet",
|
|
485
|
+
field: "organizationId"
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
const responsibility = platformResponsibility ?? org?.privateMeta.responsibilities.find(r => r.id === put.responsibilityId)
|
|
469
489
|
|
|
470
|
-
if (!responsibility
|
|
471
|
-
throw
|
|
490
|
+
if (!responsibility) {
|
|
491
|
+
throw new SimpleError({
|
|
492
|
+
code: "invalid_field",
|
|
493
|
+
message: "Invalid responsibility",
|
|
494
|
+
human: "Deze functie bestaat niet",
|
|
495
|
+
field: "responsibilityId"
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (!org && responsibility.organizationBased) {
|
|
500
|
+
throw new SimpleError({
|
|
501
|
+
code: "invalid_field",
|
|
502
|
+
message: "Invalid organization",
|
|
503
|
+
human: "Deze functie kan niet worden toegewezen aan deze vereniging",
|
|
504
|
+
field: "organizationId"
|
|
505
|
+
})
|
|
472
506
|
}
|
|
473
507
|
|
|
474
508
|
const model = new MemberResponsibilityRecord()
|
|
475
509
|
model.memberId = member.id
|
|
476
510
|
model.responsibilityId = responsibility.id
|
|
511
|
+
model.organizationId = org?.id ?? null
|
|
477
512
|
|
|
478
|
-
if (responsibility.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
513
|
+
if (responsibility.organizationTagIds !== null && (!org || !org.meta.matchTags(responsibility.organizationTagIds))) {
|
|
514
|
+
throw new SimpleError({
|
|
515
|
+
code: "invalid_field",
|
|
516
|
+
message: "Invalid organization",
|
|
517
|
+
human: "Deze functie is niet beschikbaar voor deze vereniging",
|
|
518
|
+
field: "organizationId"
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (responsibility.defaultAgeGroupIds !== null) {
|
|
523
|
+
if (!put.groupId) {
|
|
524
|
+
throw new SimpleError({
|
|
525
|
+
code: "invalid_field",
|
|
526
|
+
message: "Missing groupId",
|
|
527
|
+
human: "Kies een leeftijdsgroep waarvoor je deze functie wilt toekennen",
|
|
528
|
+
field: "groupId"
|
|
529
|
+
})
|
|
490
530
|
}
|
|
491
|
-
|
|
492
|
-
|
|
531
|
+
|
|
532
|
+
const group = await Group.getByID(put.groupId)
|
|
533
|
+
if (!group || group.organizationId !== model.organizationId) {
|
|
534
|
+
throw new SimpleError({
|
|
535
|
+
code: "invalid_field",
|
|
536
|
+
message: "Invalid groupId",
|
|
537
|
+
human: "Deze leeftijdsgroep bestaat niet",
|
|
538
|
+
field: "groupId"
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (group.defaultAgeGroupId === null || !responsibility.defaultAgeGroupIds.includes(group.defaultAgeGroupId)) {
|
|
543
|
+
throw new SimpleError({
|
|
544
|
+
code: "invalid_field",
|
|
545
|
+
message: "Invalid groupId",
|
|
546
|
+
human: "Deze leeftijdsgroep komt niet in aanmerking voor deze functie",
|
|
547
|
+
field: "groupId"
|
|
548
|
+
})
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
model.groupId = group.id
|
|
493
552
|
}
|
|
494
553
|
|
|
495
554
|
// Allow patching begin and end date
|
|
@@ -499,25 +558,17 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
499
558
|
throw Context.auth.error("Je kan de startdatum van een functie niet in de toekomst zetten")
|
|
500
559
|
}
|
|
501
560
|
|
|
561
|
+
if (put.endDate && put.endDate > new Date(Date.now() + 60*1000)) {
|
|
562
|
+
throw Context.auth.error("Je kan de einddatum van een functie niet in de toekomst zetten - kijk indien nodig je systeemtijd na")
|
|
563
|
+
}
|
|
564
|
+
|
|
502
565
|
model.startDate = put.startDate
|
|
503
566
|
|
|
504
567
|
await model.save()
|
|
505
568
|
}
|
|
506
569
|
|
|
507
|
-
// Link users
|
|
508
|
-
for (const placeholder of patch.users.getPuts()) {
|
|
509
|
-
await PatchOrganizationMembersEndpoint.linkUser(placeholder.put, member)
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Unlink users
|
|
513
|
-
for (const userId of patch.users.getDeletes()) {
|
|
514
|
-
await PatchOrganizationMembersEndpoint.unlinkUser(userId, member)
|
|
515
|
-
}
|
|
516
|
-
|
|
517
570
|
// Auto link users based on data
|
|
518
|
-
|
|
519
|
-
await PatchOrganizationMembersEndpoint.updateManagers(member)
|
|
520
|
-
}
|
|
571
|
+
await MemberUserSyncer.onChangeMember(member)
|
|
521
572
|
|
|
522
573
|
// Add platform memberships
|
|
523
574
|
for (const {put} of patch.platformMemberships.getPuts()) {
|
|
@@ -571,6 +622,8 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
571
622
|
|
|
572
623
|
await membership.calculatePrice()
|
|
573
624
|
await membership.save()
|
|
625
|
+
|
|
626
|
+
updateMembershipMemberIds.add(member.id)
|
|
574
627
|
}
|
|
575
628
|
|
|
576
629
|
// Delete platform memberships
|
|
@@ -599,7 +652,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
599
652
|
})
|
|
600
653
|
}
|
|
601
654
|
|
|
602
|
-
if (membership.
|
|
655
|
+
if (!membership.canDelete()) {
|
|
603
656
|
throw new SimpleError({
|
|
604
657
|
code: "invalid_field",
|
|
605
658
|
message: "Invalid invoice",
|
|
@@ -607,9 +660,12 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
607
660
|
})
|
|
608
661
|
}
|
|
609
662
|
|
|
610
|
-
|
|
663
|
+
membership.deletedAt = new Date()
|
|
664
|
+
await membership.save()
|
|
665
|
+
updateMembershipMemberIds.add(member.id)
|
|
611
666
|
}
|
|
612
667
|
|
|
668
|
+
|
|
613
669
|
if (!members.find(m => m.id === member.id)) {
|
|
614
670
|
members.push(member)
|
|
615
671
|
}
|
|
@@ -622,6 +678,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
622
678
|
throw Context.auth.error("Je hebt niet voldoende rechten om dit lid te verwijderen")
|
|
623
679
|
}
|
|
624
680
|
|
|
681
|
+
await MemberUserSyncer.onDeleteMember(member)
|
|
625
682
|
await User.deleteForDeletedMember(member.id)
|
|
626
683
|
await BalanceItem.deleteForDeletedMember(member.id)
|
|
627
684
|
await member.delete()
|
|
@@ -804,71 +861,4 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
804
861
|
await registration.save()
|
|
805
862
|
}
|
|
806
863
|
}
|
|
807
|
-
|
|
808
|
-
static async updateManagers(member: MemberWithRegistrations) {
|
|
809
|
-
// Check accounts
|
|
810
|
-
const managers = member.details.getManagerEmails()
|
|
811
|
-
|
|
812
|
-
for(const email of managers) {
|
|
813
|
-
const u = member.users.find(u => u.email.toLocaleLowerCase() === email.toLocaleLowerCase())
|
|
814
|
-
if (!u) {
|
|
815
|
-
console.log("Linking user "+email+" to member "+member.id)
|
|
816
|
-
await PatchOrganizationMembersEndpoint.linkUser(UserStruct.create({
|
|
817
|
-
firstName: member.details.parents.find(p => p.email === email)?.firstName,
|
|
818
|
-
lastName: member.details.parents.find(p => p.email === email)?.lastName,
|
|
819
|
-
email,
|
|
820
|
-
}), member)
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Delete accounts that should no longer have access
|
|
825
|
-
for (const u of member.users) {
|
|
826
|
-
if (!u.hasAccount()) {
|
|
827
|
-
// And not in managers list (case insensitive)
|
|
828
|
-
if (!managers.find(m => m.toLocaleLowerCase() === u.email.toLocaleLowerCase())) {
|
|
829
|
-
console.log("Unlinking user "+u.email+" from member "+member.id)
|
|
830
|
-
await PatchOrganizationMembersEndpoint.unlinkUser(u.id, member)
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
static async linkUser(user: UserStruct, member: MemberWithRegistrations) {
|
|
837
|
-
const email = user.email
|
|
838
|
-
let u = await User.getForAuthentication(member.organizationId, email, {allowWithoutAccount: true});
|
|
839
|
-
if (u) {
|
|
840
|
-
console.log("Giving an existing user access to a member: "+u.id)
|
|
841
|
-
} else {
|
|
842
|
-
u = new User()
|
|
843
|
-
u.organizationId = member.organizationId
|
|
844
|
-
u.email = email
|
|
845
|
-
u.firstName = user.firstName
|
|
846
|
-
u.lastName = user.lastName
|
|
847
|
-
await u.save()
|
|
848
|
-
|
|
849
|
-
console.log("Created new (placeholder) user that has access to a member: "+u.id)
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
await Member.users.reverse("members").link(u, [member])
|
|
853
|
-
|
|
854
|
-
// Update model relation to correct response
|
|
855
|
-
member.users.push(u)
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
static async unlinkUser(userId: string, member: MemberWithRegistrations) {
|
|
859
|
-
console.log("Removing access for "+ userId +" to member "+member.id)
|
|
860
|
-
const existingIndex = member.users.findIndex(u => u.id === userId)
|
|
861
|
-
if (existingIndex === -1) {
|
|
862
|
-
throw new SimpleError({
|
|
863
|
-
code: "user_not_found",
|
|
864
|
-
message: "Unlinking a user that doesn't exists anymore",
|
|
865
|
-
human: "Je probeert de toegang van een account tot een lid te verwijderen, maar dat account bestaat niet (meer)"
|
|
866
|
-
})
|
|
867
|
-
}
|
|
868
|
-
const existing = member.users[existingIndex]
|
|
869
|
-
await Member.users.reverse("members").unlink(existing, member)
|
|
870
|
-
|
|
871
|
-
// Update model relation to correct response
|
|
872
|
-
member.users.splice(existingIndex, 1)
|
|
873
|
-
}
|
|
874
864
|
}
|
|
@@ -4,6 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
4
4
|
import { Organization } from '@stamhoofd/models';
|
|
5
5
|
import { Organization as OrganizationStruct } from "@stamhoofd/structures";
|
|
6
6
|
import { GoogleTranslateHelper } from "@stamhoofd/utility";
|
|
7
|
+
import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
|
|
7
8
|
type Params = Record<string, never>;
|
|
8
9
|
|
|
9
10
|
class Query extends AutoEncoder {
|
|
@@ -60,7 +61,7 @@ export class GetOrganizationFromDomainEndpoint extends Endpoint<Params, Query, B
|
|
|
60
61
|
statusCode: 404
|
|
61
62
|
})
|
|
62
63
|
}
|
|
63
|
-
return new Response(await organization
|
|
64
|
+
return new Response(await AuthenticatedStructures.organization(organization));
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -75,6 +76,6 @@ export class GetOrganizationFromDomainEndpoint extends Endpoint<Params, Query, B
|
|
|
75
76
|
statusCode: 404
|
|
76
77
|
})
|
|
77
78
|
}
|
|
78
|
-
return new Response(await organization
|
|
79
|
+
return new Response(await AuthenticatedStructures.organization(organization));
|
|
79
80
|
}
|
|
80
81
|
}
|
|
@@ -4,6 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
4
4
|
import { Organization } from '@stamhoofd/models';
|
|
5
5
|
import { Organization as OrganizationStruct } from "@stamhoofd/structures";
|
|
6
6
|
import { GoogleTranslateHelper } from "@stamhoofd/utility";
|
|
7
|
+
import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
|
|
7
8
|
type Params = Record<string, never>;
|
|
8
9
|
|
|
9
10
|
class Query extends AutoEncoder {
|
|
@@ -44,6 +45,6 @@ export class GetOrganizationFromUriEndpoint extends Endpoint<Params, Query, Body
|
|
|
44
45
|
statusCode: 404
|
|
45
46
|
})
|
|
46
47
|
}
|
|
47
|
-
return new Response(await organization
|
|
48
|
+
return new Response(await AuthenticatedStructures.organization(organization));
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -2,6 +2,7 @@ import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-e
|
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
3
|
import { Organization } from "@stamhoofd/models";
|
|
4
4
|
import { Organization as OrganizationStruct,OrganizationSimple } from "@stamhoofd/structures";
|
|
5
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
5
6
|
|
|
6
7
|
type Params = Record<string, never>;
|
|
7
8
|
|
|
@@ -57,6 +58,6 @@ export class SearchOrganizationEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
57
58
|
if (request.request.getVersion() < 169) {
|
|
58
59
|
return new Response(organizations.map(o => OrganizationSimple.create(o)));
|
|
59
60
|
}
|
|
60
|
-
return new Response(await Promise.all(organizations.map(o =>
|
|
61
|
+
return new Response(await Promise.all(organizations.map(o => AuthenticatedStructures.organization(o))));
|
|
61
62
|
}
|
|
62
63
|
}
|
|
@@ -3,6 +3,7 @@ import { User } from '@stamhoofd/models';
|
|
|
3
3
|
import { 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
|
|
@@ -38,7 +39,7 @@ export class GetPlatformAdminsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
38
39
|
admins = admins.filter(a => !!a.permissions?.globalPermissions)
|
|
39
40
|
|
|
40
41
|
return new Response(
|
|
41
|
-
|
|
42
|
+
await AuthenticatedStructures.usersWithMembers(admins)
|
|
42
43
|
);
|
|
43
44
|
}
|
|
44
45
|
}
|