@nymphjs/tilmeld 1.0.0-beta.6 → 1.0.0-beta.61

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +284 -0
  2. package/README.md +14 -1
  3. package/dist/AbleObject.d.ts +15 -0
  4. package/dist/AbleObject.js +16 -1
  5. package/dist/AbleObject.js.map +1 -1
  6. package/dist/Group.d.ts +119 -8
  7. package/dist/Group.js +230 -97
  8. package/dist/Group.js.map +1 -1
  9. package/dist/Group.test.js +3 -2
  10. package/dist/Group.test.js.map +1 -1
  11. package/dist/Tilmeld.d.ts +237 -9
  12. package/dist/Tilmeld.js +559 -195
  13. package/dist/Tilmeld.js.map +1 -1
  14. package/dist/User.d.ts +333 -14
  15. package/dist/User.js +797 -204
  16. package/dist/User.js.map +1 -1
  17. package/dist/User.test.js +3 -2
  18. package/dist/User.test.js.map +1 -1
  19. package/dist/conf/d.d.ts +250 -3
  20. package/dist/conf/defaults.js +146 -98
  21. package/dist/conf/defaults.js.map +1 -1
  22. package/dist/enforceTilmeld.d.ts +3 -0
  23. package/dist/enforceTilmeld.js +22 -0
  24. package/dist/enforceTilmeld.js.map +1 -0
  25. package/dist/errors/AccessControlError.d.ts +2 -1
  26. package/dist/errors/AccessControlError.js +3 -2
  27. package/dist/errors/AccessControlError.js.map +1 -1
  28. package/dist/errors/BadDataError.d.ts +2 -1
  29. package/dist/errors/BadDataError.js +3 -2
  30. package/dist/errors/BadDataError.js.map +1 -1
  31. package/dist/errors/BadEmailError.d.ts +2 -1
  32. package/dist/errors/BadEmailError.js +3 -2
  33. package/dist/errors/BadEmailError.js.map +1 -1
  34. package/dist/errors/BadUsernameError.d.ts +2 -1
  35. package/dist/errors/BadUsernameError.js +3 -2
  36. package/dist/errors/BadUsernameError.js.map +1 -1
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.js +1 -0
  39. package/dist/index.js.map +1 -1
  40. package/package.json +30 -26
  41. package/src/AbleObject.ts +2 -2
  42. package/src/Group.test.ts +3 -3
  43. package/src/Group.ts +194 -143
  44. package/src/Tilmeld.ts +384 -249
  45. package/src/User.test.ts +3 -3
  46. package/src/User.ts +876 -266
  47. package/src/conf/d.ts +26 -13
  48. package/src/conf/defaults.ts +151 -104
  49. package/src/enforceTilmeld.ts +20 -0
  50. package/src/errors/AccessControlError.ts +4 -2
  51. package/src/errors/BadDataError.ts +4 -2
  52. package/src/errors/BadEmailError.ts +4 -2
  53. package/src/errors/BadUsernameError.ts +4 -2
  54. package/src/index.ts +2 -0
  55. package/src/tilmeldJoiProps.ts +9 -9
  56. package/tsconfig.json +1 -1
  57. package/typedoc.json +4 -0
package/src/Group.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  EntityData,
3
- EntityInterface,
4
3
  EntityJson,
5
4
  EntityPatch,
6
5
  Options,
@@ -10,7 +9,7 @@ import {
10
9
  import md5 from 'crypto-js/md5';
11
10
  import { difference } from 'lodash';
12
11
 
13
- import type Tilmeld from './Tilmeld';
12
+ import { enforceTilmeld } from './enforceTilmeld';
14
13
  import AbleObject from './AbleObject';
15
14
  import {
16
15
  BadDataError,
@@ -85,7 +84,6 @@ export default class Group extends AbleObject<GroupData> {
85
84
  /**
86
85
  * The instance of Tilmeld to use for queries.
87
86
  */
88
- static tilmeld: Tilmeld;
89
87
  static ETYPE = 'tilmeld_group';
90
88
  static class = 'Group';
91
89
 
@@ -116,12 +114,8 @@ export default class Group extends AbleObject<GroupData> {
116
114
  */
117
115
  private $skipAcWhenSaving = false;
118
116
 
119
- static async factory(guid?: string): Promise<Group & GroupData> {
120
- return (await super.factory(guid)) as Group & GroupData;
121
- }
122
-
123
117
  static async factoryGroupname(
124
- groupname?: string
118
+ groupname?: string,
125
119
  ): Promise<Group & GroupData> {
126
120
  const entity = new this();
127
121
  if (groupname != null) {
@@ -132,7 +126,7 @@ export default class Group extends AbleObject<GroupData> {
132
126
  {
133
127
  type: '&',
134
128
  ilike: ['groupname', groupname.replace(/([\\%_])/g, (s) => `\\${s}`)],
135
- }
129
+ },
136
130
  );
137
131
  if (entity != null) {
138
132
  return entity;
@@ -141,19 +135,13 @@ export default class Group extends AbleObject<GroupData> {
141
135
  return entity;
142
136
  }
143
137
 
144
- static factorySync(guid?: string): Group & GroupData {
145
- return super.factorySync(guid) as Group & GroupData;
146
- }
147
-
148
- constructor(guid?: string) {
149
- super(guid);
138
+ constructor() {
139
+ super();
150
140
 
151
- if (this.guid == null) {
152
- // Defaults.
153
- this.$data.enabled = true;
154
- this.$data.abilities = [];
155
- this.$updateDataProtection();
156
- }
141
+ // Defaults.
142
+ this.$data.enabled = true;
143
+ this.$data.abilities = [];
144
+ this.$updateDataProtection();
157
145
  }
158
146
 
159
147
  /**
@@ -165,16 +153,17 @@ export default class Group extends AbleObject<GroupData> {
165
153
  */
166
154
  public static async getPrimaryGroups(
167
155
  options?: Options,
168
- selectors?: Selector[]
156
+ selectors?: Selector[],
169
157
  ) {
170
- if (!this.tilmeld.gatekeeper('tilmeld/admin')) {
158
+ const tilmeld = enforceTilmeld(this);
159
+ if (!tilmeld.gatekeeper('tilmeld/admin')) {
171
160
  throw new Error("You don't have permission to do that.");
172
161
  }
173
162
 
174
163
  return await this.getAssignableGroups(
175
- this.tilmeld.config.highestPrimary,
176
- { ...options, class: this.tilmeld.Group, return: 'entity' },
177
- [...(selectors ?? [])]
164
+ tilmeld.config.highestPrimary,
165
+ { ...options, class: tilmeld.Group, return: 'entity' },
166
+ [...(selectors ?? [])],
178
167
  );
179
168
  }
180
169
 
@@ -187,24 +176,26 @@ export default class Group extends AbleObject<GroupData> {
187
176
  */
188
177
  public static async getSecondaryGroups(
189
178
  options?: Options,
190
- selectors?: Selector[]
179
+ selectors?: Selector[],
191
180
  ) {
192
- if (!this.tilmeld.gatekeeper('tilmeld/admin')) {
181
+ const tilmeld = enforceTilmeld(this);
182
+ if (!tilmeld.gatekeeper('tilmeld/admin')) {
193
183
  throw new Error("You don't have permission to do that.");
194
184
  }
195
185
 
196
186
  return await this.getAssignableGroups(
197
- this.tilmeld.config.highestSecondary,
198
- { ...options, class: this.tilmeld.Group, return: 'entity' },
199
- [...(selectors ?? [])]
187
+ tilmeld.config.highestSecondary,
188
+ { ...options, class: tilmeld.Group, return: 'entity' },
189
+ [...(selectors ?? [])],
200
190
  );
201
191
  }
202
192
 
203
193
  private static async getAssignableGroups(
204
194
  highestParent: string | boolean,
205
195
  options: Options<typeof Group>,
206
- selectors: Selector[]
196
+ selectors: Selector[],
207
197
  ) {
198
+ const tilmeld = enforceTilmeld(this);
208
199
  let assignableGroups: (Group & GroupData)[] = [];
209
200
 
210
201
  if (highestParent === false) {
@@ -212,20 +203,28 @@ export default class Group extends AbleObject<GroupData> {
212
203
  }
213
204
 
214
205
  assignableGroups = await this.nymph.getEntities(
215
- { ...options, class: this.tilmeld.Group },
216
- ...selectors
206
+ { ...options, class: tilmeld.Group },
207
+ ...selectors,
217
208
  );
218
209
  if (highestParent !== true) {
219
- assignableGroups = assignableGroups.filter((group) => {
220
- let curGroup = group;
221
- while (curGroup.parent != null && curGroup.parent.cdate != null) {
222
- if (curGroup.parent.guid === highestParent) {
223
- return true;
224
- }
225
- curGroup = curGroup.parent;
226
- }
227
- return false;
228
- });
210
+ assignableGroups = (
211
+ await Promise.all(
212
+ assignableGroups.map(async (group) => {
213
+ let curGroup = group;
214
+ let parent = curGroup.parent;
215
+ await parent?.$wake();
216
+ while (parent != null && parent?.cdate != null) {
217
+ if (parent?.guid === highestParent) {
218
+ return group;
219
+ }
220
+ curGroup = parent;
221
+ parent = curGroup.parent;
222
+ await parent?.$wake();
223
+ }
224
+ return null;
225
+ }),
226
+ )
227
+ ).filter((group) => group != null) as (Group & GroupData)[];
229
228
  }
230
229
  return assignableGroups;
231
230
  }
@@ -245,17 +244,42 @@ export default class Group extends AbleObject<GroupData> {
245
244
  }
246
245
 
247
246
  public $jsonAcceptData(input: EntityJson, allowConflict = false) {
248
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
249
- this.$referenceWake();
247
+ const tilmeld = enforceTilmeld(this);
248
+ this.$check();
250
249
 
251
250
  if (
252
- input.data.abilities?.indexOf('system/admin') !== -1 &&
253
- this.$data.abilities?.indexOf('system/admin') === -1 &&
254
- tilmeld.gatekeeper('tilmeld/admin') &&
255
- !tilmeld.gatekeeper('system/admin')
251
+ 'abilities' in input.data &&
252
+ input.data.abilities?.includes('system/admin') !==
253
+ this.$data.abilities?.includes('system/admin') &&
254
+ (!tilmeld.currentUser ||
255
+ !tilmeld.currentUser.abilities?.includes('system/admin'))
256
256
  ) {
257
257
  throw new BadDataError(
258
- "You don't have the authority to make this group a system admin."
258
+ "You don't have the authority to grant or revoke system/admin.",
259
+ );
260
+ }
261
+
262
+ if (
263
+ 'abilities' in input.data &&
264
+ input.data.abilities?.includes('tilmeld/admin') !==
265
+ this.$data.abilities?.includes('tilmeld/admin') &&
266
+ (!tilmeld.currentUser ||
267
+ !tilmeld.currentUser.abilities?.includes('system/admin'))
268
+ ) {
269
+ throw new BadDataError(
270
+ "You don't have the authority to grant or revoke tilmeld/admin.",
271
+ );
272
+ }
273
+
274
+ if (
275
+ 'abilities' in input.data &&
276
+ input.data.abilities?.includes('tilmeld/switch') !==
277
+ this.$data.abilities?.includes('tilmeld/switch') &&
278
+ (!tilmeld.currentUser ||
279
+ !tilmeld.currentUser.abilities?.includes('system/admin'))
280
+ ) {
281
+ throw new BadDataError(
282
+ "You don't have the authority to grant or revoke tilmeld/switch.",
259
283
  );
260
284
  }
261
285
 
@@ -263,17 +287,42 @@ export default class Group extends AbleObject<GroupData> {
263
287
  }
264
288
 
265
289
  public $jsonAcceptPatch(patch: EntityPatch, allowConflict = false) {
266
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
267
- this.$referenceWake();
290
+ const tilmeld = enforceTilmeld(this);
291
+ this.$check();
292
+
293
+ if (
294
+ 'abilities' in patch.set &&
295
+ patch.set.abilities?.includes('system/admin') !==
296
+ this.$data.abilities?.includes('system/admin') &&
297
+ (!tilmeld.currentUser ||
298
+ !tilmeld.currentUser.abilities?.includes('system/admin'))
299
+ ) {
300
+ throw new BadDataError(
301
+ "You don't have the authority to grant or revoke system/admin.",
302
+ );
303
+ }
304
+
305
+ if (
306
+ 'abilities' in patch.set &&
307
+ patch.set.abilities?.includes('tilmeld/admin') !==
308
+ this.$data.abilities?.includes('tilmeld/admin') &&
309
+ (!tilmeld.currentUser ||
310
+ !tilmeld.currentUser.abilities?.includes('system/admin'))
311
+ ) {
312
+ throw new BadDataError(
313
+ "You don't have the authority to grant or revoke tilmeld/admin.",
314
+ );
315
+ }
268
316
 
269
317
  if (
270
- patch.set.abilities?.indexOf('system/admin') !== -1 &&
271
- this.$data.abilities?.indexOf('system/admin') === -1 &&
272
- tilmeld.gatekeeper('tilmeld/admin') &&
273
- !tilmeld.gatekeeper('system/admin')
318
+ 'abilities' in patch.set &&
319
+ patch.set.abilities?.includes('tilmeld/switch') !==
320
+ this.$data.abilities?.includes('tilmeld/switch') &&
321
+ (!tilmeld.currentUser ||
322
+ !tilmeld.currentUser.abilities?.includes('system/admin'))
274
323
  ) {
275
324
  throw new BadDataError(
276
- "You don't have the authority to make this group a system admin."
325
+ "You don't have the authority to grant or revoke tilmeld/switch.",
277
326
  );
278
327
  }
279
328
 
@@ -291,7 +340,7 @@ export default class Group extends AbleObject<GroupData> {
291
340
  * @param givenUser User to update protection for. If undefined, will use the currently logged in user.
292
341
  */
293
342
  public $updateDataProtection(givenUser?: User & UserData) {
294
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
343
+ const tilmeld = enforceTilmeld(this);
295
344
  let user = givenUser ?? tilmeld.User.current();
296
345
 
297
346
  this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
@@ -303,8 +352,8 @@ export default class Group extends AbleObject<GroupData> {
303
352
 
304
353
  if (
305
354
  user != null &&
306
- ((user.abilities?.indexOf('tilmeld/admin') ?? -1) !== -1 ||
307
- (user.abilities?.indexOf('system/admin') ?? -1) !== -1)
355
+ (user.abilities?.includes('tilmeld/admin') ||
356
+ user.abilities?.includes('system/admin'))
308
357
  ) {
309
358
  // Users who can edit groups can see their data.
310
359
  this.$privateData = [];
@@ -312,7 +361,7 @@ export default class Group extends AbleObject<GroupData> {
312
361
  } else if (
313
362
  this.$data.user != null &&
314
363
  user != null &&
315
- this.$data.user.$is(user)
364
+ this.$data.user.guid === user.guid
316
365
  ) {
317
366
  // Users can see their group's data.
318
367
  this.$privateData = [];
@@ -325,11 +374,13 @@ export default class Group extends AbleObject<GroupData> {
325
374
  * @param group The group, or the group's GUID.
326
375
  * @returns True or false.
327
376
  */
328
- public $isDescendant(givenGroup: (Group & GroupData) | string): boolean {
329
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
377
+ public async $isDescendant(
378
+ givenGroup: (Group & GroupData) | string,
379
+ ): Promise<boolean> {
380
+ const tilmeld = enforceTilmeld(this);
330
381
  let group: Group & GroupData;
331
382
  if (typeof givenGroup === 'string') {
332
- group = tilmeld.Group.factorySync(givenGroup);
383
+ group = await tilmeld.Group.factory(givenGroup);
333
384
  } else {
334
385
  group = givenGroup;
335
386
  }
@@ -337,13 +388,15 @@ export default class Group extends AbleObject<GroupData> {
337
388
  return false;
338
389
  }
339
390
  // Check to see if the group is a descendant of the given group.
340
- if (this.$data.parent == null) {
391
+ const parent = this.$data.parent;
392
+ await parent?.$wake();
393
+ if (parent == null) {
341
394
  return false;
342
395
  }
343
- if (this.$data.parent.$is(group)) {
396
+ if (parent.$is(group)) {
344
397
  return true;
345
398
  }
346
- if (this.$data.parent.$isDescendant(group)) {
399
+ if (await parent.$isDescendant(group)) {
347
400
  return true;
348
401
  }
349
402
  return false;
@@ -355,14 +408,14 @@ export default class Group extends AbleObject<GroupData> {
355
408
  * @returns An array of groups.
356
409
  */
357
410
  public async $getChildren() {
358
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
411
+ const tilmeld = enforceTilmeld(this);
359
412
  return await this.$nymph.getEntities(
360
413
  { class: tilmeld.Group },
361
414
  {
362
415
  type: '&',
363
416
  equal: ['enabled', true],
364
417
  ref: ['parent', this],
365
- }
418
+ },
366
419
  );
367
420
  }
368
421
 
@@ -373,9 +426,9 @@ export default class Group extends AbleObject<GroupData> {
373
426
  * @returns An array of groups.
374
427
  */
375
428
  public async $getDescendants(
376
- andSelf = false
429
+ andSelf = false,
377
430
  ): Promise<(Group & GroupData)[]> {
378
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
431
+ const tilmeld = enforceTilmeld(this);
379
432
  let groups: (Group & GroupData)[] = [];
380
433
  const entities = await this.$nymph.getEntities(
381
434
  { class: tilmeld.Group },
@@ -383,7 +436,7 @@ export default class Group extends AbleObject<GroupData> {
383
436
  type: '&',
384
437
  equal: ['enabled', true],
385
438
  ref: ['parent', this],
386
- }
439
+ },
387
440
  );
388
441
  for (let entity of entities) {
389
442
  groups = (await entity.$getDescendants(true)).concat(groups);
@@ -394,37 +447,6 @@ export default class Group extends AbleObject<GroupData> {
394
447
  return groups;
395
448
  }
396
449
 
397
- /**
398
- * Gets an array of the group's descendant groups.
399
- *
400
- * @param andSelf Include this group in the returned array.
401
- * @returns An array of groups.
402
- */
403
- public $getDescendantsSync(andSelf = false): (Group & GroupData)[] {
404
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
405
- let groups: (Group & GroupData)[] = [];
406
- let entity: EntityInterface | null;
407
- let offset = 0;
408
- do {
409
- entity = this.$nymph.driver.getEntitySync(
410
- { class: tilmeld.Group, offset },
411
- {
412
- type: '&',
413
- equal: ['enabled', true],
414
- ref: ['parent', this],
415
- }
416
- );
417
- if (entity != null) {
418
- groups = entity.$getDescendantsSync(true).concat(groups);
419
- }
420
- offset++;
421
- } while (entity != null);
422
- if (andSelf) {
423
- groups.push(this);
424
- }
425
- return groups;
426
- }
427
-
428
450
  /**
429
451
  * Get the number of parents the group has.
430
452
  *
@@ -436,13 +458,17 @@ export default class Group extends AbleObject<GroupData> {
436
458
  *
437
459
  * @returns The level of the group.
438
460
  */
439
- public $getLevel() {
440
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
441
- let group = tilmeld.Group.factorySync(this.guid ?? undefined);
461
+ public async $getLevel() {
462
+ const tilmeld = enforceTilmeld(this);
463
+ let group = await tilmeld.Group.factory(this.guid ?? undefined);
464
+ let parent = group.parent;
465
+ await parent?.$wake();
442
466
  let level = 0;
443
- while (group.parent != null && group.parent.cdate != null && level < 1024) {
467
+ while (parent != null && parent.cdate != null && level < 1024) {
444
468
  level++;
445
- group = group.parent;
469
+ group = parent;
470
+ parent = group.parent;
471
+ await parent?.$wake();
446
472
  }
447
473
  return level;
448
474
  }
@@ -458,9 +484,9 @@ export default class Group extends AbleObject<GroupData> {
458
484
  public async $getUsers(
459
485
  descendants = false,
460
486
  limit?: number,
461
- offset?: number
487
+ offset?: number,
462
488
  ): Promise<(User & UserData)[]> {
463
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
489
+ const tilmeld = enforceTilmeld(this);
464
490
  let groups: (Group & GroupData)[] = [];
465
491
  if (descendants) {
466
492
  groups = await this.$getDescendants();
@@ -489,7 +515,7 @@ export default class Group extends AbleObject<GroupData> {
489
515
  group,
490
516
  ]),
491
517
  ],
492
- }
518
+ },
493
519
  );
494
520
  }
495
521
 
@@ -502,15 +528,21 @@ export default class Group extends AbleObject<GroupData> {
502
528
  result: boolean;
503
529
  message: string;
504
530
  }> {
505
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
531
+ const tilmeld = enforceTilmeld(this);
506
532
  if (!tilmeld.config.emailUsernames) {
507
533
  if (this.$data.groupname == null || !this.$data.groupname.length) {
508
534
  return { result: false, message: 'Please specify a groupname.' };
509
535
  }
510
- if (
511
- tilmeld.config.maxUsernameLength > 0 &&
512
- this.$data.groupname.length > tilmeld.config.maxUsernameLength
513
- ) {
536
+ if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
537
+ return {
538
+ result: false,
539
+ message:
540
+ 'Groupnames must be at least ' +
541
+ tilmeld.config.minUsernameLength +
542
+ ' characters.',
543
+ };
544
+ }
545
+ if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
514
546
  return {
515
547
  result: false,
516
548
  message:
@@ -522,7 +554,7 @@ export default class Group extends AbleObject<GroupData> {
522
554
  if (
523
555
  difference(
524
556
  this.$data.groupname.split(''),
525
- tilmeld.config.validChars.split('')
557
+ tilmeld.config.validChars.split(''),
526
558
  ).length
527
559
  ) {
528
560
  return {
@@ -549,7 +581,7 @@ export default class Group extends AbleObject<GroupData> {
549
581
  }
550
582
  const test = await this.$nymph.getEntity(
551
583
  { class: tilmeld.Group, skipAc: true },
552
- selector
584
+ selector,
553
585
  );
554
586
  if (test != null) {
555
587
  return { result: false, message: 'That groupname is taken.' };
@@ -565,10 +597,16 @@ export default class Group extends AbleObject<GroupData> {
565
597
  if (this.$data.groupname == null || !this.$data.groupname.length) {
566
598
  return { result: false, message: 'Please specify an email.' };
567
599
  }
568
- if (
569
- tilmeld.config.maxUsernameLength > 0 &&
570
- this.$data.groupname.length > tilmeld.config.maxUsernameLength
571
- ) {
600
+ if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
601
+ return {
602
+ result: false,
603
+ message:
604
+ 'Emails must be at least ' +
605
+ tilmeld.config.minUsernameLength +
606
+ ' characters.',
607
+ };
608
+ }
609
+ if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
572
610
  return {
573
611
  result: false,
574
612
  message:
@@ -588,7 +626,7 @@ export default class Group extends AbleObject<GroupData> {
588
626
  * @returns An object with a boolean 'result' entry and a 'message' entry.
589
627
  */
590
628
  public async $checkEmail(): Promise<{ result: boolean; message: string }> {
591
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
629
+ const tilmeld = enforceTilmeld(this);
592
630
  if (this.$data.email === '') {
593
631
  return { result: true, message: '' };
594
632
  }
@@ -610,7 +648,7 @@ export default class Group extends AbleObject<GroupData> {
610
648
  }
611
649
  const test = await this.$nymph.getEntity(
612
650
  { class: tilmeld.Group, skipAc: true },
613
- selector
651
+ selector,
614
652
  );
615
653
  if (test != null) {
616
654
  return {
@@ -627,7 +665,7 @@ export default class Group extends AbleObject<GroupData> {
627
665
  }
628
666
 
629
667
  public async $save() {
630
- const tilmeld = this.$nymph.tilmeld as Tilmeld;
668
+ const tilmeld = enforceTilmeld(this);
631
669
  if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
632
670
  return false;
633
671
  }
@@ -643,16 +681,22 @@ export default class Group extends AbleObject<GroupData> {
643
681
  this.$data.enabled = !!this.$data.enabled;
644
682
 
645
683
  // Clear empty values.
646
- if (this.$data.name === '') {
684
+ if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
647
685
  delete this.$data.name;
648
686
  }
649
- if (this.$data.email === '') {
687
+ if (
688
+ this.$data.email === '' ||
689
+ !tilmeld.config.userFields.includes('email')
690
+ ) {
650
691
  delete this.$data.email;
651
692
  }
652
693
  if (this.$data.avatar === '') {
653
694
  delete this.$data.avatar;
654
695
  }
655
- if (this.$data.phone === '') {
696
+ if (
697
+ this.$data.phone === '' ||
698
+ !tilmeld.config.userFields.includes('phone')
699
+ ) {
656
700
  delete this.$data.phone;
657
701
  }
658
702
  if (this.$data.parent == null) {
@@ -671,10 +715,12 @@ export default class Group extends AbleObject<GroupData> {
671
715
  delete this.$data.unverifiedSecondary;
672
716
  }
673
717
 
674
- // Groups should never have 'system/admin' or 'tilmeld/admin' abilities.
718
+ // Groups should never have 'system/admin', 'tilmeld/admin', or
719
+ // 'tilmeld/switch' abilities.
675
720
  this.$data.abilities = difference(this.$data.abilities ?? [], [
676
721
  'system/admin',
677
722
  'tilmeld/admin',
723
+ 'tilmeld/switch',
678
724
  null,
679
725
  undefined,
680
726
  ]) as string[];
@@ -684,7 +730,10 @@ export default class Group extends AbleObject<GroupData> {
684
730
  if (!unCheck.result) {
685
731
  throw new BadUsernameError(unCheck.message);
686
732
  }
687
- if (!tilmeld.config.emailUsernames) {
733
+ if (
734
+ !tilmeld.config.emailUsernames &&
735
+ tilmeld.config.userFields.includes('email')
736
+ ) {
688
737
  const emCheck = await this.$checkEmail();
689
738
  if (!emCheck.result) {
690
739
  throw new BadEmailError(emCheck.message);
@@ -692,19 +741,21 @@ export default class Group extends AbleObject<GroupData> {
692
741
  }
693
742
 
694
743
  // Validate group parent. Make sure it's not a descendant of this group.
744
+ const parent = this.$data.parent;
745
+ await parent?.$wake();
695
746
  if (
696
- this.$data.parent &&
697
- (this.$data.parent.cdate == null ||
698
- this.$is(this.$data.parent) ||
699
- this.$data.parent.$isDescendant(this))
747
+ parent &&
748
+ (parent.cdate == null ||
749
+ this.$is(parent) ||
750
+ (await parent.$isDescendant(this)))
700
751
  ) {
701
752
  throw new BadDataError(
702
- "Group parent can't be itself or descendant of itself."
753
+ "Group parent can't be itself or descendant of itself.",
703
754
  );
704
755
  }
705
756
 
706
757
  try {
707
- tilmeld.config.validatorGroup(this);
758
+ tilmeld.config.validatorGroup(tilmeld, this);
708
759
  } catch (e: any) {
709
760
  throw new BadDataError(e?.message);
710
761
  }
@@ -713,14 +764,14 @@ export default class Group extends AbleObject<GroupData> {
713
764
  if (this.$data.defaultPrimary) {
714
765
  const currentPrimary = await this.$nymph.getEntity(
715
766
  { class: tilmeld.Group },
716
- { type: '&', truthy: 'defaultPrimary' }
767
+ { type: '&', truthy: 'defaultPrimary' },
717
768
  );
718
769
  if (currentPrimary != null && !this.$is(currentPrimary)) {
719
770
  currentPrimary.defaultPrimary = false;
720
771
  if (!(await currentPrimary.$save())) {
721
772
  throw new CouldNotChangeDefaultPrimaryGroupError(
722
773
  'Could not change new user primary group from ' +
723
- `${currentPrimary.groupname}.`
774
+ `${currentPrimary.groupname}.`,
724
775
  );
725
776
  }
726
777
  }
@@ -746,7 +797,7 @@ export default class Group extends AbleObject<GroupData> {
746
797
  }
747
798
 
748
799
  public async $delete() {
749
- let tilmeld = this.$nymph.tilmeld as Tilmeld;
800
+ let tilmeld = enforceTilmeld(this);
750
801
  if (!tilmeld.gatekeeper('tilmeld/admin')) {
751
802
  throw new BadDataError("You don't have the authority to delete groups.");
752
803
  }
@@ -755,7 +806,7 @@ export default class Group extends AbleObject<GroupData> {
755
806
  const nymph = this.$nymph;
756
807
  const tnymph = await nymph.startTransaction(transaction);
757
808
  this.$nymph = tnymph;
758
- tilmeld = this.$nymph.tilmeld as Tilmeld;
809
+ tilmeld = enforceTilmeld(this);
759
810
 
760
811
  // Delete descendants.
761
812
  const descendants = await this.$getDescendants();
@@ -778,7 +829,7 @@ export default class Group extends AbleObject<GroupData> {
778
829
  {
779
830
  type: '&',
780
831
  ref: ['group', this],
781
- }
832
+ },
782
833
  );
783
834
  for (let user of primaryUsers) {
784
835
  delete user.group;
@@ -798,7 +849,7 @@ export default class Group extends AbleObject<GroupData> {
798
849
  {
799
850
  type: '&',
800
851
  ref: ['groups', this],
801
- }
852
+ },
802
853
  );
803
854
  for (let user of secondaryUsers) {
804
855
  user.$delGroup(this);