@stamhoofd/backend 2.78.4 → 2.79.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 +1 -1
- package/package.json +13 -12
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateAdminEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
- package/src/endpoints/auth/PatchUserEndpoint.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +1964 -4
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +175 -2
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +1 -1
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +7 -1
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +3 -23
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +164 -0
- package/src/helpers/AdminPermissionChecker.ts +22 -1
- package/src/helpers/AuthenticatedStructures.ts +77 -20
- package/src/helpers/ForwardHandler.test.ts +16 -5
- package/src/helpers/ForwardHandler.ts +21 -9
- package/src/helpers/MemberUserSyncer.test.ts +822 -0
- package/src/helpers/MemberUserSyncer.ts +137 -108
- package/src/helpers/TagHelper.ts +3 -3
- package/src/seeds/1734596144-fill-previous-period-id.ts +1 -1
- package/src/seeds/1741008870-fix-auditlog-description.ts +50 -0
- package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +88 -0
- package/src/services/PlatformMembershipService.ts +7 -2
- package/src/services/SSOService.ts +1 -1
- package/tests/e2e/register.test.ts +2 -2
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { CachedBalance, Member, MemberResponsibilityRecord,
|
|
1
|
+
import { CachedBalance, Member, MemberResponsibilityRecord, MemberWithUsers, Organization, Platform, User } from '@stamhoofd/models';
|
|
2
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
2
3
|
import { SQL } from '@stamhoofd/sql';
|
|
3
4
|
import { AuditLogSource, MemberDetails, PermissionRole, Permissions, UserPermissions } from '@stamhoofd/structures';
|
|
4
|
-
import
|
|
5
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
5
6
|
import basex from 'base-x';
|
|
7
|
+
import crypto from 'crypto';
|
|
6
8
|
import { AuditLogService } from '../services/AuditLogService';
|
|
7
|
-
import { Formatter } from '@stamhoofd/utility';
|
|
8
|
-
import { QueueHandler } from '@stamhoofd/queues';
|
|
9
9
|
|
|
10
10
|
const ALPHABET = '123456789ABCDEFGHJKMNPQRSTUVWXYZ'; // Note: we removed 0, O, I and l to make it easier for humans
|
|
11
11
|
const customBase = basex(ALPHABET);
|
|
@@ -28,89 +28,101 @@ export class MemberUserSyncerStatic {
|
|
|
28
28
|
* - responsibilities have changed
|
|
29
29
|
* - email addresses have changed
|
|
30
30
|
*/
|
|
31
|
-
async onChangeMember(member:
|
|
32
|
-
await
|
|
33
|
-
|
|
31
|
+
async onChangeMember(member: Member) {
|
|
32
|
+
await QueueHandler.schedule('change-member/' + member.id, async () => {
|
|
33
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
34
|
+
// Note: Refresh member data is not really required because we'll just schedule a new task if it changes again
|
|
35
|
+
// Reload member users
|
|
36
|
+
await Member.users.load(member);
|
|
37
|
+
if (!Member.users.isLoaded(member)) {
|
|
38
|
+
throw new Error('Failed to load users for member ' + member.id);
|
|
39
|
+
}
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
for (const email of userEmails) {
|
|
37
|
-
// Link users that are found with these email addresses.
|
|
38
|
-
await this.linkUser(email, member, false, true);
|
|
39
|
-
}
|
|
41
|
+
const { userEmails, parentEmails, unverifiedEmails, allEmails } = this.getMemberAccessEmails(member.details);
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
// Make sure all these users have access to the member
|
|
44
|
+
for (const email of userEmails) {
|
|
45
|
+
// Link users that are found with these email addresses.
|
|
46
|
+
await this.linkUser(email, member, false, {
|
|
47
|
+
firstName: member.details.firstName,
|
|
48
|
+
lastName: member.details.lastName,
|
|
49
|
+
});
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
for (const parent of member.details.parents) {
|
|
53
|
+
for (const email of parent.getEmails()) {
|
|
54
|
+
if (userEmails.includes(email.toLocaleLowerCase())) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
// Link parents and unverified emails
|
|
59
|
+
await this.linkUser(email, member, true, {
|
|
60
|
+
firstName: parent.firstName,
|
|
61
|
+
lastName: parent.lastName,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
// Make sure all these users have access to the member
|
|
67
|
+
for (const email of unverifiedEmails) {
|
|
68
|
+
if (userEmails.includes(email)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (parentEmails.includes(email)) {
|
|
72
|
+
continue;
|
|
61
73
|
}
|
|
74
|
+
|
|
75
|
+
// Do not update name + do not link as member (no permissions inheritance here)
|
|
76
|
+
await this.linkUser(email, member, true);
|
|
62
77
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
for (const user of member.users) {
|
|
67
|
-
if (!
|
|
78
|
+
|
|
79
|
+
// Only auto unlink users that do not have an account
|
|
80
|
+
// note: we create a copy of the users array here because it is modified in the loop
|
|
81
|
+
for (const user of [...member.users]) {
|
|
82
|
+
if (!allEmails.includes(user.email.toLocaleLowerCase())) {
|
|
68
83
|
if (!user.hasAccount()) {
|
|
69
84
|
await this.unlinkUser(user, member);
|
|
70
85
|
}
|
|
71
86
|
else {
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
// Make sure only linked as a parent, not as user self
|
|
88
|
+
// This makes sure we don't inherit permissions and aren't counted as 'being' the member
|
|
74
89
|
await this.linkUser(user.email, member, true);
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
|
-
}
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
if (member.details.securityCode === null) {
|
|
95
|
+
console.log('Generating security code for member ' + member.id);
|
|
82
96
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
const length = 16;
|
|
98
|
+
const code = customBase.encode(await randomBytes(100)).toUpperCase().substring(0, length);
|
|
99
|
+
member.details.securityCode = code;
|
|
100
|
+
await member.save();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
88
103
|
});
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
getMemberAccessEmails(details: MemberDetails) {
|
|
92
|
-
const userEmails =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const unverifiedEmails: string[] = details.unverifiedEmails;
|
|
99
|
-
const parentAndUnverifiedEmails = details.parentsHaveAccess ? details.parents.flatMap(p => p.email ? [p.email, ...p.alternativeEmails] : p.alternativeEmails).concat(unverifiedEmails) : details.unverifiedEmails;
|
|
107
|
+
const userEmails = details.getMemberEmails().map(e => e.toLocaleLowerCase());
|
|
108
|
+
const parentEmails = details.getParentEmails().map(e => e.toLocaleLowerCase());
|
|
109
|
+
const unverifiedEmails = details.unverifiedEmails.map(e => e.toLocaleLowerCase());
|
|
110
|
+
const allEmails = [...userEmails, ...parentEmails, ...unverifiedEmails];
|
|
100
111
|
|
|
101
112
|
return {
|
|
113
|
+
allEmails,
|
|
102
114
|
userEmails,
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
parentEmails,
|
|
116
|
+
unverifiedEmails,
|
|
105
117
|
};
|
|
106
118
|
}
|
|
107
119
|
|
|
108
120
|
doesEmailHaveAccess(details: MemberDetails, email: string) {
|
|
109
|
-
const {
|
|
110
|
-
return
|
|
121
|
+
const { allEmails } = this.getMemberAccessEmails(details);
|
|
122
|
+
return allEmails.includes(email.toLocaleLowerCase());
|
|
111
123
|
}
|
|
112
124
|
|
|
113
|
-
async onDeleteMember(member:
|
|
125
|
+
async onDeleteMember(member: MemberWithUsers) {
|
|
114
126
|
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
115
127
|
for (const u of member.users) {
|
|
116
128
|
console.log('Unlinking user ' + u.email + ' from deleted member ' + member.id);
|
|
@@ -245,7 +257,7 @@ export class MemberUserSyncerStatic {
|
|
|
245
257
|
await user.save();
|
|
246
258
|
}
|
|
247
259
|
|
|
248
|
-
async unlinkUser(user: User, member:
|
|
260
|
+
async unlinkUser(user: User, member: MemberWithUsers) {
|
|
249
261
|
console.log('Removing access for ' + user.id + ' to member ' + member.id);
|
|
250
262
|
await Member.users.reverse('members').unlink(user, member);
|
|
251
263
|
|
|
@@ -254,6 +266,11 @@ export class MemberUserSyncerStatic {
|
|
|
254
266
|
|
|
255
267
|
if (user.memberId === member.id) {
|
|
256
268
|
user.memberId = null;
|
|
269
|
+
|
|
270
|
+
if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
|
|
271
|
+
user.firstName = null;
|
|
272
|
+
user.lastName = null;
|
|
273
|
+
}
|
|
257
274
|
}
|
|
258
275
|
|
|
259
276
|
// Update model relation to correct response
|
|
@@ -265,64 +282,84 @@ export class MemberUserSyncerStatic {
|
|
|
265
282
|
await this.updateInheritedPermissions(user);
|
|
266
283
|
}
|
|
267
284
|
|
|
268
|
-
async
|
|
285
|
+
private async resolveUserWithMultipleMembers(currentMemberId: string, otherMember: Member): Promise<'current' | 'other'> {
|
|
286
|
+
const otherMemberId = otherMember.id;
|
|
287
|
+
const responsibilities = await this.getResponsibilitiesForMembers([otherMemberId, currentMemberId]);
|
|
288
|
+
const responsibilitiesOther = responsibilities.filter(r => r.memberId === otherMemberId && r.getBaseStructure().isActive);
|
|
289
|
+
const responsibilitiesCurrent = responsibilities.filter(r => r.memberId === currentMemberId && r.getBaseStructure().isActive);
|
|
290
|
+
|
|
291
|
+
if (responsibilitiesOther.length > 0 && responsibilitiesCurrent.length === 0) {
|
|
292
|
+
return 'other';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (responsibilitiesCurrent.length > 0 && responsibilitiesOther.length === 0) {
|
|
296
|
+
return 'current';
|
|
297
|
+
}
|
|
298
|
+
const currentMember = await Member.getByID(currentMemberId);
|
|
299
|
+
if (!currentMember) {
|
|
300
|
+
return 'other';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (responsibilitiesOther.length > 0 && responsibilitiesCurrent.length > 0) {
|
|
304
|
+
// Resolve to oldest created member (this is always the most secure option)
|
|
305
|
+
if (currentMember.createdAt <= otherMember.createdAt) {
|
|
306
|
+
return 'current';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return 'other';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Resolve to newest member
|
|
313
|
+
if (currentMember.createdAt > otherMember.createdAt) {
|
|
314
|
+
return 'current';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return 'other';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async linkUser(email: string, member: MemberWithUsers, asParent: boolean, name: { firstName: string; lastName: string } | null = null) {
|
|
269
321
|
// This needs to happen in the queue to prevent creating duplicate users in the database
|
|
270
322
|
await QueueHandler.schedule('link-user/' + email, async () => {
|
|
271
|
-
console.log('Linking user', email, 'to member', member.id, 'as parent', asParent, 'update name if equal', updateNameIfEqual);
|
|
272
|
-
|
|
273
323
|
let user = member.users.find(u => u.email.toLocaleLowerCase() === email.toLocaleLowerCase()) ?? await User.getForAuthentication(member.organizationId, email, { allowWithoutAccount: true });
|
|
274
324
|
|
|
275
325
|
if (user) {
|
|
276
|
-
// console.log("Giving an existing user access to a member: " + user.id + ' - ' + member.id)
|
|
277
326
|
if (!asParent) {
|
|
327
|
+
let setMemberId = true;
|
|
278
328
|
if (user.memberId && user.memberId !== member.id) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (otherMember.registrations.length > 0 && member.registrations.length === 0) {
|
|
285
|
-
// Choose the other member
|
|
286
|
-
// don't make changes
|
|
287
|
-
console.error('Resolved to current member - no changes made');
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const responsibilities = await this.getResponsibilitiesForMembers([otherMember.id, member.id]);
|
|
292
|
-
const responsibilitiesOther = responsibilities.filter(r => r.memberId === otherMember.id);
|
|
293
|
-
const responsibilitiesCurrent = responsibilities.filter(r => r.memberId === member.id);
|
|
294
|
-
|
|
295
|
-
if (responsibilitiesOther.length >= responsibilitiesCurrent.length) {
|
|
296
|
-
console.error('Resolved to current member because of more responsibilities - no changes made');
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
329
|
+
const resolveTo = await this.resolveUserWithMultipleMembers(user.memberId, member);
|
|
330
|
+
|
|
331
|
+
if (resolveTo === 'current') {
|
|
332
|
+
// Do not change memberId
|
|
333
|
+
setMemberId = false;
|
|
299
334
|
}
|
|
300
335
|
}
|
|
301
336
|
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
337
|
+
if (setMemberId) {
|
|
338
|
+
if (name) {
|
|
339
|
+
user.firstName = name.firstName;
|
|
340
|
+
user.lastName = name.lastName;
|
|
341
|
+
}
|
|
342
|
+
user.memberId = member.id;
|
|
305
343
|
}
|
|
306
|
-
user.memberId = member.id;
|
|
307
344
|
await this.updateInheritedPermissions(user);
|
|
308
345
|
}
|
|
309
346
|
else {
|
|
310
347
|
let shouldSave = false;
|
|
311
348
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
user.lastName = parents[0].lastName;
|
|
318
|
-
}
|
|
349
|
+
// Clear clearly wrong name
|
|
350
|
+
if (user.firstName?.toLocaleLowerCase() === member.details.firstName?.toLocaleLowerCase() && user.lastName?.toLocaleLowerCase() === member.details.lastName?.toLocaleLowerCase()) {
|
|
351
|
+
if (!name || (name.firstName !== user.firstName && name.lastName !== user.lastName)) {
|
|
352
|
+
user.firstName = null;
|
|
353
|
+
user.lastName = null;
|
|
319
354
|
shouldSave = true;
|
|
320
355
|
}
|
|
321
356
|
}
|
|
322
357
|
|
|
323
|
-
if (user.firstName
|
|
324
|
-
|
|
325
|
-
|
|
358
|
+
if (!user.firstName && !user.lastName) {
|
|
359
|
+
if (name) {
|
|
360
|
+
user.firstName = name.firstName;
|
|
361
|
+
user.lastName = name.lastName;
|
|
362
|
+
}
|
|
326
363
|
shouldSave = true;
|
|
327
364
|
}
|
|
328
365
|
|
|
@@ -341,25 +378,17 @@ export class MemberUserSyncerStatic {
|
|
|
341
378
|
user = new User();
|
|
342
379
|
user.organizationId = member.organizationId;
|
|
343
380
|
user.email = email;
|
|
381
|
+
if (name) {
|
|
382
|
+
user.firstName = name.firstName;
|
|
383
|
+
user.lastName = name.lastName;
|
|
384
|
+
}
|
|
344
385
|
|
|
345
386
|
if (!asParent) {
|
|
346
|
-
if (updateNameIfEqual) {
|
|
347
|
-
user.firstName = member.details.firstName;
|
|
348
|
-
user.lastName = member.details.lastName;
|
|
349
|
-
}
|
|
350
387
|
user.memberId = member.id;
|
|
351
388
|
await this.updateInheritedPermissions(user);
|
|
352
389
|
}
|
|
353
390
|
else {
|
|
354
|
-
|
|
355
|
-
if (parents.length === 1) {
|
|
356
|
-
if (updateNameIfEqual) {
|
|
357
|
-
user.firstName = parents[0].firstName;
|
|
358
|
-
user.lastName = parents[0].lastName;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
|
|
391
|
+
if (!name && user.firstName?.toLocaleLowerCase() === member.details.firstName?.toLocaleLowerCase() && user.lastName?.toLocaleLowerCase() === member.details.lastName?.toLocaleLowerCase()) {
|
|
363
392
|
user.firstName = null;
|
|
364
393
|
user.lastName = null;
|
|
365
394
|
}
|
package/src/helpers/TagHelper.ts
CHANGED
|
@@ -10,12 +10,12 @@ export class TagHelper extends SharedTagHelper {
|
|
|
10
10
|
|
|
11
11
|
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
12
12
|
await QueueHandler.schedule(queueId, async () => {
|
|
13
|
-
|
|
13
|
+
const sharedPlatform = await Platform.getShared();
|
|
14
14
|
|
|
15
15
|
const tagCounts = new Map<string, number>();
|
|
16
16
|
|
|
17
17
|
for await (const organization of Organization.select().all()) {
|
|
18
|
-
organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags,
|
|
18
|
+
organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, sharedPlatform.config.tags);
|
|
19
19
|
|
|
20
20
|
for (const tag of organization.meta.tags) {
|
|
21
21
|
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
@@ -24,7 +24,7 @@ export class TagHelper extends SharedTagHelper {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Reload platform to avoid race conditions
|
|
27
|
-
platform = await Platform.
|
|
27
|
+
const platform = await Platform.getForEditing();
|
|
28
28
|
for (const [tag, count] of tagCounts.entries()) {
|
|
29
29
|
const tagObject = platform.config.tags.find(t => t.id === tag);
|
|
30
30
|
if (tagObject) {
|
|
@@ -44,7 +44,7 @@ export default new Migration(async () => {
|
|
|
44
44
|
console.log('Updated ' + c + ' registration periods');
|
|
45
45
|
|
|
46
46
|
// Now update platform
|
|
47
|
-
const platform = await Platform.
|
|
47
|
+
const platform = await Platform.getForEditing();
|
|
48
48
|
await platform.setPreviousPeriodId();
|
|
49
49
|
await platform.save();
|
|
50
50
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { AuditLog } from '@stamhoofd/models';
|
|
3
|
+
import { scalarToSQLExpression, SQL, SQLWhereLike } from '@stamhoofd/sql';
|
|
4
|
+
|
|
5
|
+
export default new Migration(async () => {
|
|
6
|
+
if (STAMHOOFD.environment === 'test') {
|
|
7
|
+
console.log('skipped in tests');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (STAMHOOFD.platformName !== 'keeo') {
|
|
12
|
+
console.log('skipped for platform (only run for keeo): ' + STAMHOOFD.platformName);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log('Start fixing audit log descriptions.');
|
|
17
|
+
|
|
18
|
+
const batchSize = 100;
|
|
19
|
+
let count = 0;
|
|
20
|
+
|
|
21
|
+
for await (const log of AuditLog.select()
|
|
22
|
+
.where(new SQLWhereLike(
|
|
23
|
+
SQL.column('audit_logs', 'description'),
|
|
24
|
+
scalarToSQLExpression(`%werd verplaatst van % naar %, door%`),
|
|
25
|
+
)).limit(batchSize).all()) {
|
|
26
|
+
if (log.meta.get('fixed') === 1) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
log.description = fixDescription(log.description);
|
|
30
|
+
log.meta.set('fixed', 1);
|
|
31
|
+
await log.save();
|
|
32
|
+
count += 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('Finished saving ' + count + ' audit logs.');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function fixDescription(description: string): string {
|
|
39
|
+
const regex = /werd verplaatst van (.+) naar (.*), door/;
|
|
40
|
+
const result = description.match(regex);
|
|
41
|
+
if (!result) {
|
|
42
|
+
throw new Error(`Could not fix audit log description, no regex match found: ${description}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const to = result[1];
|
|
46
|
+
const from = result[2];
|
|
47
|
+
|
|
48
|
+
const fixed = description.replace(regex, `werd verplaatst van ${from} naar ${to}, door`);
|
|
49
|
+
return fixed;
|
|
50
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { AuditLog } from '@stamhoofd/models';
|
|
3
|
+
import { scalarToSQLExpression, SQL, SQLWhereLike } from '@stamhoofd/sql';
|
|
4
|
+
|
|
5
|
+
const replacements: [from: string, to: string][] = [
|
|
6
|
+
['u00eb', 'ë'],
|
|
7
|
+
['u00E9', 'é'],
|
|
8
|
+
['U00EF', 'ï'],
|
|
9
|
+
['U00E6', 'æ'],
|
|
10
|
+
['U00F6', 'ö'],
|
|
11
|
+
['U00E0', 'à'],
|
|
12
|
+
['U00E1', 'á'],
|
|
13
|
+
['U00E7', 'ç'],
|
|
14
|
+
['U00E4', 'ä'],
|
|
15
|
+
['U00E5', 'å'],
|
|
16
|
+
['U00E8', 'è'],
|
|
17
|
+
['U00F3', 'ó'],
|
|
18
|
+
['U00ED', 'í'],
|
|
19
|
+
['U00C4', 'Ä'],
|
|
20
|
+
['U00C1', 'Á'],
|
|
21
|
+
['U00FC', 'ü'],
|
|
22
|
+
['U00F4', 'ö'],
|
|
23
|
+
['U00D6', 'Ö'],
|
|
24
|
+
['U00C9', 'É'],
|
|
25
|
+
['U00F2', 'ò'],
|
|
26
|
+
['U00E2', 'â'],
|
|
27
|
+
['U00F2', 'ò'],
|
|
28
|
+
['U00F2', 'ò'],
|
|
29
|
+
['U00E2', 'â'],
|
|
30
|
+
['U00EC', 'ì'],
|
|
31
|
+
['U00C5', 'Å'],
|
|
32
|
+
['U00EE', 'î'],
|
|
33
|
+
['U00A9', '©'],
|
|
34
|
+
['U00C3', 'Ã'],
|
|
35
|
+
['U00C7', 'Ç'],
|
|
36
|
+
['U00F9', 'ù'],
|
|
37
|
+
['U00EA', 'ê'],
|
|
38
|
+
['U00FF', 'ÿ'],
|
|
39
|
+
['U00CA', 'Ê'],
|
|
40
|
+
['U00CD', 'Í'],
|
|
41
|
+
['U00A8', '¨'],
|
|
42
|
+
['U00AF', '¯'],
|
|
43
|
+
['U00AB', '«'],
|
|
44
|
+
['U00CE', 'Î'],
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export default new Migration(async () => {
|
|
48
|
+
if (STAMHOOFD.environment === 'test') {
|
|
49
|
+
console.log('skipped in tests');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (STAMHOOFD.platformName !== 'keeo') {
|
|
54
|
+
console.log('skipped for platform (only run for keeo): ' + STAMHOOFD.platformName);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('Start fixing audit log uft16 descriptions.');
|
|
59
|
+
|
|
60
|
+
const batchSize = 100;
|
|
61
|
+
let count = 0;
|
|
62
|
+
|
|
63
|
+
for await (const log of AuditLog.select()
|
|
64
|
+
.where(new SQLWhereLike(
|
|
65
|
+
SQL.column('audit_logs', 'description'),
|
|
66
|
+
scalarToSQLExpression(`%u00%`),
|
|
67
|
+
))
|
|
68
|
+
.limit(batchSize).all()) {
|
|
69
|
+
if (log.meta.get('fixed-uft16') === 1) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
log.description = fixDescription(log.description);
|
|
73
|
+
log.meta.set('fixed-uft16', 1);
|
|
74
|
+
await log.save();
|
|
75
|
+
count += 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log('Finished saving ' + count + ' audit logs uft16.');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function fixDescription(description: string): string {
|
|
82
|
+
let result = description;
|
|
83
|
+
replacements.forEach(([from, to]) => {
|
|
84
|
+
result = result.replace(new RegExp(from.toLowerCase(), 'g'), to);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
@@ -169,6 +169,13 @@ export class PlatformMembershipService {
|
|
|
169
169
|
console.warn('No periods found for member ' + me.id);
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
const types = platform.config.membershipTypes.filter(m => m.behaviour === PlatformMembershipTypeBehaviour.Period).map(m => m.id);
|
|
173
|
+
if (types.length === 0) {
|
|
174
|
+
if (!silent) {
|
|
175
|
+
console.warn('No membership types found for memberships');
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
172
179
|
|
|
173
180
|
// Every (not-locked) period can have a generated membership
|
|
174
181
|
for (const period of periods) {
|
|
@@ -200,8 +207,6 @@ export class PlatformMembershipService {
|
|
|
200
207
|
}];
|
|
201
208
|
});
|
|
202
209
|
|
|
203
|
-
const types = platform.config.membershipTypes.filter(m => m.behaviour === PlatformMembershipTypeBehaviour.Period).map(m => m.id);
|
|
204
|
-
|
|
205
210
|
// Get active memberships for this member that
|
|
206
211
|
const activeMemberships = await MemberPlatformMembership.where({
|
|
207
212
|
memberId: me.id,
|
|
@@ -130,7 +130,7 @@ export class SSOService {
|
|
|
130
130
|
|
|
131
131
|
static async fromContext(provider: LoginProviderType) {
|
|
132
132
|
const organization = Context.organization;
|
|
133
|
-
const platform = await Platform.
|
|
133
|
+
const platform = await Platform.getForEditing();
|
|
134
134
|
|
|
135
135
|
const service = new SSOService({ provider, platform, organization });
|
|
136
136
|
service.validateConfiguration();
|
|
@@ -1035,7 +1035,7 @@ describe('E2E.Register', () => {
|
|
|
1035
1035
|
]),
|
|
1036
1036
|
});
|
|
1037
1037
|
|
|
1038
|
-
const platform = await Platform.
|
|
1038
|
+
const platform = await Platform.getForEditing();
|
|
1039
1039
|
|
|
1040
1040
|
platform.config.membershipTypes = [
|
|
1041
1041
|
platformMembershipType,
|
|
@@ -1135,7 +1135,7 @@ describe('E2E.Register', () => {
|
|
|
1135
1135
|
]),
|
|
1136
1136
|
});
|
|
1137
1137
|
|
|
1138
|
-
const platform = await Platform.
|
|
1138
|
+
const platform = await Platform.getForEditing();
|
|
1139
1139
|
|
|
1140
1140
|
platform.config.membershipTypes = [
|
|
1141
1141
|
platformMembershipType,
|