@stamhoofd/backend 2.27.2 → 2.29.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/package.json +10 -10
- package/src/crons.ts +5 -1
- package/src/endpoints/global/members/GetMembersEndpoint.ts +53 -4
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +1 -1
- package/src/helpers/AdminPermissionChecker.ts +6 -1
- package/src/helpers/MemberUserSyncer.ts +11 -2
- package/src/sql-filters/members.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.29.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"@simonbackx/simple-encoding": "2.15.1",
|
|
37
37
|
"@simonbackx/simple-endpoints": "1.14.0",
|
|
38
38
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
39
|
-
"@stamhoofd/backend-i18n": "2.
|
|
40
|
-
"@stamhoofd/backend-middleware": "2.
|
|
41
|
-
"@stamhoofd/email": "2.
|
|
42
|
-
"@stamhoofd/models": "2.
|
|
43
|
-
"@stamhoofd/queues": "2.
|
|
44
|
-
"@stamhoofd/sql": "2.
|
|
45
|
-
"@stamhoofd/structures": "2.
|
|
46
|
-
"@stamhoofd/utility": "2.
|
|
39
|
+
"@stamhoofd/backend-i18n": "2.29.0",
|
|
40
|
+
"@stamhoofd/backend-middleware": "2.29.0",
|
|
41
|
+
"@stamhoofd/email": "2.29.0",
|
|
42
|
+
"@stamhoofd/models": "2.29.0",
|
|
43
|
+
"@stamhoofd/queues": "2.29.0",
|
|
44
|
+
"@stamhoofd/sql": "2.29.0",
|
|
45
|
+
"@stamhoofd/structures": "2.29.0",
|
|
46
|
+
"@stamhoofd/utility": "2.29.0",
|
|
47
47
|
"archiver": "^7.0.1",
|
|
48
48
|
"aws-sdk": "^2.885.0",
|
|
49
49
|
"axios": "1.6.8",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"postmark": "4.0.2",
|
|
61
61
|
"stripe": "^16.6.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "0579111a61319f41f3789c570e9f494a648bd07c"
|
|
64
64
|
}
|
package/src/crons.ts
CHANGED
|
@@ -586,7 +586,11 @@ let lastDripCheck: Date | null = null
|
|
|
586
586
|
let lastDripId = ""
|
|
587
587
|
async function checkDrips() {
|
|
588
588
|
if (STAMHOOFD.environment === "development") {
|
|
589
|
-
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (STAMHOOFD.userMode === 'platform') {
|
|
593
|
+
return;
|
|
590
594
|
}
|
|
591
595
|
|
|
592
596
|
if (lastDripCheck && lastDripCheck > new Date(new Date().getTime() - 6 * 60 * 60 * 1000)) {
|
|
@@ -4,9 +4,10 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
5
|
import { Member, Platform } from '@stamhoofd/models';
|
|
6
6
|
import { SQL, compileToSQLFilter, compileToSQLSorter } from "@stamhoofd/sql";
|
|
7
|
-
import { CountFilteredRequest, LimitedFilteredRequest, MembersBlob, PaginatedResponse, PermissionLevel, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
|
|
7
|
+
import { CountFilteredRequest, Country, LimitedFilteredRequest, MembersBlob, PaginatedResponse, PermissionLevel, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
|
|
8
8
|
import { DataValidator } from '@stamhoofd/utility';
|
|
9
9
|
|
|
10
|
+
import parsePhoneNumber from "libphonenumber-js/max";
|
|
10
11
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
11
12
|
import { Context } from '../../../helpers/Context';
|
|
12
13
|
import { memberFilterCompilers } from '../../../sql-filters/members';
|
|
@@ -131,11 +132,55 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
if (q.search) {
|
|
134
|
-
let searchFilter: StamhoofdFilter|null = null
|
|
135
|
+
let searchFilter: StamhoofdFilter|null = null
|
|
136
|
+
|
|
137
|
+
// is phone?
|
|
138
|
+
if (!searchFilter && q.search.match(/^\+?[0-9\s-]+$/)) {
|
|
139
|
+
// Try to format as phone so we have 1:1 space matches
|
|
140
|
+
try {
|
|
141
|
+
const phoneNumber = parsePhoneNumber(q.search, (Context.i18n.country as Country) || Country.Belgium)
|
|
142
|
+
if (phoneNumber && phoneNumber.isValid()) {
|
|
143
|
+
const formatted = phoneNumber.formatInternational();
|
|
144
|
+
searchFilter = {
|
|
145
|
+
$or: [
|
|
146
|
+
{
|
|
147
|
+
phone: {
|
|
148
|
+
$eq: formatted
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
parentPhone: {
|
|
153
|
+
$eq: formatted
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
unverifiedPhone: {
|
|
158
|
+
$eq: formatted
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error('Failed to parse phone number', q.search, e)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Is lidnummer?
|
|
171
|
+
if (!searchFilter && q.search.match(/^[0-9]{4}-[0-9]{6}-[0-9]{1,2}$/) || q.search.match(/^[0-9]{10}$/)) {
|
|
172
|
+
searchFilter = {
|
|
173
|
+
memberNumber: {
|
|
174
|
+
$eq: q.search
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
135
178
|
|
|
136
179
|
// Two search modes:
|
|
137
180
|
// e-mail or name based searching
|
|
138
|
-
if (
|
|
181
|
+
if (searchFilter) {
|
|
182
|
+
// already done
|
|
183
|
+
} else if (q.search.includes('@')) {
|
|
139
184
|
const isCompleteAddress = DataValidator.isEmailValid(q.search);
|
|
140
185
|
|
|
141
186
|
// Member email address contains, or member parent contains
|
|
@@ -150,6 +195,11 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
150
195
|
parentEmail: {
|
|
151
196
|
[(isCompleteAddress ? '$eq' : '$contains')]: q.search
|
|
152
197
|
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
unverifiedEmail: {
|
|
201
|
+
[(isCompleteAddress ? '$eq' : '$contains')]: q.search
|
|
202
|
+
}
|
|
153
203
|
}
|
|
154
204
|
]
|
|
155
205
|
} as any as StamhoofdFilter
|
|
@@ -162,7 +212,6 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
162
212
|
}
|
|
163
213
|
|
|
164
214
|
// todo: Address search detection
|
|
165
|
-
// todo: Phone number search detection
|
|
166
215
|
|
|
167
216
|
if (searchFilter) {
|
|
168
217
|
query.where(compileToSQLFilter(searchFilter, filterCompilers))
|
|
@@ -121,7 +121,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
121
121
|
throw new SimpleError({
|
|
122
122
|
code: "forbidden",
|
|
123
123
|
message: "No permission to register this member",
|
|
124
|
-
human: 'Je hebt niet de juiste toegangsrechten om dit lid in te schrijven.',
|
|
124
|
+
human: 'Je hebt niet de juiste toegangsrechten om dit lid in te schrijven. Je kan enkel leden inschrijven als je minstens bewerkrechten hebt voor dat lid.',
|
|
125
125
|
statusCode: 403
|
|
126
126
|
});
|
|
127
127
|
}
|
|
@@ -271,9 +271,14 @@ export class AdminPermissionChecker {
|
|
|
271
271
|
return true;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
if (registration.deactivatedAt || !registration.registeredAt) {
|
|
275
|
+
// No full access: cannot access deactivated registrations
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
274
279
|
const allGroups = await this.getOrganizationGroups(registration.organizationId)
|
|
275
280
|
const group = allGroups.find(g => g.id === registration.groupId)
|
|
276
|
-
if (!group) {
|
|
281
|
+
if (!group || group.deletedAt) {
|
|
277
282
|
return false;
|
|
278
283
|
}
|
|
279
284
|
|
|
@@ -9,8 +9,12 @@ export class MemberUserSyncerStatic {
|
|
|
9
9
|
* - email addresses have changed
|
|
10
10
|
*/
|
|
11
11
|
async onChangeMember(member: MemberWithRegistrations, unlinkUsers: boolean = false) {
|
|
12
|
+
console.log('onchange member', member.id, 'unlink', unlinkUsers)
|
|
13
|
+
|
|
12
14
|
const {userEmails, parentAndUnverifiedEmails} = this.getMemberAccessEmails(member.details)
|
|
13
15
|
|
|
16
|
+
console.log('emails', userEmails, parentAndUnverifiedEmails)
|
|
17
|
+
|
|
14
18
|
// Make sure all these users have access to the member
|
|
15
19
|
for (const email of userEmails) {
|
|
16
20
|
// Link users that are found with these email addresses.
|
|
@@ -18,8 +22,11 @@ export class MemberUserSyncerStatic {
|
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
for (const email of parentAndUnverifiedEmails) {
|
|
25
|
+
if (userEmails.includes(email)) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
// Link parents and unverified emails
|
|
22
|
-
|
|
23
30
|
// Now we add the responsibility permissions to the parent if there are no userEmails
|
|
24
31
|
await this.linkUser(email, member, userEmails.length > 0, userEmails.length > 0)
|
|
25
32
|
}
|
|
@@ -59,7 +66,7 @@ export class MemberUserSyncerStatic {
|
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
const unverifiedEmails: string[] = details.unverifiedEmails;
|
|
62
|
-
const parentAndUnverifiedEmails = details.parentsHaveAccess ? details.parents.flatMap(p => p.email ? [p.email, ...p.alternativeEmails] : p.alternativeEmails).concat(unverifiedEmails) :
|
|
69
|
+
const parentAndUnverifiedEmails = details.parentsHaveAccess ? details.parents.flatMap(p => p.email ? [p.email, ...p.alternativeEmails] : p.alternativeEmails).concat(unverifiedEmails) : details.unverifiedEmails
|
|
63
70
|
|
|
64
71
|
return {
|
|
65
72
|
userEmails,
|
|
@@ -163,6 +170,8 @@ export class MemberUserSyncerStatic {
|
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
async linkUser(email: string, member: MemberWithRegistrations, asParent: boolean, updateNameIfEqual = true) {
|
|
173
|
+
console.log('Linking user', email, 'to member', member.id, 'as parent', asParent, 'update name if equal', updateNameIfEqual)
|
|
174
|
+
|
|
166
175
|
let user = member.users.find(u => u.email.toLocaleLowerCase() === email.toLocaleLowerCase()) ?? await User.getForAuthentication(member.organizationId, email, {allowWithoutAccount: true})
|
|
167
176
|
|
|
168
177
|
if (user) {
|
|
@@ -9,6 +9,7 @@ import { registrationFilterCompilers } from "./registrations";
|
|
|
9
9
|
export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
10
10
|
...baseSQLFilterCompilers,
|
|
11
11
|
id: createSQLColumnFilterCompiler('id'),
|
|
12
|
+
memberNumber: createSQLColumnFilterCompiler('memberNumber'),
|
|
12
13
|
firstName: createSQLColumnFilterCompiler('firstName'),
|
|
13
14
|
lastName: createSQLColumnFilterCompiler('lastName'),
|
|
14
15
|
name: createSQLExpressionFilterCompiler(
|
|
@@ -49,6 +50,26 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
49
50
|
{isJSONValue: true, isJSONObject: true}
|
|
50
51
|
),
|
|
51
52
|
|
|
53
|
+
unverifiedEmail: createSQLExpressionFilterCompiler(
|
|
54
|
+
SQL.jsonValue(SQL.column('details'), '$.value.unverifiedEmails'),
|
|
55
|
+
{isJSONValue: true, isJSONObject: true}
|
|
56
|
+
),
|
|
57
|
+
|
|
58
|
+
phone: createSQLExpressionFilterCompiler(
|
|
59
|
+
SQL.jsonValue(SQL.column('details'), '$.value.phone'),
|
|
60
|
+
{isJSONValue: true}
|
|
61
|
+
),
|
|
62
|
+
|
|
63
|
+
parentPhone: createSQLExpressionFilterCompiler(
|
|
64
|
+
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].phone'),
|
|
65
|
+
{isJSONValue: true, isJSONObject: true}
|
|
66
|
+
),
|
|
67
|
+
|
|
68
|
+
unverifiedPhone: createSQLExpressionFilterCompiler(
|
|
69
|
+
SQL.jsonValue(SQL.column('details'), '$.value.unverifiedPhones'),
|
|
70
|
+
{isJSONValue: true, isJSONObject: true}
|
|
71
|
+
),
|
|
72
|
+
|
|
52
73
|
registrations: createSQLRelationFilterCompiler(
|
|
53
74
|
SQL.select()
|
|
54
75
|
.from(
|