@nymphjs/tilmeld 1.0.0-beta.7 → 1.0.0-beta.71

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 +330 -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 +130 -10
  7. package/dist/Group.js +311 -129
  8. package/dist/Group.js.map +1 -1
  9. package/dist/Group.test.js +77 -2
  10. package/dist/Group.test.js.map +1 -1
  11. package/dist/Tilmeld.d.ts +237 -9
  12. package/dist/Tilmeld.js +569 -201
  13. package/dist/Tilmeld.js.map +1 -1
  14. package/dist/User.d.ts +344 -16
  15. package/dist/User.js +979 -313
  16. package/dist/User.js.map +1 -1
  17. package/dist/User.test.js +63 -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 +102 -4
  43. package/src/Group.ts +274 -158
  44. package/src/Tilmeld.ts +383 -249
  45. package/src/User.test.ts +80 -4
  46. package/src/User.ts +1061 -382
  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 +3 -3
  57. package/typedoc.json +4 -0
package/dist/Group.js CHANGED
@@ -5,12 +5,52 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const md5_1 = __importDefault(require("crypto-js/md5"));
7
7
  const lodash_1 = require("lodash");
8
+ const enforceTilmeld_1 = require("./enforceTilmeld");
8
9
  const AbleObject_1 = __importDefault(require("./AbleObject"));
9
10
  const errors_1 = require("./errors");
11
+ /**
12
+ * A user group data model.
13
+ *
14
+ * Written by Hunter Perrin for SciActive.
15
+ *
16
+ * @author Hunter Perrin <hperrin@gmail.com>
17
+ * @copyright SciActive Inc
18
+ * @see http://nymph.io/
19
+ */
10
20
  class Group extends AbleObject_1.default {
11
- static async factory(guid) {
12
- return (await super.factory(guid));
13
- }
21
+ /**
22
+ * The instance of Tilmeld to use for queries.
23
+ */
24
+ static ETYPE = 'tilmeld_group';
25
+ static class = 'Group';
26
+ static DEFAULT_PRIVATE_DATA = ['email', 'phone', 'abilities', 'user'];
27
+ static DEFAULT_ALLOWLIST_DATA = [];
28
+ $tags = [];
29
+ $clientEnabledMethods = [
30
+ '$checkGroupname',
31
+ '$checkEmail',
32
+ '$getAvatar',
33
+ '$getChildren',
34
+ '$getDescendants',
35
+ '$getLevel',
36
+ '$isDescendant',
37
+ ];
38
+ static clientEnabledStaticMethods = [
39
+ 'getPrimaryGroups',
40
+ 'getSecondaryGroups',
41
+ ];
42
+ $privateData = [...Group.DEFAULT_PRIVATE_DATA];
43
+ static searchRestrictedData = [...Group.DEFAULT_PRIVATE_DATA];
44
+ $allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
45
+ $allowlistTags = [];
46
+ /**
47
+ * This should only be used by the backend.
48
+ */
49
+ $skipAcWhenSaving = false;
50
+ /**
51
+ * This should only be used by the backend.
52
+ */
53
+ $skipAcWhenDeleting = false;
14
54
  static async factoryGroupname(groupname) {
15
55
  const entity = new this();
16
56
  if (groupname != null) {
@@ -26,63 +66,89 @@ class Group extends AbleObject_1.default {
26
66
  }
27
67
  return entity;
28
68
  }
29
- static factorySync(guid) {
30
- return super.factorySync(guid);
31
- }
32
- constructor(guid) {
33
- super(guid);
34
- this.$tags = [];
35
- this.$clientEnabledMethods = [
36
- '$checkGroupname',
37
- '$checkEmail',
38
- '$getAvatar',
39
- '$getChildren',
40
- '$getDescendants',
41
- '$getLevel',
42
- '$isDescendant',
43
- ];
44
- this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
45
- this.$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
46
- this.$allowlistTags = [];
47
- this.$skipAcWhenSaving = false;
48
- if (this.guid == null) {
49
- this.$data.enabled = true;
50
- this.$data.abilities = [];
51
- this.$updateDataProtection();
52
- }
53
- }
69
+ /**
70
+ * Get all the groups that can be assigned as primary groups.
71
+ *
72
+ * @param options The options for an optional search query.
73
+ * @param selectors The selectors for an optional search query.
74
+ * @returns An array of the assignable primary groups.
75
+ */
54
76
  static async getPrimaryGroups(options, selectors) {
55
- if (!this.tilmeld.gatekeeper('tilmeld/admin')) {
77
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
78
+ if (!tilmeld.gatekeeper('tilmeld/admin')) {
56
79
  throw new Error("You don't have permission to do that.");
57
80
  }
58
- return await this.getAssignableGroups(this.tilmeld.config.highestPrimary, { ...options, class: this.tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
81
+ return await this.getAssignableGroups(tilmeld.config.highestPrimary, { ...options, class: tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
59
82
  }
83
+ /**
84
+ * Get all the groups that can be assigned as secondary groups.
85
+ *
86
+ * @param options The options for an optional search query.
87
+ * @param selectors The selectors for an optional search query.
88
+ * @returns An array of the assignable secondary groups.
89
+ */
60
90
  static async getSecondaryGroups(options, selectors) {
61
- if (!this.tilmeld.gatekeeper('tilmeld/admin')) {
91
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
92
+ if (!tilmeld.gatekeeper('tilmeld/admin')) {
62
93
  throw new Error("You don't have permission to do that.");
63
94
  }
64
- return await this.getAssignableGroups(this.tilmeld.config.highestSecondary, { ...options, class: this.tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
95
+ return await this.getAssignableGroups(tilmeld.config.highestSecondary, { ...options, class: tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
65
96
  }
66
97
  static async getAssignableGroups(highestParent, options, selectors) {
98
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
67
99
  let assignableGroups = [];
68
100
  if (highestParent === false) {
69
101
  return assignableGroups;
70
102
  }
71
- assignableGroups = await this.nymph.getEntities({ ...options, class: this.tilmeld.Group }, ...selectors);
103
+ assignableGroups = await this.nymph.getEntities({ ...options, class: tilmeld.Group }, ...selectors);
72
104
  if (highestParent !== true) {
73
- assignableGroups = assignableGroups.filter((group) => {
105
+ assignableGroups = (await Promise.all(assignableGroups.map(async (group) => {
74
106
  let curGroup = group;
75
- while (curGroup.parent != null && curGroup.parent.cdate != null) {
76
- if (curGroup.parent.guid === highestParent) {
77
- return true;
107
+ let parent = curGroup.parent;
108
+ await parent?.$wake();
109
+ while (parent != null && parent?.cdate != null) {
110
+ if (parent?.guid === highestParent) {
111
+ return group;
78
112
  }
79
- curGroup = curGroup.parent;
113
+ curGroup = parent;
114
+ parent = curGroup.parent;
115
+ await parent?.$wake();
80
116
  }
81
- return false;
82
- });
117
+ return null;
118
+ }))).filter((group) => group != null);
83
119
  }
84
120
  return assignableGroups;
85
121
  }
122
+ constructor() {
123
+ super();
124
+ // Defaults.
125
+ this.$data.enabled = true;
126
+ this.$data.abilities = [];
127
+ this.$updateDataProtection();
128
+ }
129
+ $setNymph(nymph) {
130
+ this.$nymph = nymph;
131
+ if (!this.$asleep()) {
132
+ if (this.$data.user && this.$data.user.$nymph !== nymph) {
133
+ this.$data.user.$setNymph(nymph);
134
+ }
135
+ if (this.$data.parent && this.$data.parent.$nymph !== nymph) {
136
+ this.$data.parent.$setNymph(nymph);
137
+ }
138
+ }
139
+ }
140
+ async $getUniques() {
141
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
142
+ const uniques = [`u:${this.$data.groupname}`];
143
+ if (!tilmeld.config.emailUsernames &&
144
+ tilmeld.config.userFields.includes('email')) {
145
+ uniques.push(`e:${this.$data.email}`);
146
+ }
147
+ if (this.$data.defaultPrimary) {
148
+ uniques.push(`o:default-primary`);
149
+ }
150
+ return uniques;
151
+ }
86
152
  $getAvatar() {
87
153
  if (this.$data.avatar != null && this.$data.avatar !== '') {
88
154
  return this.$data.avatar;
@@ -95,24 +161,54 @@ class Group extends AbleObject_1.default {
95
161
  '?d=identicon&s=40');
96
162
  }
97
163
  $jsonAcceptData(input, allowConflict = false) {
98
- const tilmeld = this.$nymph.tilmeld;
99
- this.$referenceWake();
100
- if (input.data.abilities?.indexOf('system/admin') !== -1 &&
101
- this.$data.abilities?.indexOf('system/admin') === -1 &&
102
- tilmeld.gatekeeper('tilmeld/admin') &&
103
- !tilmeld.gatekeeper('system/admin')) {
104
- throw new errors_1.BadDataError("You don't have the authority to make this group a system admin.");
164
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
165
+ this.$check();
166
+ if ('abilities' in input.data &&
167
+ input.data.abilities?.includes('system/admin') !==
168
+ this.$data.abilities?.includes('system/admin') &&
169
+ (!tilmeld.currentUser ||
170
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
171
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke system/admin.");
172
+ }
173
+ if ('abilities' in input.data &&
174
+ input.data.abilities?.includes('tilmeld/admin') !==
175
+ this.$data.abilities?.includes('tilmeld/admin') &&
176
+ (!tilmeld.currentUser ||
177
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
178
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
179
+ }
180
+ if ('abilities' in input.data &&
181
+ input.data.abilities?.includes('tilmeld/switch') !==
182
+ this.$data.abilities?.includes('tilmeld/switch') &&
183
+ (!tilmeld.currentUser ||
184
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
185
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
105
186
  }
106
187
  super.$jsonAcceptData(input, allowConflict);
107
188
  }
108
189
  $jsonAcceptPatch(patch, allowConflict = false) {
109
- const tilmeld = this.$nymph.tilmeld;
110
- this.$referenceWake();
111
- if (patch.set.abilities?.indexOf('system/admin') !== -1 &&
112
- this.$data.abilities?.indexOf('system/admin') === -1 &&
113
- tilmeld.gatekeeper('tilmeld/admin') &&
114
- !tilmeld.gatekeeper('system/admin')) {
115
- throw new errors_1.BadDataError("You don't have the authority to make this group a system admin.");
190
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
191
+ this.$check();
192
+ if ('abilities' in patch.set &&
193
+ patch.set.abilities?.includes('system/admin') !==
194
+ this.$data.abilities?.includes('system/admin') &&
195
+ (!tilmeld.currentUser ||
196
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
197
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke system/admin.");
198
+ }
199
+ if ('abilities' in patch.set &&
200
+ patch.set.abilities?.includes('tilmeld/admin') !==
201
+ this.$data.abilities?.includes('tilmeld/admin') &&
202
+ (!tilmeld.currentUser ||
203
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
204
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
205
+ }
206
+ if ('abilities' in patch.set &&
207
+ patch.set.abilities?.includes('tilmeld/switch') !==
208
+ this.$data.abilities?.includes('tilmeld/switch') &&
209
+ (!tilmeld.currentUser ||
210
+ !tilmeld.currentUser.abilities?.includes('system/admin'))) {
211
+ throw new errors_1.BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
116
212
  }
117
213
  super.$jsonAcceptPatch(patch, allowConflict);
118
214
  }
@@ -120,8 +216,13 @@ class Group extends AbleObject_1.default {
120
216
  super.$putData(data, sdata);
121
217
  this.$updateDataProtection();
122
218
  }
219
+ /**
220
+ * Update the data protection arrays for a user.
221
+ *
222
+ * @param givenUser User to update protection for. If undefined, will use the currently logged in user.
223
+ */
123
224
  $updateDataProtection(givenUser) {
124
- const tilmeld = this.$nymph.tilmeld;
225
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
125
226
  let user = givenUser ?? tilmeld.User.current();
126
227
  this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
127
228
  this.$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
@@ -129,22 +230,30 @@ class Group extends AbleObject_1.default {
129
230
  this.$privateData.push('groupname');
130
231
  }
131
232
  if (user != null &&
132
- ((user.abilities?.indexOf('tilmeld/admin') ?? -1) !== -1 ||
133
- (user.abilities?.indexOf('system/admin') ?? -1) !== -1)) {
233
+ (user.abilities?.includes('tilmeld/admin') ||
234
+ user.abilities?.includes('system/admin'))) {
235
+ // Users who can edit groups can see their data.
134
236
  this.$privateData = [];
135
237
  this.$allowlistData = undefined;
136
238
  }
137
239
  else if (this.$data.user != null &&
138
240
  user != null &&
139
- this.$data.user.$is(user)) {
241
+ this.$data.user.guid === user.guid) {
242
+ // Users can see their group's data.
140
243
  this.$privateData = [];
141
244
  }
142
245
  }
143
- $isDescendant(givenGroup) {
144
- const tilmeld = this.$nymph.tilmeld;
246
+ /**
247
+ * Check whether the group is a descendant of a group.
248
+ *
249
+ * @param group The group, or the group's GUID.
250
+ * @returns True or false.
251
+ */
252
+ async $isDescendant(givenGroup) {
253
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
145
254
  let group;
146
255
  if (typeof givenGroup === 'string') {
147
- group = tilmeld.Group.factorySync(givenGroup);
256
+ group = await tilmeld.Group.factory(givenGroup);
148
257
  }
149
258
  else {
150
259
  group = givenGroup;
@@ -152,27 +261,41 @@ class Group extends AbleObject_1.default {
152
261
  if (group.guid == null) {
153
262
  return false;
154
263
  }
155
- if (this.$data.parent == null) {
264
+ // Check to see if the group is a descendant of the given group.
265
+ const parent = this.$data.parent;
266
+ await parent?.$wake();
267
+ if (parent == null) {
156
268
  return false;
157
269
  }
158
- if (this.$data.parent.$is(group)) {
270
+ if (parent.$is(group)) {
159
271
  return true;
160
272
  }
161
- if (this.$data.parent.$isDescendant(group)) {
273
+ if (await parent.$isDescendant(group)) {
162
274
  return true;
163
275
  }
164
276
  return false;
165
277
  }
278
+ /**
279
+ * Gets an array of the group's child groups.
280
+ *
281
+ * @returns An array of groups.
282
+ */
166
283
  async $getChildren() {
167
- const tilmeld = this.$nymph.tilmeld;
284
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
168
285
  return await this.$nymph.getEntities({ class: tilmeld.Group }, {
169
286
  type: '&',
170
287
  equal: ['enabled', true],
171
288
  ref: ['parent', this],
172
289
  });
173
290
  }
291
+ /**
292
+ * Gets an array of the group's descendant groups.
293
+ *
294
+ * @param andSelf Include this group in the returned array.
295
+ * @returns An array of groups.
296
+ */
174
297
  async $getDescendants(andSelf = false) {
175
- const tilmeld = this.$nymph.tilmeld;
298
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
176
299
  let groups = [];
177
300
  const entities = await this.$nymph.getEntities({ class: tilmeld.Group }, {
178
301
  type: '&',
@@ -187,39 +310,41 @@ class Group extends AbleObject_1.default {
187
310
  }
188
311
  return groups;
189
312
  }
190
- $getDescendantsSync(andSelf = false) {
191
- const tilmeld = this.$nymph.tilmeld;
192
- let groups = [];
193
- let entity;
194
- let offset = 0;
195
- do {
196
- entity = this.$nymph.driver.getEntitySync({ class: tilmeld.Group, offset }, {
197
- type: '&',
198
- equal: ['enabled', true],
199
- ref: ['parent', this],
200
- });
201
- if (entity != null) {
202
- groups = entity.$getDescendantsSync(true).concat(groups);
203
- }
204
- offset++;
205
- } while (entity != null);
206
- if (andSelf) {
207
- groups.push(this);
208
- }
209
- return groups;
210
- }
211
- $getLevel() {
212
- const tilmeld = this.$nymph.tilmeld;
213
- let group = tilmeld.Group.factorySync(this.guid ?? undefined);
313
+ /**
314
+ * Get the number of parents the group has.
315
+ *
316
+ * If the group is a top level group, this will return 0. If it is a child of
317
+ * a top level group, this will return 1. If it is a grandchild of a top level
318
+ * group, this will return 2, and so on.
319
+ *
320
+ * Levels will max out at 1024 to avoid recursive loops.
321
+ *
322
+ * @returns The level of the group.
323
+ */
324
+ async $getLevel() {
325
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
326
+ let group = await tilmeld.Group.factory(this.guid ?? undefined);
327
+ let parent = group.parent;
328
+ await parent?.$wake();
214
329
  let level = 0;
215
- while (group.parent != null && group.parent.cdate != null && level < 1024) {
330
+ while (parent != null && parent.cdate != null && level < 1024) {
216
331
  level++;
217
- group = group.parent;
332
+ group = parent;
333
+ parent = group.parent;
334
+ await parent?.$wake();
218
335
  }
219
336
  return level;
220
337
  }
338
+ /**
339
+ * Gets an array of users in the group.
340
+ *
341
+ * @param descendants Include users in all descendant groups too.
342
+ * @param limit The limit for the query.
343
+ * @param offset The offset for the query.
344
+ * @returns An array of users.
345
+ */
221
346
  async $getUsers(descendants = false, limit, offset) {
222
- const tilmeld = this.$nymph.tilmeld;
347
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
223
348
  let groups = [];
224
349
  if (descendants) {
225
350
  groups = await this.$getDescendants();
@@ -247,14 +372,26 @@ class Group extends AbleObject_1.default {
247
372
  ],
248
373
  });
249
374
  }
375
+ /**
376
+ * Check that a groupname is valid.
377
+ *
378
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
379
+ */
250
380
  async $checkGroupname() {
251
- const tilmeld = this.$nymph.tilmeld;
381
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
252
382
  if (!tilmeld.config.emailUsernames) {
253
383
  if (this.$data.groupname == null || !this.$data.groupname.length) {
254
384
  return { result: false, message: 'Please specify a groupname.' };
255
385
  }
256
- if (tilmeld.config.maxUsernameLength > 0 &&
257
- this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
386
+ if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
387
+ return {
388
+ result: false,
389
+ message: 'Groupnames must be at least ' +
390
+ tilmeld.config.minUsernameLength +
391
+ ' characters.',
392
+ };
393
+ }
394
+ if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
258
395
  return {
259
396
  result: false,
260
397
  message: 'Groupnames must not exceed ' +
@@ -298,8 +435,15 @@ class Group extends AbleObject_1.default {
298
435
  if (this.$data.groupname == null || !this.$data.groupname.length) {
299
436
  return { result: false, message: 'Please specify an email.' };
300
437
  }
301
- if (tilmeld.config.maxUsernameLength > 0 &&
302
- this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
438
+ if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
439
+ return {
440
+ result: false,
441
+ message: 'Emails must be at least ' +
442
+ tilmeld.config.minUsernameLength +
443
+ ' characters.',
444
+ };
445
+ }
446
+ if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
303
447
  return {
304
448
  result: false,
305
449
  message: 'Emails must not exceed ' +
@@ -310,8 +454,13 @@ class Group extends AbleObject_1.default {
310
454
  return await this.$checkEmail();
311
455
  }
312
456
  }
457
+ /**
458
+ * Check that an email is unique.
459
+ *
460
+ * @returns An object with a boolean 'result' entry and a 'message' entry.
461
+ */
313
462
  async $checkEmail() {
314
- const tilmeld = this.$nymph.tilmeld;
463
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
315
464
  if (this.$data.email === '') {
316
465
  return { result: true, message: '' };
317
466
  }
@@ -344,10 +493,11 @@ class Group extends AbleObject_1.default {
344
493
  };
345
494
  }
346
495
  async $save() {
347
- const tilmeld = this.$nymph.tilmeld;
496
+ const tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
348
497
  if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
349
498
  return false;
350
499
  }
500
+ // Formatting.
351
501
  this.$data.groupname = this.$data.groupname.trim();
352
502
  if (tilmeld.config.emailUsernames) {
353
503
  this.$data.email = this.$data.groupname;
@@ -356,16 +506,19 @@ class Group extends AbleObject_1.default {
356
506
  this.$data.name = (this.$data.name ?? '').trim();
357
507
  this.$data.phone = (this.$data.phone ?? '').trim();
358
508
  this.$data.enabled = !!this.$data.enabled;
359
- if (this.$data.name === '') {
509
+ // Clear empty values.
510
+ if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
360
511
  delete this.$data.name;
361
512
  }
362
- if (this.$data.email === '') {
513
+ if (this.$data.email === '' ||
514
+ !tilmeld.config.userFields.includes('email')) {
363
515
  delete this.$data.email;
364
516
  }
365
517
  if (this.$data.avatar === '') {
366
518
  delete this.$data.avatar;
367
519
  }
368
- if (this.$data.phone === '') {
520
+ if (this.$data.phone === '' ||
521
+ !tilmeld.config.userFields.includes('phone')) {
369
522
  delete this.$data.phone;
370
523
  }
371
524
  if (this.$data.parent == null) {
@@ -383,39 +536,50 @@ class Group extends AbleObject_1.default {
383
536
  if (!this.$data.unverifiedSecondary) {
384
537
  delete this.$data.unverifiedSecondary;
385
538
  }
539
+ // Groups should never have 'system/admin', 'tilmeld/admin', or
540
+ // 'tilmeld/switch' abilities.
386
541
  this.$data.abilities = (0, lodash_1.difference)(this.$data.abilities ?? [], [
387
542
  'system/admin',
388
543
  'tilmeld/admin',
544
+ 'tilmeld/switch',
389
545
  null,
390
546
  undefined,
391
547
  ]);
548
+ // Verification.
392
549
  const unCheck = await this.$checkGroupname();
393
550
  if (!unCheck.result) {
394
551
  throw new errors_1.BadUsernameError(unCheck.message);
395
552
  }
396
- if (!tilmeld.config.emailUsernames) {
553
+ if (!tilmeld.config.emailUsernames &&
554
+ tilmeld.config.userFields.includes('email')) {
397
555
  const emCheck = await this.$checkEmail();
398
556
  if (!emCheck.result) {
399
557
  throw new errors_1.BadEmailError(emCheck.message);
400
558
  }
401
559
  }
402
- if (this.$data.parent &&
403
- (this.$data.parent.cdate == null ||
404
- this.$is(this.$data.parent) ||
405
- this.$data.parent.$isDescendant(this))) {
560
+ // Validate group parent. Make sure it's not a descendant of this group.
561
+ const parent = this.$data.parent;
562
+ await parent?.$wake();
563
+ if (parent &&
564
+ (parent.cdate == null ||
565
+ this.$is(parent) ||
566
+ (await parent.$isDescendant(this)))) {
406
567
  throw new errors_1.BadDataError("Group parent can't be itself or descendant of itself.");
407
568
  }
408
569
  try {
409
- tilmeld.config.validatorGroup(this);
570
+ tilmeld.config.validatorGroup(tilmeld, this);
410
571
  }
411
572
  catch (e) {
412
573
  throw new errors_1.BadDataError(e?.message);
413
574
  }
575
+ // Only one default primary group is allowed.
414
576
  if (this.$data.defaultPrimary) {
415
577
  const currentPrimary = await this.$nymph.getEntity({ class: tilmeld.Group }, { type: '&', truthy: 'defaultPrimary' });
416
578
  if (currentPrimary != null && !this.$is(currentPrimary)) {
417
579
  currentPrimary.defaultPrimary = false;
418
- if (!(await currentPrimary.$save())) {
580
+ if (this.$skipAcWhenSaving
581
+ ? !(await currentPrimary.$saveSkipAC())
582
+ : !(await currentPrimary.$save())) {
419
583
  throw new errors_1.CouldNotChangeDefaultPrimaryGroupError('Could not change new user primary group from ' +
420
584
  `${currentPrimary.groupname}.`);
421
585
  }
@@ -423,6 +587,9 @@ class Group extends AbleObject_1.default {
423
587
  }
424
588
  return await super.$save();
425
589
  }
590
+ /*
591
+ * This should *never* be accessible on the client.
592
+ */
426
593
  async $saveSkipAC() {
427
594
  this.$skipAcWhenSaving = true;
428
595
  return await this.$save();
@@ -435,25 +602,29 @@ class Group extends AbleObject_1.default {
435
602
  return false;
436
603
  }
437
604
  async $delete() {
438
- let tilmeld = this.$nymph.tilmeld;
439
- if (!tilmeld.gatekeeper('tilmeld/admin')) {
605
+ let tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
606
+ if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
440
607
  throw new errors_1.BadDataError("You don't have the authority to delete groups.");
441
608
  }
442
609
  const transaction = 'tilmeld-delete-' + this.guid;
443
610
  const nymph = this.$nymph;
444
611
  const tnymph = await nymph.startTransaction(transaction);
445
- this.$nymph = tnymph;
446
- tilmeld = this.$nymph.tilmeld;
612
+ this.$setNymph(tnymph);
613
+ tilmeld = (0, enforceTilmeld_1.enforceTilmeld)(this);
614
+ // Delete descendants.
447
615
  const descendants = await this.$getDescendants();
448
616
  if (descendants.length) {
449
617
  for (let curGroup of descendants) {
450
- if (!(await curGroup.$delete())) {
618
+ if (this.$skipAcWhenDeleting
619
+ ? !(await curGroup.$deleteSkipAC())
620
+ : !(await curGroup.$delete())) {
451
621
  await tnymph.rollback(transaction);
452
- this.$nymph = nymph;
622
+ this.$setNymph(nymph);
453
623
  return false;
454
624
  }
455
625
  }
456
626
  }
627
+ // Remove users from this primary group.
457
628
  const primaryUsers = await tnymph.getEntities({
458
629
  class: tilmeld.User,
459
630
  skipAc: true,
@@ -463,12 +634,15 @@ class Group extends AbleObject_1.default {
463
634
  });
464
635
  for (let user of primaryUsers) {
465
636
  delete user.group;
466
- if (!(await user.$save())) {
637
+ if (this.$skipAcWhenDeleting
638
+ ? !(await user.$saveSkipAC())
639
+ : !(await user.$save())) {
467
640
  await tnymph.rollback(transaction);
468
- this.$nymph = nymph;
641
+ this.$setNymph(nymph);
469
642
  return false;
470
643
  }
471
644
  }
645
+ // Remove users from this secondary group.
472
646
  const secondaryUsers = await tnymph.getEntities({
473
647
  class: tilmeld.User,
474
648
  skipAc: true,
@@ -478,11 +652,14 @@ class Group extends AbleObject_1.default {
478
652
  });
479
653
  for (let user of secondaryUsers) {
480
654
  user.$delGroup(this);
481
- if (!(await user.$save())) {
655
+ if (this.$skipAcWhenDeleting
656
+ ? !(await user.$saveSkipAC())
657
+ : !(await user.$save())) {
482
658
  await tnymph.rollback(transaction);
483
659
  return false;
484
660
  }
485
661
  }
662
+ // Delete the group.
486
663
  const success = await super.$delete();
487
664
  if (success) {
488
665
  await tnymph.commit(transaction);
@@ -490,18 +667,23 @@ class Group extends AbleObject_1.default {
490
667
  else {
491
668
  await tnymph.rollback(transaction);
492
669
  }
493
- this.$nymph = nymph;
670
+ this.$setNymph(nymph);
494
671
  return success;
495
672
  }
673
+ /*
674
+ * This should *never* be accessible on the client.
675
+ */
676
+ async $deleteSkipAC() {
677
+ this.$skipAcWhenDeleting = true;
678
+ return await this.$delete();
679
+ }
680
+ $tilmeldDeleteSkipAC() {
681
+ if (this.$skipAcWhenDeleting) {
682
+ this.$skipAcWhenDeleting = false;
683
+ return true;
684
+ }
685
+ return false;
686
+ }
496
687
  }
497
688
  exports.default = Group;
498
- Group.ETYPE = 'tilmeld_group';
499
- Group.class = 'Group';
500
- Group.DEFAULT_PRIVATE_DATA = ['email', 'phone', 'abilities', 'user'];
501
- Group.DEFAULT_ALLOWLIST_DATA = [];
502
- Group.clientEnabledStaticMethods = [
503
- 'getPrimaryGroups',
504
- 'getSecondaryGroups',
505
- ];
506
- Group.searchRestrictedData = [...Group.DEFAULT_PRIVATE_DATA];
507
689
  //# sourceMappingURL=Group.js.map