@stamhoofd/backend 2.2.0 → 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/.env.json +2 -2
- package/index.ts +3 -0
- package/package.json +4 -4
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +63 -2
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +3 -0
- 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
|
@@ -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;
|
|
@@ -188,13 +189,8 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
188
189
|
updateGroups.set(group.id, group)
|
|
189
190
|
}
|
|
190
191
|
|
|
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
192
|
// Auto link users based on data
|
|
197
|
-
await
|
|
193
|
+
await MemberUserSyncer.onChangeMember(member)
|
|
198
194
|
}
|
|
199
195
|
|
|
200
196
|
// Loop all members one by one
|
|
@@ -428,7 +424,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
428
424
|
const platform = await Platform.getShared()
|
|
429
425
|
const responsibility = platform.config.responsibilities.find(r => r.id === patchResponsibility.responsibilityId)
|
|
430
426
|
|
|
431
|
-
if (responsibility && !responsibility.
|
|
427
|
+
if (responsibility && !responsibility.organizationBased && !Context.auth.hasPlatformFullAccess()) {
|
|
432
428
|
throw Context.auth.error("Je hebt niet voldoende rechten om deze functie aan te passen")
|
|
433
429
|
}
|
|
434
430
|
|
|
@@ -458,38 +454,88 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
458
454
|
await responsibilityRecord.save()
|
|
459
455
|
}
|
|
460
456
|
|
|
461
|
-
//
|
|
457
|
+
// Create responsibilities
|
|
462
458
|
for (const {put} of patch.responsibilities.getPuts()) {
|
|
463
459
|
if (!Context.auth.hasPlatformFullAccess() && !(organization && await Context.auth.hasFullAccess(organization.id))) {
|
|
464
460
|
throw Context.auth.error("Je hebt niet voldoende rechten om functies van leden aan te passen")
|
|
465
461
|
}
|
|
466
462
|
|
|
467
463
|
const platform = await Platform.getShared()
|
|
468
|
-
const
|
|
464
|
+
const platformResponsibility = platform.config.responsibilities.find(r => r.id === put.responsibilityId)
|
|
465
|
+
const org = organization ?? (put.organizationId ? await Organization.getByID(put.organizationId) : null)
|
|
469
466
|
|
|
470
|
-
if (!
|
|
471
|
-
throw
|
|
467
|
+
if (!org && put.organizationId) {
|
|
468
|
+
throw new SimpleError({
|
|
469
|
+
code: "invalid_field",
|
|
470
|
+
message: "Invalid organization",
|
|
471
|
+
human: "Deze vereniging bestaat niet",
|
|
472
|
+
field: "organizationId"
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
const responsibility = platformResponsibility ?? org?.privateMeta.responsibilities.find(r => r.id === put.responsibilityId)
|
|
476
|
+
|
|
477
|
+
if (!responsibility) {
|
|
478
|
+
throw new SimpleError({
|
|
479
|
+
code: "invalid_field",
|
|
480
|
+
message: "Invalid responsibility",
|
|
481
|
+
human: "Deze functie bestaat niet",
|
|
482
|
+
field: "responsibilityId"
|
|
483
|
+
})
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!org && responsibility.organizationBased) {
|
|
487
|
+
throw new SimpleError({
|
|
488
|
+
code: "invalid_field",
|
|
489
|
+
message: "Invalid organization",
|
|
490
|
+
human: "Deze functie kan niet worden toegewezen aan deze vereniging",
|
|
491
|
+
field: "organizationId"
|
|
492
|
+
})
|
|
472
493
|
}
|
|
473
494
|
|
|
474
495
|
const model = new MemberResponsibilityRecord()
|
|
475
496
|
model.memberId = member.id
|
|
476
497
|
model.responsibilityId = responsibility.id
|
|
498
|
+
model.organizationId = org?.id ?? null
|
|
477
499
|
|
|
478
|
-
if (responsibility.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
500
|
+
if (responsibility.organizationTagIds !== null && (!org || !org.meta.matchTags(responsibility.organizationTagIds))) {
|
|
501
|
+
throw new SimpleError({
|
|
502
|
+
code: "invalid_field",
|
|
503
|
+
message: "Invalid organization",
|
|
504
|
+
human: "Deze functie is niet beschikbaar voor deze vereniging",
|
|
505
|
+
field: "organizationId"
|
|
506
|
+
})
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (responsibility.defaultAgeGroupIds !== null) {
|
|
510
|
+
if (!put.groupId) {
|
|
511
|
+
throw new SimpleError({
|
|
512
|
+
code: "invalid_field",
|
|
513
|
+
message: "Missing groupId",
|
|
514
|
+
human: "Kies een leeftijdsgroep waarvoor je deze functie wilt toekennen",
|
|
515
|
+
field: "groupId"
|
|
516
|
+
})
|
|
490
517
|
}
|
|
491
|
-
|
|
492
|
-
|
|
518
|
+
|
|
519
|
+
const group = await Group.getByID(put.groupId)
|
|
520
|
+
if (!group || group.organizationId !== model.organizationId) {
|
|
521
|
+
throw new SimpleError({
|
|
522
|
+
code: "invalid_field",
|
|
523
|
+
message: "Invalid groupId",
|
|
524
|
+
human: "Deze leeftijdsgroep bestaat niet",
|
|
525
|
+
field: "groupId"
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (group.defaultAgeGroupId === null || !responsibility.defaultAgeGroupIds.includes(group.defaultAgeGroupId)) {
|
|
530
|
+
throw new SimpleError({
|
|
531
|
+
code: "invalid_field",
|
|
532
|
+
message: "Invalid groupId",
|
|
533
|
+
human: "Deze leeftijdsgroep komt niet in aanmerking voor deze functie",
|
|
534
|
+
field: "groupId"
|
|
535
|
+
})
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
model.groupId = group.id
|
|
493
539
|
}
|
|
494
540
|
|
|
495
541
|
// Allow patching begin and end date
|
|
@@ -499,25 +545,17 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
499
545
|
throw Context.auth.error("Je kan de startdatum van een functie niet in de toekomst zetten")
|
|
500
546
|
}
|
|
501
547
|
|
|
548
|
+
if (put.endDate && put.endDate > new Date(Date.now() + 60*1000)) {
|
|
549
|
+
throw Context.auth.error("Je kan de einddatum van een functie niet in de toekomst zetten - kijk indien nodig je systeemtijd na")
|
|
550
|
+
}
|
|
551
|
+
|
|
502
552
|
model.startDate = put.startDate
|
|
503
553
|
|
|
504
554
|
await model.save()
|
|
505
555
|
}
|
|
506
556
|
|
|
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
557
|
// Auto link users based on data
|
|
518
|
-
|
|
519
|
-
await PatchOrganizationMembersEndpoint.updateManagers(member)
|
|
520
|
-
}
|
|
558
|
+
await MemberUserSyncer.onChangeMember(member)
|
|
521
559
|
|
|
522
560
|
// Add platform memberships
|
|
523
561
|
for (const {put} of patch.platformMemberships.getPuts()) {
|
|
@@ -571,6 +609,8 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
571
609
|
|
|
572
610
|
await membership.calculatePrice()
|
|
573
611
|
await membership.save()
|
|
612
|
+
|
|
613
|
+
updateMembershipMemberIds.add(member.id)
|
|
574
614
|
}
|
|
575
615
|
|
|
576
616
|
// Delete platform memberships
|
|
@@ -599,7 +639,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
599
639
|
})
|
|
600
640
|
}
|
|
601
641
|
|
|
602
|
-
if (membership.
|
|
642
|
+
if (!membership.canDelete()) {
|
|
603
643
|
throw new SimpleError({
|
|
604
644
|
code: "invalid_field",
|
|
605
645
|
message: "Invalid invoice",
|
|
@@ -607,9 +647,12 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
607
647
|
})
|
|
608
648
|
}
|
|
609
649
|
|
|
610
|
-
|
|
650
|
+
membership.deletedAt = new Date()
|
|
651
|
+
await membership.save()
|
|
652
|
+
updateMembershipMemberIds.add(member.id)
|
|
611
653
|
}
|
|
612
654
|
|
|
655
|
+
|
|
613
656
|
if (!members.find(m => m.id === member.id)) {
|
|
614
657
|
members.push(member)
|
|
615
658
|
}
|
|
@@ -622,6 +665,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
622
665
|
throw Context.auth.error("Je hebt niet voldoende rechten om dit lid te verwijderen")
|
|
623
666
|
}
|
|
624
667
|
|
|
668
|
+
await MemberUserSyncer.onDeleteMember(member)
|
|
625
669
|
await User.deleteForDeletedMember(member.id)
|
|
626
670
|
await BalanceItem.deleteForDeletedMember(member.id)
|
|
627
671
|
await member.delete()
|
|
@@ -804,71 +848,4 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
804
848
|
await registration.save()
|
|
805
849
|
}
|
|
806
850
|
}
|
|
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
851
|
}
|
|
@@ -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
|
}
|
|
@@ -47,6 +47,15 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
47
47
|
// Update roles
|
|
48
48
|
platform.privateConfig.roles = patchObject(platform.privateConfig.roles, request.body.privateConfig.roles)
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
if (request.body.privateConfig.emails) {
|
|
52
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
53
|
+
throw Context.auth.error()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Update roles
|
|
57
|
+
platform.privateConfig.emails = patchObject(platform.privateConfig.emails, request.body.privateConfig.emails)
|
|
58
|
+
}
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
if (request.body.config) {
|
|
@@ -7,6 +7,7 @@ import { MemberWithRegistrationsBlob, MembersBlob } from "@stamhoofd/structures"
|
|
|
7
7
|
import { Context } from '../../../helpers/Context';
|
|
8
8
|
import { PatchOrganizationMembersEndpoint } from '../../global/members/PatchOrganizationMembersEndpoint';
|
|
9
9
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
10
|
+
import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
|
|
10
11
|
type Params = Record<string, never>;
|
|
11
12
|
type Query = undefined;
|
|
12
13
|
type Body = PatchableArrayAutoEncoder<MemberWithRegistrationsBlob>
|
|
@@ -83,7 +84,7 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
83
84
|
const updatedMember = members.find(m => m.id === member.id);
|
|
84
85
|
if (updatedMember) {
|
|
85
86
|
// Make sure we also give access to other parents
|
|
86
|
-
await
|
|
87
|
+
await MemberUserSyncer.onChangeMember(updatedMember)
|
|
87
88
|
await Document.updateForMember(updatedMember.id)
|
|
88
89
|
}
|
|
89
90
|
}
|
|
@@ -121,7 +122,7 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
121
122
|
})
|
|
122
123
|
}
|
|
123
124
|
await member.save();
|
|
124
|
-
await
|
|
125
|
+
await MemberUserSyncer.onChangeMember(member)
|
|
125
126
|
|
|
126
127
|
// Update documents
|
|
127
128
|
await Document.updateForMember(member.id)
|
|
@@ -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];
|