@stamhoofd/backend 2.27.2 → 2.28.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.27.2",
3
+ "version": "2.28.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.27.2",
40
- "@stamhoofd/backend-middleware": "2.27.2",
41
- "@stamhoofd/email": "2.27.2",
42
- "@stamhoofd/models": "2.27.2",
43
- "@stamhoofd/queues": "2.27.2",
44
- "@stamhoofd/sql": "2.27.2",
45
- "@stamhoofd/structures": "2.27.2",
46
- "@stamhoofd/utility": "2.27.2",
39
+ "@stamhoofd/backend-i18n": "2.28.0",
40
+ "@stamhoofd/backend-middleware": "2.28.0",
41
+ "@stamhoofd/email": "2.28.0",
42
+ "@stamhoofd/models": "2.28.0",
43
+ "@stamhoofd/queues": "2.28.0",
44
+ "@stamhoofd/sql": "2.28.0",
45
+ "@stamhoofd/structures": "2.28.0",
46
+ "@stamhoofd/utility": "2.28.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": "4c7804970b02fa339d884ace0105a6efc6dab179"
63
+ "gitHead": "3598a1baed3fc513175f081cd0aa1e6741226fbd"
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
- //return;
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 (q.search.includes('@')) {
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))
@@ -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(