@stamhoofd/backend 2.75.0 → 2.75.2

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.75.0",
3
+ "version": "2.75.2",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -37,14 +37,14 @@
37
37
  "@simonbackx/simple-encoding": "2.20.0",
38
38
  "@simonbackx/simple-endpoints": "1.19.0",
39
39
  "@simonbackx/simple-logging": "^1.0.1",
40
- "@stamhoofd/backend-i18n": "2.75.0",
41
- "@stamhoofd/backend-middleware": "2.75.0",
42
- "@stamhoofd/email": "2.75.0",
43
- "@stamhoofd/models": "2.75.0",
44
- "@stamhoofd/queues": "2.75.0",
45
- "@stamhoofd/sql": "2.75.0",
46
- "@stamhoofd/structures": "2.75.0",
47
- "@stamhoofd/utility": "2.75.0",
40
+ "@stamhoofd/backend-i18n": "2.75.2",
41
+ "@stamhoofd/backend-middleware": "2.75.2",
42
+ "@stamhoofd/email": "2.75.2",
43
+ "@stamhoofd/models": "2.75.2",
44
+ "@stamhoofd/queues": "2.75.2",
45
+ "@stamhoofd/sql": "2.75.2",
46
+ "@stamhoofd/structures": "2.75.2",
47
+ "@stamhoofd/utility": "2.75.2",
48
48
  "archiver": "^7.0.1",
49
49
  "aws-sdk": "^2.885.0",
50
50
  "axios": "1.6.8",
@@ -64,5 +64,5 @@
64
64
  "publishConfig": {
65
65
  "access": "public"
66
66
  },
67
- "gitHead": "4710a9386f9f489356e04518622cc83c5faa2bf1"
67
+ "gitHead": "aafa49d4e89f748e62f364b52a40efe339206779"
68
68
  }
@@ -0,0 +1,43 @@
1
+ import { AutoEncoder, field, StringDecoder } from '@simonbackx/simple-encoding';
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+
4
+ import { Context } from '../../helpers/Context';
5
+ import { SSOService } from '../../services/SSOService';
6
+ import { OpenIDAuthTokenResponse } from '@stamhoofd/structures';
7
+
8
+ type Params = Record<string, never>;
9
+ type Query = undefined;
10
+ type Body = undefined;
11
+ type ResponseBody = OpenIDAuthTokenResponse;
12
+
13
+ /**
14
+ * This endpoint does nothing but build a URL to start the OpenID Connect flow.
15
+ * It is used to provide authenticateion data to the url that is temporarily valid (allows to connect an SSO provider to an existing account)
16
+ */
17
+ export class OpenIDConnectAuthTokenEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
18
+ protected doesMatch(request: Request): [true, Params] | [false] {
19
+ if (request.method !== 'POST') {
20
+ return [false];
21
+ }
22
+
23
+ const params = Endpoint.parseParameters(request.url, '/openid/auth-token', {});
24
+
25
+ if (params) {
26
+ return [true, params as Params];
27
+ }
28
+ return [false];
29
+ }
30
+
31
+ async handle(request: DecodedRequest<Params, Query, Body>) {
32
+ // Check webshop and/or organization
33
+ await Context.setUserOrganizationScope();
34
+ await Context.authenticate({ allowWithoutAccount: false });
35
+
36
+ // Create a SSO auth token that can only be used once
37
+ const token = await SSOService.createToken();
38
+
39
+ return new Response(OpenIDAuthTokenResponse.create({
40
+ ssoAuthToken: token,
41
+ }));
42
+ }
43
+ }
@@ -6,15 +6,15 @@ import { Context } from '../../helpers/Context';
6
6
  import { SSOService } from '../../services/SSOService';
7
7
 
8
8
  type Params = Record<string, never>;
9
- type Query = undefined;
10
- type Body = StartOpenIDFlowStruct;
9
+ type Query = StartOpenIDFlowStruct;
10
+ type Body = undefined;
11
11
  type ResponseBody = undefined;
12
12
 
13
13
  export class OpenIDConnectStartEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
- bodyDecoder = StartOpenIDFlowStruct as Decoder<StartOpenIDFlowStruct>;
14
+ queryDecoder = StartOpenIDFlowStruct as Decoder<StartOpenIDFlowStruct>;
15
15
 
16
16
  protected doesMatch(request: Request): [true, Params] | [false] {
17
- if (request.method !== 'POST') {
17
+ if (request.method !== 'GET') {
18
18
  return [false];
19
19
  }
20
20
 
@@ -29,13 +29,7 @@ export class OpenIDConnectStartEndpoint extends Endpoint<Params, Query, Body, Re
29
29
  async handle(request: DecodedRequest<Params, Query, Body>) {
30
30
  // Check webshop and/or organization
31
31
  await Context.setUserOrganizationScope();
32
- await Context.optionalAuthenticate({ allowWithoutAccount: false });
33
- console.log('Full start connect body;', await request.request.body);
34
-
35
- if (Context.user) {
36
- console.log('User:', Context.user);
37
- }
38
- const service = await SSOService.fromContext(request.body.provider);
39
- return await service.validateAndStartAuthCodeFlow(request.body);
32
+ const service = await SSOService.fromContext(request.query.provider);
33
+ return await service.validateAndStartAuthCodeFlow(request.query);
40
34
  }
41
35
  }
@@ -1,7 +1,7 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { Email } from '@stamhoofd/email';
3
3
  import { BalanceItemFactory, Group, GroupFactory, MemberFactory, MemberWithRegistrations, Organization, OrganizationFactory, Registration, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
4
- import { BalanceItemCartItem, BalanceItemType, Company, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, OrganizationPackages, PayconiqAccount, PaymentCustomer, PaymentMethod, PermissionLevel, Permissions, STPackageStatus, STPackageType, UserPermissions, Version } from '@stamhoofd/structures';
4
+ import { BalanceItemCartItem, BalanceItemType, Company, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, OrganizationPackages, PayconiqAccount, PaymentCustomer, PaymentMethod, PermissionLevel, Permissions, ReduceablePrice, RegisterItemOption, STPackageStatus, STPackageType, UserPermissions, Version } from '@stamhoofd/structures';
5
5
  import nock from 'nock';
6
6
  import { v4 as uuidv4 } from 'uuid';
7
7
  import { testServer } from '../../../../tests/helpers/TestServer';
@@ -1011,6 +1011,334 @@ describe('Endpoint.RegisterMembers', () => {
1011
1011
  jest.useFakeTimers().resetAllMocks();
1012
1012
  }
1013
1013
  });
1014
+
1015
+ test('Should update group stock reservations', async () => {
1016
+ // #region arrange
1017
+ const { organization, group, groupPrice, token, member } = await initData();
1018
+ groupPrice.stock = 5;
1019
+ await group.save();
1020
+
1021
+ const body = IDRegisterCheckout.create({
1022
+ cart: IDRegisterCart.create({
1023
+ items: [
1024
+ IDRegisterItem.create({
1025
+ id: uuidv4(),
1026
+ replaceRegistrationIds: [],
1027
+ options: [],
1028
+ groupPrice,
1029
+ organizationId: organization.id,
1030
+ groupId: group.id,
1031
+ memberId: member.id,
1032
+ }),
1033
+ ],
1034
+ balanceItems: [],
1035
+ deleteRegistrationIds: [],
1036
+ }),
1037
+ administrationFee: 0,
1038
+ freeContribution: 0,
1039
+ paymentMethod: PaymentMethod.PointOfSale,
1040
+ totalPrice: 25,
1041
+ asOrganizationId: organization.id,
1042
+ customer: null,
1043
+ });
1044
+ // #endregion
1045
+
1046
+ // #region act and assert
1047
+ expect(group?.stockReservations.length).toBe(0);
1048
+
1049
+ await post(body, organization, token);
1050
+
1051
+ const updatedGroup = await Group.getByID(group.id);
1052
+ expect(updatedGroup?.stockReservations.length).toBe(1);
1053
+ // #endregion
1054
+ });
1055
+
1056
+ test('Should fail if group price stock sold out', async () => {
1057
+ // #region arrange
1058
+ const { organization, group, groupPrice, token, member, otherMembers } = await initData({
1059
+ permissionLevel: PermissionLevel.Read, otherMemberAmount: 3 });
1060
+ groupPrice.stock = 2;
1061
+ await group.save();
1062
+
1063
+ const body = IDRegisterCheckout.create({
1064
+ cart: IDRegisterCart.create({
1065
+ items: [
1066
+ IDRegisterItem.create({
1067
+ id: uuidv4(),
1068
+ replaceRegistrationIds: [],
1069
+ options: [],
1070
+ groupPrice,
1071
+ organizationId: organization.id,
1072
+ groupId: group.id,
1073
+ memberId: member.id,
1074
+ }),
1075
+ ...otherMembers.map(m => IDRegisterItem.create({
1076
+ id: uuidv4(),
1077
+ replaceRegistrationIds: [],
1078
+ options: [],
1079
+ groupPrice,
1080
+ organizationId: organization.id,
1081
+ groupId: group.id,
1082
+ memberId: m.id,
1083
+ })),
1084
+ ],
1085
+ balanceItems: [],
1086
+ deleteRegistrationIds: [],
1087
+ }),
1088
+ administrationFee: 0,
1089
+ freeContribution: 0,
1090
+ paymentMethod: PaymentMethod.PointOfSale,
1091
+ totalPrice: 75,
1092
+ customer: null,
1093
+ });
1094
+ // #endregion
1095
+
1096
+ // #region act and assert
1097
+ expect(group?.stockReservations.length).toBe(0);
1098
+
1099
+ await expect(async () => await post(body, organization, token))
1100
+ .rejects
1101
+ .toThrow(new RegExp('Maximum reached'));
1102
+ // #endregion
1103
+ });
1104
+
1105
+ test('Should fail if option stock sold out', async () => {
1106
+ // #region arrange
1107
+ const { organization, group, groupPrice, token, member } = await initData();
1108
+
1109
+ const option1 = GroupOption.create({
1110
+ name: 'option 1',
1111
+ stock: 4,
1112
+ price: ReduceablePrice.create({
1113
+ price: 5,
1114
+ reducedPrice: 3,
1115
+ }),
1116
+ });
1117
+
1118
+ const option2 = GroupOption.create({
1119
+ name: 'option 2',
1120
+ stock: 4,
1121
+ price: ReduceablePrice.create({
1122
+ price: 3,
1123
+ reducedPrice: 1,
1124
+ }),
1125
+ });
1126
+
1127
+ const optionMenu = GroupOptionMenu.create({
1128
+ name: 'option menu 1',
1129
+ multipleChoice: true,
1130
+ options: [option1, option2],
1131
+ });
1132
+
1133
+ group.settings.optionMenus = [
1134
+ optionMenu,
1135
+ ];
1136
+
1137
+ await group.save();
1138
+
1139
+ const body = IDRegisterCheckout.create({
1140
+ cart: IDRegisterCart.create({
1141
+ items: [
1142
+ IDRegisterItem.create({
1143
+ id: uuidv4(),
1144
+ replaceRegistrationIds: [],
1145
+ options: [
1146
+ RegisterItemOption.create({
1147
+ option: option1,
1148
+ amount: 2,
1149
+ optionMenu,
1150
+ }),
1151
+ RegisterItemOption.create({
1152
+ option: option2,
1153
+ amount: 5,
1154
+ optionMenu,
1155
+ }),
1156
+ ],
1157
+ groupPrice,
1158
+ organizationId: organization.id,
1159
+ groupId: group.id,
1160
+ memberId: member.id,
1161
+ }),
1162
+ ],
1163
+ balanceItems: [
1164
+ ],
1165
+ deleteRegistrationIds: [],
1166
+ }),
1167
+ administrationFee: 0,
1168
+ freeContribution: 0,
1169
+ paymentMethod: PaymentMethod.PointOfSale,
1170
+ totalPrice: 50,
1171
+ customer: null,
1172
+ });
1173
+ // #endregion
1174
+
1175
+ // #region act and assert
1176
+ await expect(async () => await post(body, organization, token))
1177
+ .rejects
1178
+ .toThrow(new RegExp('Stock empty'));
1179
+ // #endregion
1180
+ });
1181
+
1182
+ test('Should fail if max option exceeded', async () => {
1183
+ // #region arrange
1184
+ const { organization, group, groupPrice, token, member } = await initData();
1185
+
1186
+ const option1 = GroupOption.create({
1187
+ name: 'option 1',
1188
+ stock: 4,
1189
+ maximum: 5,
1190
+ allowAmount: true,
1191
+ price: ReduceablePrice.create({
1192
+ price: 5,
1193
+ reducedPrice: 3,
1194
+ }),
1195
+ });
1196
+
1197
+ const option2 = GroupOption.create({
1198
+ name: 'option 2',
1199
+ stock: 5,
1200
+ maximum: 2,
1201
+ allowAmount: true,
1202
+ price: ReduceablePrice.create({
1203
+ price: 3,
1204
+ reducedPrice: 1,
1205
+ }),
1206
+ });
1207
+
1208
+ const optionMenu = GroupOptionMenu.create({
1209
+ name: 'option menu 1',
1210
+ multipleChoice: true,
1211
+ options: [option1, option2],
1212
+ });
1213
+
1214
+ group.settings.optionMenus = [
1215
+ optionMenu,
1216
+ ];
1217
+
1218
+ await group.save();
1219
+
1220
+ const body = IDRegisterCheckout.create({
1221
+ cart: IDRegisterCart.create({
1222
+ items: [
1223
+ IDRegisterItem.create({
1224
+ id: uuidv4(),
1225
+ replaceRegistrationIds: [],
1226
+ options: [
1227
+ RegisterItemOption.create({
1228
+ option: option1,
1229
+ amount: 2,
1230
+ optionMenu,
1231
+ }),
1232
+ RegisterItemOption.create({
1233
+ option: option2,
1234
+ amount: 5,
1235
+ optionMenu,
1236
+ }),
1237
+ ],
1238
+ groupPrice,
1239
+ organizationId: organization.id,
1240
+ groupId: group.id,
1241
+ memberId: member.id,
1242
+ }),
1243
+ ],
1244
+ balanceItems: [
1245
+ ],
1246
+ deleteRegistrationIds: [],
1247
+ }),
1248
+ administrationFee: 0,
1249
+ freeContribution: 0,
1250
+ paymentMethod: PaymentMethod.PointOfSale,
1251
+ totalPrice: 50,
1252
+ customer: null,
1253
+ });
1254
+ // #endregion
1255
+
1256
+ // #region act and assert
1257
+ await expect(async () => await post(body, organization, token))
1258
+ .rejects
1259
+ .toThrow(new RegExp('Option maximum exceeded'));
1260
+ // #endregion
1261
+ });
1262
+
1263
+ test('Should not fail if max option not exceeded', async () => {
1264
+ // #region arrange
1265
+ const { organization, group, groupPrice, token, member } = await initData();
1266
+
1267
+ const option1 = GroupOption.create({
1268
+ name: 'option 1',
1269
+ stock: 4,
1270
+ maximum: 5,
1271
+ allowAmount: true,
1272
+ price: ReduceablePrice.create({
1273
+ price: 5,
1274
+ reducedPrice: 3,
1275
+ }),
1276
+ });
1277
+
1278
+ const option2 = GroupOption.create({
1279
+ name: 'option 2',
1280
+ stock: 5,
1281
+ maximum: 5,
1282
+ allowAmount: true,
1283
+ price: ReduceablePrice.create({
1284
+ price: 3,
1285
+ reducedPrice: 1,
1286
+ }),
1287
+ });
1288
+
1289
+ const optionMenu = GroupOptionMenu.create({
1290
+ name: 'option menu 1',
1291
+ multipleChoice: true,
1292
+ options: [option1, option2],
1293
+ });
1294
+
1295
+ group.settings.optionMenus = [
1296
+ optionMenu,
1297
+ ];
1298
+
1299
+ await group.save();
1300
+
1301
+ const body = IDRegisterCheckout.create({
1302
+ cart: IDRegisterCart.create({
1303
+ items: [
1304
+ IDRegisterItem.create({
1305
+ id: uuidv4(),
1306
+ replaceRegistrationIds: [],
1307
+ options: [
1308
+ RegisterItemOption.create({
1309
+ option: option1,
1310
+ amount: 2,
1311
+ optionMenu,
1312
+ }),
1313
+ RegisterItemOption.create({
1314
+ option: option2,
1315
+ amount: 5,
1316
+ optionMenu,
1317
+ }),
1318
+ ],
1319
+ groupPrice,
1320
+ organizationId: organization.id,
1321
+ groupId: group.id,
1322
+ memberId: member.id,
1323
+ }),
1324
+ ],
1325
+ balanceItems: [
1326
+ ],
1327
+ deleteRegistrationIds: [],
1328
+ }),
1329
+ administrationFee: 0,
1330
+ freeContribution: 0,
1331
+ paymentMethod: PaymentMethod.PointOfSale,
1332
+ totalPrice: 50,
1333
+ customer: null,
1334
+ });
1335
+ // #endregion
1336
+
1337
+ // #region act and assert
1338
+ const result = await post(body, organization, token);
1339
+ expect(result).toBeDefined();
1340
+ // #endregion
1341
+ });
1014
1342
  });
1015
1343
 
1016
1344
  describe('Register by other organization', () => {
@@ -5,6 +5,7 @@ import crypto from 'crypto';
5
5
  import basex from 'base-x';
6
6
  import { AuditLogService } from '../services/AuditLogService';
7
7
  import { Formatter } from '@stamhoofd/utility';
8
+ import { QueueHandler } from '@stamhoofd/queues';
8
9
 
9
10
  const ALPHABET = '123456789ABCDEFGHJKMNPQRSTUVWXYZ'; // Note: we removed 0, O, I and l to make it easier for humans
10
11
  const customBase = basex(ALPHABET);
@@ -203,116 +204,119 @@ export class MemberUserSyncerStatic {
203
204
  }
204
205
 
205
206
  async linkUser(email: string, member: MemberWithRegistrations, asParent: boolean, updateNameIfEqual = true) {
206
- console.log('Linking user', email, 'to member', member.id, 'as parent', asParent, 'update name if equal', updateNameIfEqual);
207
-
208
- let user = member.users.find(u => u.email.toLocaleLowerCase() === email.toLocaleLowerCase()) ?? await User.getForAuthentication(member.organizationId, email, { allowWithoutAccount: true });
209
-
210
- if (user) {
211
- // console.log("Giving an existing user access to a member: " + user.id + ' - ' + member.id)
212
- if (!asParent) {
213
- if (user.memberId && user.memberId !== member.id) {
214
- console.error('Found conflicting user with multiple members', user.id, 'members', user.memberId, 'to', member.id);
215
-
216
- const otherMember = await Member.getWithRegistrations(user.memberId);
217
-
218
- if (otherMember) {
219
- if (otherMember.registrations.length > 0 && member.registrations.length === 0) {
220
- // Choose the other member
221
- // don't make changes
222
- console.error('Resolved to current member - no changes made');
223
- return;
207
+ // This needs to happen in the queue to prevent creating duplicate users in the database
208
+ await QueueHandler.schedule('link-user/' + email, async () => {
209
+ console.log('Linking user', email, 'to member', member.id, 'as parent', asParent, 'update name if equal', updateNameIfEqual);
210
+
211
+ let user = member.users.find(u => u.email.toLocaleLowerCase() === email.toLocaleLowerCase()) ?? await User.getForAuthentication(member.organizationId, email, { allowWithoutAccount: true });
212
+
213
+ if (user) {
214
+ // console.log("Giving an existing user access to a member: " + user.id + ' - ' + member.id)
215
+ if (!asParent) {
216
+ if (user.memberId && user.memberId !== member.id) {
217
+ console.error('Found conflicting user with multiple members', user.id, 'members', user.memberId, 'to', member.id);
218
+
219
+ const otherMember = await Member.getWithRegistrations(user.memberId);
220
+
221
+ if (otherMember) {
222
+ if (otherMember.registrations.length > 0 && member.registrations.length === 0) {
223
+ // Choose the other member
224
+ // don't make changes
225
+ console.error('Resolved to current member - no changes made');
226
+ return;
227
+ }
228
+
229
+ const responsibilities = await this.getResponsibilitiesForMembers([otherMember.id, member.id]);
230
+ const responsibilitiesOther = responsibilities.filter(r => r.memberId === otherMember.id);
231
+ const responsibilitiesCurrent = responsibilities.filter(r => r.memberId === member.id);
232
+
233
+ if (responsibilitiesOther.length >= responsibilitiesCurrent.length) {
234
+ console.error('Resolved to current member because of more responsibilities - no changes made');
235
+ return;
236
+ }
224
237
  }
238
+ }
225
239
 
226
- const responsibilities = await this.getResponsibilitiesForMembers([otherMember.id, member.id]);
227
- const responsibilitiesOther = responsibilities.filter(r => r.memberId === otherMember.id);
228
- const responsibilitiesCurrent = responsibilities.filter(r => r.memberId === member.id);
229
-
230
- if (responsibilitiesOther.length >= responsibilitiesCurrent.length) {
231
- console.error('Resolved to current member because of more responsibilities - no changes made');
232
- return;
233
- }
240
+ if (updateNameIfEqual) {
241
+ user.firstName = member.details.firstName;
242
+ user.lastName = member.details.lastName;
234
243
  }
244
+ user.memberId = member.id;
245
+ await this.updateInheritedPermissions(user);
235
246
  }
247
+ else {
248
+ let shouldSave = false;
249
+
250
+ if (!user.firstName && !user.lastName) {
251
+ const parents = member.details.parents.filter(p => p.email === email);
252
+ if (parents.length === 1) {
253
+ if (updateNameIfEqual) {
254
+ user.firstName = parents[0].firstName;
255
+ user.lastName = parents[0].lastName;
256
+ }
257
+ shouldSave = true;
258
+ }
259
+ }
236
260
 
237
- if (updateNameIfEqual) {
238
- user.firstName = member.details.firstName;
239
- user.lastName = member.details.lastName;
261
+ if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
262
+ user.firstName = null;
263
+ user.lastName = null;
264
+ shouldSave = true;
265
+ }
266
+
267
+ if (user.memberId === member.id) {
268
+ // Unlink: parents are never 'equal' to the member
269
+ user.memberId = null;
270
+ await this.updateInheritedPermissions(user);
271
+ }
272
+ if (shouldSave) {
273
+ await user.save();
274
+ }
240
275
  }
241
- user.memberId = member.id;
242
- await this.updateInheritedPermissions(user);
243
276
  }
244
277
  else {
245
- let shouldSave = false;
278
+ // Create a new placeholder user
279
+ user = new User();
280
+ user.organizationId = member.organizationId;
281
+ user.email = email;
246
282
 
247
- if (!user.firstName && !user.lastName) {
283
+ if (!asParent) {
284
+ if (updateNameIfEqual) {
285
+ user.firstName = member.details.firstName;
286
+ user.lastName = member.details.lastName;
287
+ }
288
+ user.memberId = member.id;
289
+ await this.updateInheritedPermissions(user);
290
+ }
291
+ else {
248
292
  const parents = member.details.parents.filter(p => p.email === email);
249
293
  if (parents.length === 1) {
250
294
  if (updateNameIfEqual) {
251
295
  user.firstName = parents[0].firstName;
252
296
  user.lastName = parents[0].lastName;
253
297
  }
254
- shouldSave = true;
255
298
  }
256
- }
257
-
258
- if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
259
- user.firstName = null;
260
- user.lastName = null;
261
- shouldSave = true;
262
- }
263
299
 
264
- if (user.memberId === member.id) {
265
- // Unlink: parents are never 'equal' to the member
266
- user.memberId = null;
267
- await this.updateInheritedPermissions(user);
268
- }
269
- if (shouldSave) {
270
- await user.save();
271
- }
272
- }
273
- }
274
- else {
275
- // Create a new placeholder user
276
- user = new User();
277
- user.organizationId = member.organizationId;
278
- user.email = email;
279
-
280
- if (!asParent) {
281
- if (updateNameIfEqual) {
282
- user.firstName = member.details.firstName;
283
- user.lastName = member.details.lastName;
284
- }
285
- user.memberId = member.id;
286
- await this.updateInheritedPermissions(user);
287
- }
288
- else {
289
- const parents = member.details.parents.filter(p => p.email === email);
290
- if (parents.length === 1) {
291
- if (updateNameIfEqual) {
292
- user.firstName = parents[0].firstName;
293
- user.lastName = parents[0].lastName;
300
+ if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
301
+ user.firstName = null;
302
+ user.lastName = null;
294
303
  }
295
- }
296
304
 
297
- if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
298
- user.firstName = null;
299
- user.lastName = null;
305
+ await user.save();
300
306
  }
301
307
 
302
- await user.save();
308
+ console.log('Created new (placeholder) user that has access to a member: ' + user.id);
303
309
  }
304
310
 
305
- console.log('Created new (placeholder) user that has access to a member: ' + user.id);
306
- }
307
-
308
- // Update model relation to correct response
309
- if (!member.users.find(u => u.id === user.id)) {
310
- await Member.users.reverse('members').link(user, [member]);
311
- member.users.push(user);
311
+ // Update model relation to correct response
312
+ if (!member.users.find(u => u.id === user.id)) {
313
+ await Member.users.reverse('members').link(user, [member]);
314
+ member.users.push(user);
312
315
 
313
- // Update balance of this user, as it could have changed
314
- await this.updateUserBalance(user.id, member.id);
315
- }
316
+ // Update balance of this user, as it could have changed
317
+ await this.updateUserBalance(user.id, member.id);
318
+ }
319
+ });
316
320
  }
317
321
 
318
322
  /**