@nymphjs/tilmeld 1.0.0-beta.11 → 1.0.0-beta.110
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/CHANGELOG.md +498 -0
- package/README.md +15 -2
- package/dist/AbleObject.d.ts +15 -0
- package/dist/AbleObject.js +18 -6
- package/dist/AbleObject.js.map +1 -1
- package/dist/Group.d.ts +182 -15
- package/dist/Group.js +564 -191
- package/dist/Group.js.map +1 -1
- package/dist/Group.test.js +86 -13
- package/dist/Group.test.js.map +1 -1
- package/dist/Tilmeld.d.ts +241 -12
- package/dist/Tilmeld.js +657 -257
- package/dist/Tilmeld.js.map +1 -1
- package/dist/Tilmeld.types.d.ts +5 -5
- package/dist/Tilmeld.types.js +1 -2
- package/dist/User.d.ts +396 -22
- package/dist/User.js +1379 -367
- package/dist/User.js.map +1 -1
- package/dist/User.test.js +133 -13
- package/dist/User.test.js.map +1 -1
- package/dist/conf/d.d.ts +306 -7
- package/dist/conf/d.js +1 -2
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +196 -141
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +2 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/enforceTilmeld.d.ts +3 -0
- package/dist/enforceTilmeld.js +18 -0
- package/dist/enforceTilmeld.js.map +1 -0
- package/dist/errors/AccessControlError.d.ts +2 -1
- package/dist/errors/AccessControlError.js +3 -6
- package/dist/errors/AccessControlError.js.map +1 -1
- package/dist/errors/BadDataError.d.ts +2 -1
- package/dist/errors/BadDataError.js +3 -6
- package/dist/errors/BadDataError.js.map +1 -1
- package/dist/errors/BadEmailError.d.ts +2 -1
- package/dist/errors/BadEmailError.js +3 -6
- package/dist/errors/BadEmailError.js.map +1 -1
- package/dist/errors/BadUsernameError.d.ts +2 -1
- package/dist/errors/BadUsernameError.js +3 -6
- package/dist/errors/BadUsernameError.js.map +1 -1
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js +1 -5
- package/dist/errors/CouldNotChangeDefaultPrimaryGroupError.js.map +1 -1
- package/dist/errors/EmailChangeRateLimitExceededError.js +1 -5
- package/dist/errors/EmailChangeRateLimitExceededError.js.map +1 -1
- package/dist/errors/index.d.ts +6 -6
- package/dist/errors/index.js +6 -22
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +12 -10
- package/dist/index.js +17 -35
- package/dist/index.js.map +1 -1
- package/dist/testArtifacts.d.ts +19 -0
- package/dist/testArtifacts.js +920 -0
- package/dist/testArtifacts.js.map +1 -0
- package/dist/tilmeldJoiProps.d.ts +3 -3
- package/dist/tilmeldJoiProps.js +13 -18
- package/dist/tilmeldJoiProps.js.map +1 -1
- package/jest.config.js +11 -2
- package/package.json +32 -31
- package/src/AbleObject.ts +2 -2
- package/src/Group.test.ts +107 -6
- package/src/Group.ts +676 -224
- package/src/Tilmeld.ts +450 -262
- package/src/Tilmeld.types.ts +5 -5
- package/src/User.test.ts +163 -7
- package/src/User.ts +1621 -454
- package/src/conf/d.ts +83 -17
- package/src/conf/defaults.ts +172 -109
- package/src/conf/index.ts +2 -2
- package/src/enforceTilmeld.ts +20 -0
- package/src/errors/AccessControlError.ts +4 -2
- package/src/errors/BadDataError.ts +4 -2
- package/src/errors/BadEmailError.ts +4 -2
- package/src/errors/BadUsernameError.ts +4 -2
- package/src/errors/index.ts +6 -6
- package/src/index.ts +14 -10
- package/src/testArtifacts.ts +1485 -0
- package/src/tilmeldJoiProps.ts +8 -23
- package/src/type/locutus.d.ts +3 -0
- package/tsconfig.json +5 -3
- package/typedoc.json +4 -0
package/dist/Group.js
CHANGED
|
@@ -1,15 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { nanoid } from '@nymphjs/guid';
|
|
3
|
+
import { difference, xor } from 'lodash-es';
|
|
4
|
+
import { splitn } from '@sciactive/splitn';
|
|
5
|
+
import { enforceTilmeld } from './enforceTilmeld.js';
|
|
6
|
+
import AbleObject from './AbleObject.js';
|
|
7
|
+
import { AccessControlError, BadDataError, BadEmailError, BadUsernameError, CouldNotChangeDefaultPrimaryGroupError, } from './errors/index.js';
|
|
8
|
+
import UserClass from './User.js';
|
|
9
|
+
/**
|
|
10
|
+
* A user group data model.
|
|
11
|
+
*
|
|
12
|
+
* Written by Hunter Perrin for SciActive.
|
|
13
|
+
*
|
|
14
|
+
* @author Hunter Perrin <hperrin@gmail.com>
|
|
15
|
+
* @copyright SciActive Inc
|
|
16
|
+
* @see http://nymph.io/
|
|
17
|
+
*/
|
|
18
|
+
export default class Group extends AbleObject {
|
|
19
|
+
/**
|
|
20
|
+
* The instance of Tilmeld to use for queries.
|
|
21
|
+
*/
|
|
22
|
+
static ETYPE = 'tilmeld_group';
|
|
23
|
+
static class = 'Group';
|
|
24
|
+
static checkGroupnameCallbacks = [];
|
|
25
|
+
static beforeSaveCallbacks = [];
|
|
26
|
+
static afterSaveCallbacks = [];
|
|
27
|
+
static beforeDeleteCallbacks = [];
|
|
28
|
+
static afterDeleteCallbacks = [];
|
|
29
|
+
static DEFAULT_PRIVATE_DATA = ['email', 'phone', 'abilities', 'user'];
|
|
30
|
+
static DEFAULT_ALLOWLIST_DATA = [];
|
|
31
|
+
$tags = [];
|
|
32
|
+
$clientEnabledMethods = [
|
|
33
|
+
'$checkGroupname',
|
|
34
|
+
'$checkEmail',
|
|
35
|
+
'$getAvatar',
|
|
36
|
+
'$getChildren',
|
|
37
|
+
'$getDescendants',
|
|
38
|
+
'$getLevel',
|
|
39
|
+
'$isDescendant',
|
|
40
|
+
];
|
|
41
|
+
static clientEnabledStaticMethods = [
|
|
42
|
+
'getPrimaryGroups',
|
|
43
|
+
'getSecondaryGroups',
|
|
44
|
+
];
|
|
45
|
+
$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
46
|
+
static searchRestrictedData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
47
|
+
$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
|
|
48
|
+
$allowlistTags = [];
|
|
49
|
+
/**
|
|
50
|
+
* This should only be used by the backend.
|
|
51
|
+
*/
|
|
52
|
+
$skipAcWhenSaving = false;
|
|
53
|
+
/**
|
|
54
|
+
* This should only be used by the backend.
|
|
55
|
+
*/
|
|
56
|
+
$skipAcWhenDeleting = false;
|
|
57
|
+
/**
|
|
58
|
+
* Used to save the current groupname for domain admin permissions.
|
|
59
|
+
*/
|
|
60
|
+
$originalGroupname;
|
|
61
|
+
static getFTSText(name, value) {
|
|
62
|
+
if ([
|
|
63
|
+
'abilities',
|
|
64
|
+
'parent',
|
|
65
|
+
'user',
|
|
66
|
+
'enabled',
|
|
67
|
+
'defaultPrimary',
|
|
68
|
+
'defaultSecondary',
|
|
69
|
+
'unverifiedSecondary',
|
|
70
|
+
].indexOf(name) !== -1) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return super.getFTSText(name, value);
|
|
13
74
|
}
|
|
14
75
|
static async factoryGroupname(groupname) {
|
|
15
76
|
const entity = new this();
|
|
@@ -26,63 +87,89 @@ class Group extends AbleObject_1.default {
|
|
|
26
87
|
}
|
|
27
88
|
return entity;
|
|
28
89
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
}
|
|
90
|
+
/**
|
|
91
|
+
* Get all the groups that can be assigned as primary groups.
|
|
92
|
+
*
|
|
93
|
+
* @param options The options for an optional search query.
|
|
94
|
+
* @param selectors The selectors for an optional search query.
|
|
95
|
+
* @returns An array of the assignable primary groups.
|
|
96
|
+
*/
|
|
54
97
|
static async getPrimaryGroups(options, selectors) {
|
|
55
|
-
|
|
98
|
+
const tilmeld = enforceTilmeld(this);
|
|
99
|
+
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
56
100
|
throw new Error("You don't have permission to do that.");
|
|
57
101
|
}
|
|
58
|
-
return await this.getAssignableGroups(
|
|
102
|
+
return await this.getAssignableGroups(tilmeld.config.highestPrimary, { ...options, class: tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
|
|
59
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Get all the groups that can be assigned as secondary groups.
|
|
106
|
+
*
|
|
107
|
+
* @param options The options for an optional search query.
|
|
108
|
+
* @param selectors The selectors for an optional search query.
|
|
109
|
+
* @returns An array of the assignable secondary groups.
|
|
110
|
+
*/
|
|
60
111
|
static async getSecondaryGroups(options, selectors) {
|
|
61
|
-
|
|
112
|
+
const tilmeld = enforceTilmeld(this);
|
|
113
|
+
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
62
114
|
throw new Error("You don't have permission to do that.");
|
|
63
115
|
}
|
|
64
|
-
return await this.getAssignableGroups(
|
|
116
|
+
return await this.getAssignableGroups(tilmeld.config.highestSecondary, { ...options, class: tilmeld.Group, return: 'entity' }, [...(selectors ?? [])]);
|
|
65
117
|
}
|
|
66
118
|
static async getAssignableGroups(highestParent, options, selectors) {
|
|
119
|
+
const tilmeld = enforceTilmeld(this);
|
|
67
120
|
let assignableGroups = [];
|
|
68
121
|
if (highestParent === false) {
|
|
69
122
|
return assignableGroups;
|
|
70
123
|
}
|
|
71
|
-
assignableGroups = await this.nymph.getEntities({ ...options, class:
|
|
124
|
+
assignableGroups = await this.nymph.getEntities({ ...options, class: tilmeld.Group }, ...selectors);
|
|
72
125
|
if (highestParent !== true) {
|
|
73
|
-
assignableGroups = assignableGroups.
|
|
126
|
+
assignableGroups = (await Promise.all(assignableGroups.map(async (group) => {
|
|
74
127
|
let curGroup = group;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
128
|
+
let parent = curGroup.parent;
|
|
129
|
+
await parent?.$wake();
|
|
130
|
+
while (parent != null && parent?.cdate != null) {
|
|
131
|
+
if (parent?.guid === highestParent) {
|
|
132
|
+
return group;
|
|
78
133
|
}
|
|
79
|
-
curGroup =
|
|
134
|
+
curGroup = parent;
|
|
135
|
+
parent = curGroup.parent;
|
|
136
|
+
await parent?.$wake();
|
|
80
137
|
}
|
|
81
|
-
return
|
|
82
|
-
});
|
|
138
|
+
return null;
|
|
139
|
+
}))).filter((group) => group != null);
|
|
83
140
|
}
|
|
84
141
|
return assignableGroups;
|
|
85
142
|
}
|
|
143
|
+
constructor() {
|
|
144
|
+
super();
|
|
145
|
+
// Defaults.
|
|
146
|
+
this.$data.enabled = true;
|
|
147
|
+
this.$data.abilities = [];
|
|
148
|
+
this.$updateDataProtection();
|
|
149
|
+
}
|
|
150
|
+
$setNymph(nymph) {
|
|
151
|
+
this.$nymph = nymph;
|
|
152
|
+
if (!this.$asleep()) {
|
|
153
|
+
if (this.$data.user && this.$data.user.$nymph !== nymph) {
|
|
154
|
+
this.$data.user.$setNymph(nymph);
|
|
155
|
+
}
|
|
156
|
+
if (this.$data.parent && this.$data.parent.$nymph !== nymph) {
|
|
157
|
+
this.$data.parent.$setNymph(nymph);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async $getUniques() {
|
|
162
|
+
const tilmeld = enforceTilmeld(this);
|
|
163
|
+
const uniques = [`u:${this.$data.groupname}`];
|
|
164
|
+
if (!tilmeld.config.emailUsernames &&
|
|
165
|
+
tilmeld.config.userFields.includes('email')) {
|
|
166
|
+
uniques.push(`e:${this.$data.email}`);
|
|
167
|
+
}
|
|
168
|
+
if (this.$data.defaultPrimary) {
|
|
169
|
+
uniques.push(`o:default-primary`);
|
|
170
|
+
}
|
|
171
|
+
return uniques;
|
|
172
|
+
}
|
|
86
173
|
$getAvatar() {
|
|
87
174
|
if (this.$data.avatar != null && this.$data.avatar !== '') {
|
|
88
175
|
return this.$data.avatar;
|
|
@@ -91,60 +178,117 @@ class Group extends AbleObject_1.default {
|
|
|
91
178
|
return 'https://secure.gravatar.com/avatar/?d=mm&s=40';
|
|
92
179
|
}
|
|
93
180
|
return ('https://secure.gravatar.com/avatar/' +
|
|
94
|
-
|
|
181
|
+
crypto
|
|
182
|
+
.createHash('sha256')
|
|
183
|
+
.update(this.$data.email.trim().toLowerCase())
|
|
184
|
+
.digest('hex')
|
|
185
|
+
.toLowerCase() +
|
|
95
186
|
'?d=identicon&s=40');
|
|
96
187
|
}
|
|
97
188
|
$jsonAcceptData(input, allowConflict = false) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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.");
|
|
189
|
+
this.$check();
|
|
190
|
+
if ('abilities' in input.data) {
|
|
191
|
+
this.$checkIncomingAbilities(input.data.abilities);
|
|
105
192
|
}
|
|
106
193
|
super.$jsonAcceptData(input, allowConflict);
|
|
107
194
|
}
|
|
108
195
|
$jsonAcceptPatch(patch, allowConflict = false) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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.");
|
|
196
|
+
this.$check();
|
|
197
|
+
if ('abilities' in patch.set) {
|
|
198
|
+
this.$checkIncomingAbilities(patch.set.abilities);
|
|
116
199
|
}
|
|
117
200
|
super.$jsonAcceptPatch(patch, allowConflict);
|
|
118
201
|
}
|
|
119
|
-
$
|
|
120
|
-
|
|
202
|
+
$checkIncomingAbilities(incoming) {
|
|
203
|
+
const tilmeld = enforceTilmeld(this);
|
|
204
|
+
if (!Array.isArray(incoming) ||
|
|
205
|
+
incoming.find((ability) => typeof ability !== 'string')) {
|
|
206
|
+
throw new BadDataError('Abilities must be an array of strings.');
|
|
207
|
+
}
|
|
208
|
+
if (incoming.includes('system/admin') !==
|
|
209
|
+
this.$data.abilities?.includes('system/admin') &&
|
|
210
|
+
(!tilmeld.currentUser ||
|
|
211
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
212
|
+
throw new BadDataError("You don't have the authority to grant or revoke system/admin.");
|
|
213
|
+
}
|
|
214
|
+
if (incoming.includes('tilmeld/admin') !==
|
|
215
|
+
this.$data.abilities?.includes('tilmeld/admin') &&
|
|
216
|
+
(!tilmeld.currentUser ||
|
|
217
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
218
|
+
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/admin.");
|
|
219
|
+
}
|
|
220
|
+
if (incoming.includes('tilmeld/switch') !==
|
|
221
|
+
this.$data.abilities?.includes('tilmeld/switch') &&
|
|
222
|
+
(!tilmeld.currentUser ||
|
|
223
|
+
!tilmeld.currentUser.abilities?.includes('system/admin'))) {
|
|
224
|
+
throw new BadDataError("You don't have the authority to grant or revoke tilmeld/switch.");
|
|
225
|
+
}
|
|
226
|
+
if (xor(incoming.filter((ability) => ability.startsWith('tilmeld/')), this.$data.abilities?.filter((ability) => ability.startsWith('tilmeld/')) ?? []).length &&
|
|
227
|
+
(!tilmeld.currentUser ||
|
|
228
|
+
(!tilmeld.currentUser.abilities?.includes('system/admin') &&
|
|
229
|
+
!tilmeld.currentUser.abilities?.includes('tilmeld/admin')))) {
|
|
230
|
+
throw new BadDataError("You don't have the authority to grant or revoke Tilmeld abilities.");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
$putData(data, sdata, source) {
|
|
234
|
+
super.$putData(data, sdata, source);
|
|
235
|
+
if (source === 'server') {
|
|
236
|
+
this.$originalGroupname = this.$data.groupname;
|
|
237
|
+
}
|
|
121
238
|
this.$updateDataProtection();
|
|
122
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Update the data protection arrays for a user.
|
|
242
|
+
*
|
|
243
|
+
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
244
|
+
*/
|
|
123
245
|
$updateDataProtection(givenUser) {
|
|
124
|
-
const tilmeld = this
|
|
246
|
+
const tilmeld = enforceTilmeld(this);
|
|
125
247
|
let user = givenUser ?? tilmeld.User.current();
|
|
248
|
+
let [_groupname, domain] = tilmeld.config.domainSupport
|
|
249
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
250
|
+
: [this.$data.groupname, null];
|
|
251
|
+
// Lowercase the domain part.
|
|
252
|
+
if (domain) {
|
|
253
|
+
domain = domain.toLowerCase();
|
|
254
|
+
}
|
|
126
255
|
this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
127
256
|
this.$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
|
|
128
257
|
if (tilmeld.config.emailUsernames) {
|
|
129
258
|
this.$privateData.push('groupname');
|
|
130
259
|
}
|
|
131
260
|
if (user != null &&
|
|
132
|
-
(
|
|
133
|
-
|
|
261
|
+
(user.abilities?.includes('tilmeld/admin') ||
|
|
262
|
+
user.abilities?.includes('system/admin'))) {
|
|
263
|
+
// Users who can edit groups can see their data.
|
|
134
264
|
this.$privateData = [];
|
|
135
265
|
this.$allowlistData = undefined;
|
|
136
266
|
}
|
|
137
267
|
else if (this.$data.user != null &&
|
|
138
268
|
user != null &&
|
|
139
|
-
this.$data.user
|
|
269
|
+
this.$data.user.guid === user.guid) {
|
|
270
|
+
// Users can see their group's data.
|
|
271
|
+
this.$privateData = [];
|
|
272
|
+
}
|
|
273
|
+
else if (user != null &&
|
|
274
|
+
tilmeld.config.domainSupport &&
|
|
275
|
+
domain &&
|
|
276
|
+
user.abilities?.includes(`tilmeld/domain/${domain}/admin`)) {
|
|
277
|
+
// Domain admins can see their groups' data.
|
|
140
278
|
this.$privateData = [];
|
|
141
279
|
}
|
|
142
280
|
}
|
|
143
|
-
|
|
144
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Check whether the group is a descendant of a group.
|
|
283
|
+
*
|
|
284
|
+
* @param group The group, or the group's GUID.
|
|
285
|
+
* @returns True or false.
|
|
286
|
+
*/
|
|
287
|
+
async $isDescendant(givenGroup) {
|
|
288
|
+
const tilmeld = enforceTilmeld(this);
|
|
145
289
|
let group;
|
|
146
290
|
if (typeof givenGroup === 'string') {
|
|
147
|
-
group = tilmeld.Group.
|
|
291
|
+
group = await tilmeld.Group.factory(givenGroup);
|
|
148
292
|
}
|
|
149
293
|
else {
|
|
150
294
|
group = givenGroup;
|
|
@@ -152,27 +296,41 @@ class Group extends AbleObject_1.default {
|
|
|
152
296
|
if (group.guid == null) {
|
|
153
297
|
return false;
|
|
154
298
|
}
|
|
155
|
-
if
|
|
299
|
+
// Check to see if the group is a descendant of the given group.
|
|
300
|
+
const parent = this.$data.parent;
|
|
301
|
+
await parent?.$wake();
|
|
302
|
+
if (parent == null) {
|
|
156
303
|
return false;
|
|
157
304
|
}
|
|
158
|
-
if (
|
|
305
|
+
if (parent.$is(group)) {
|
|
159
306
|
return true;
|
|
160
307
|
}
|
|
161
|
-
if (
|
|
308
|
+
if (await parent.$isDescendant(group)) {
|
|
162
309
|
return true;
|
|
163
310
|
}
|
|
164
311
|
return false;
|
|
165
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Gets an array of the group's child groups.
|
|
315
|
+
*
|
|
316
|
+
* @returns An array of groups.
|
|
317
|
+
*/
|
|
166
318
|
async $getChildren() {
|
|
167
|
-
const tilmeld = this
|
|
319
|
+
const tilmeld = enforceTilmeld(this);
|
|
168
320
|
return await this.$nymph.getEntities({ class: tilmeld.Group }, {
|
|
169
321
|
type: '&',
|
|
170
322
|
equal: ['enabled', true],
|
|
171
323
|
ref: ['parent', this],
|
|
172
324
|
});
|
|
173
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Gets an array of the group's descendant groups.
|
|
328
|
+
*
|
|
329
|
+
* @param andSelf Include this group in the returned array.
|
|
330
|
+
* @returns An array of groups.
|
|
331
|
+
*/
|
|
174
332
|
async $getDescendants(andSelf = false) {
|
|
175
|
-
const tilmeld = this
|
|
333
|
+
const tilmeld = enforceTilmeld(this);
|
|
176
334
|
let groups = [];
|
|
177
335
|
const entities = await this.$nymph.getEntities({ class: tilmeld.Group }, {
|
|
178
336
|
type: '&',
|
|
@@ -187,39 +345,41 @@ class Group extends AbleObject_1.default {
|
|
|
187
345
|
}
|
|
188
346
|
return groups;
|
|
189
347
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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);
|
|
348
|
+
/**
|
|
349
|
+
* Get the number of parents the group has.
|
|
350
|
+
*
|
|
351
|
+
* If the group is a top level group, this will return 0. If it is a child of
|
|
352
|
+
* a top level group, this will return 1. If it is a grandchild of a top level
|
|
353
|
+
* group, this will return 2, and so on.
|
|
354
|
+
*
|
|
355
|
+
* Levels will max out at 1024 to avoid recursive loops.
|
|
356
|
+
*
|
|
357
|
+
* @returns The level of the group.
|
|
358
|
+
*/
|
|
359
|
+
async $getLevel() {
|
|
360
|
+
const tilmeld = enforceTilmeld(this);
|
|
361
|
+
let group = await tilmeld.Group.factory(this.guid ?? undefined);
|
|
362
|
+
let parent = group.parent;
|
|
363
|
+
await parent?.$wake();
|
|
214
364
|
let level = 0;
|
|
215
|
-
while (
|
|
365
|
+
while (parent != null && parent.cdate != null && level < 1024) {
|
|
216
366
|
level++;
|
|
217
|
-
group =
|
|
367
|
+
group = parent;
|
|
368
|
+
parent = group.parent;
|
|
369
|
+
await parent?.$wake();
|
|
218
370
|
}
|
|
219
371
|
return level;
|
|
220
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Gets an array of users in the group.
|
|
375
|
+
*
|
|
376
|
+
* @param descendants Include users in all descendant groups too.
|
|
377
|
+
* @param limit The limit for the query.
|
|
378
|
+
* @param offset The offset for the query.
|
|
379
|
+
* @returns An array of users.
|
|
380
|
+
*/
|
|
221
381
|
async $getUsers(descendants = false, limit, offset) {
|
|
222
|
-
const tilmeld = this
|
|
382
|
+
const tilmeld = enforceTilmeld(this);
|
|
223
383
|
let groups = [];
|
|
224
384
|
if (descendants) {
|
|
225
385
|
groups = await this.$getDescendants();
|
|
@@ -247,14 +407,37 @@ class Group extends AbleObject_1.default {
|
|
|
247
407
|
],
|
|
248
408
|
});
|
|
249
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Check that a groupname is valid.
|
|
412
|
+
*
|
|
413
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
414
|
+
*/
|
|
250
415
|
async $checkGroupname() {
|
|
251
|
-
const tilmeld = this
|
|
416
|
+
const tilmeld = enforceTilmeld(this);
|
|
252
417
|
if (!tilmeld.config.emailUsernames) {
|
|
253
|
-
|
|
418
|
+
let [groupname, domain] = tilmeld.config.domainSupport
|
|
419
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
420
|
+
: [this.$data.groupname, null];
|
|
421
|
+
// Lowercase the domain part.
|
|
422
|
+
if (domain) {
|
|
423
|
+
domain = domain.toLowerCase();
|
|
424
|
+
this.$data.groupname = `${groupname}@${domain}`;
|
|
425
|
+
}
|
|
426
|
+
if (this.$data.groupname == null ||
|
|
427
|
+
!this.$data.groupname.length ||
|
|
428
|
+
groupname == null ||
|
|
429
|
+
!groupname.length) {
|
|
254
430
|
return { result: false, message: 'Please specify a groupname.' };
|
|
255
431
|
}
|
|
256
|
-
if (tilmeld.config.
|
|
257
|
-
|
|
432
|
+
if (groupname.length < tilmeld.config.minUsernameLength) {
|
|
433
|
+
return {
|
|
434
|
+
result: false,
|
|
435
|
+
message: 'Groupnames must be at least ' +
|
|
436
|
+
tilmeld.config.minUsernameLength +
|
|
437
|
+
' characters.',
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (groupname.length > tilmeld.config.maxUsernameLength) {
|
|
258
441
|
return {
|
|
259
442
|
result: false,
|
|
260
443
|
message: 'Groupnames must not exceed ' +
|
|
@@ -262,18 +445,34 @@ class Group extends AbleObject_1.default {
|
|
|
262
445
|
' characters.',
|
|
263
446
|
};
|
|
264
447
|
}
|
|
265
|
-
if (
|
|
448
|
+
if (difference(groupname.split(''), tilmeld.config.validChars.split(''))
|
|
449
|
+
.length) {
|
|
266
450
|
return {
|
|
267
451
|
result: false,
|
|
268
452
|
message: tilmeld.config.validCharsNotice,
|
|
269
453
|
};
|
|
270
454
|
}
|
|
271
|
-
if (!tilmeld.config.validRegex.test(
|
|
455
|
+
if (!tilmeld.config.validRegex.test(groupname)) {
|
|
272
456
|
return {
|
|
273
457
|
result: false,
|
|
274
458
|
message: tilmeld.config.validRegexNotice,
|
|
275
459
|
};
|
|
276
460
|
}
|
|
461
|
+
if (domain != null) {
|
|
462
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
463
|
+
return {
|
|
464
|
+
result: false,
|
|
465
|
+
message: tilmeld.config.validDomainRegexNotice,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (!this.$is(tilmeld.currentUser?.group) &&
|
|
469
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
470
|
+
return {
|
|
471
|
+
result: false,
|
|
472
|
+
message: "You don't have the authority to manage groups on this domain.",
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
277
476
|
const selector = {
|
|
278
477
|
type: '&',
|
|
279
478
|
ilike: [
|
|
@@ -288,6 +487,17 @@ class Group extends AbleObject_1.default {
|
|
|
288
487
|
if (test != null) {
|
|
289
488
|
return { result: false, message: 'That groupname is taken.' };
|
|
290
489
|
}
|
|
490
|
+
for (let callback of this.constructor
|
|
491
|
+
.checkGroupnameCallbacks) {
|
|
492
|
+
if (callback) {
|
|
493
|
+
const result = await callback(this, {
|
|
494
|
+
groupname: this.$data.groupname,
|
|
495
|
+
});
|
|
496
|
+
if (!result.result) {
|
|
497
|
+
return { message: 'Groupname is not available.', ...result };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
291
501
|
return {
|
|
292
502
|
result: true,
|
|
293
503
|
message: this.guid != null ? 'Groupname is valid.' : 'Groupname is available!',
|
|
@@ -298,8 +508,15 @@ class Group extends AbleObject_1.default {
|
|
|
298
508
|
if (this.$data.groupname == null || !this.$data.groupname.length) {
|
|
299
509
|
return { result: false, message: 'Please specify an email.' };
|
|
300
510
|
}
|
|
301
|
-
if (tilmeld.config.
|
|
302
|
-
|
|
511
|
+
if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
|
|
512
|
+
return {
|
|
513
|
+
result: false,
|
|
514
|
+
message: 'Emails must be at least ' +
|
|
515
|
+
tilmeld.config.minUsernameLength +
|
|
516
|
+
' characters.',
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
|
|
303
520
|
return {
|
|
304
521
|
result: false,
|
|
305
522
|
message: 'Emails must not exceed ' +
|
|
@@ -310,8 +527,13 @@ class Group extends AbleObject_1.default {
|
|
|
310
527
|
return await this.$checkEmail();
|
|
311
528
|
}
|
|
312
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Check that an email is unique.
|
|
532
|
+
*
|
|
533
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
534
|
+
*/
|
|
313
535
|
async $checkEmail() {
|
|
314
|
-
const tilmeld = this
|
|
536
|
+
const tilmeld = enforceTilmeld(this);
|
|
315
537
|
if (this.$data.email === '') {
|
|
316
538
|
return { result: true, message: '' };
|
|
317
539
|
}
|
|
@@ -344,10 +566,42 @@ class Group extends AbleObject_1.default {
|
|
|
344
566
|
};
|
|
345
567
|
}
|
|
346
568
|
async $save() {
|
|
347
|
-
|
|
569
|
+
let tilmeld = enforceTilmeld(this);
|
|
348
570
|
if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
|
|
349
571
|
return false;
|
|
350
572
|
}
|
|
573
|
+
// Lowercase the domain part.
|
|
574
|
+
if (tilmeld.config.domainSupport) {
|
|
575
|
+
const [groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
576
|
+
if (domain) {
|
|
577
|
+
this.$data.groupname = `${groupname}@${domain.toLowerCase()}`;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (tilmeld.config.domainSupport) {
|
|
581
|
+
const [_groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
582
|
+
const [_originalGroupname, originalDomain] = splitn(this.$originalGroupname ?? '', '@', 2);
|
|
583
|
+
if (this.guid != null &&
|
|
584
|
+
((originalDomain == null && domain != null) ||
|
|
585
|
+
(domain == null && originalDomain != null))) {
|
|
586
|
+
throw new AccessControlError("Groups can't switch to or from having a domain once created.");
|
|
587
|
+
}
|
|
588
|
+
if (!this.$skipAcWhenSaving &&
|
|
589
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
590
|
+
!tilmeld.gatekeeper('system/admin')) {
|
|
591
|
+
if (domain != null &&
|
|
592
|
+
!this.$is(tilmeld.currentUser) &&
|
|
593
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
594
|
+
throw new AccessControlError("You don't have the authority to modify groups on this domain.");
|
|
595
|
+
}
|
|
596
|
+
if (originalDomain != null &&
|
|
597
|
+
originalDomain !== domain &&
|
|
598
|
+
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
599
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))) {
|
|
600
|
+
throw new AccessControlError("You don't have the authority to modify groups on this domain.");
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// Formatting.
|
|
351
605
|
this.$data.groupname = this.$data.groupname.trim();
|
|
352
606
|
if (tilmeld.config.emailUsernames) {
|
|
353
607
|
this.$data.email = this.$data.groupname;
|
|
@@ -356,16 +610,19 @@ class Group extends AbleObject_1.default {
|
|
|
356
610
|
this.$data.name = (this.$data.name ?? '').trim();
|
|
357
611
|
this.$data.phone = (this.$data.phone ?? '').trim();
|
|
358
612
|
this.$data.enabled = !!this.$data.enabled;
|
|
359
|
-
|
|
613
|
+
// Clear empty values.
|
|
614
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
360
615
|
delete this.$data.name;
|
|
361
616
|
}
|
|
362
|
-
if (this.$data.email === ''
|
|
617
|
+
if (this.$data.email === '' ||
|
|
618
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
363
619
|
delete this.$data.email;
|
|
364
620
|
}
|
|
365
621
|
if (this.$data.avatar === '') {
|
|
366
622
|
delete this.$data.avatar;
|
|
367
623
|
}
|
|
368
|
-
if (this.$data.phone === ''
|
|
624
|
+
if (this.$data.phone === '' ||
|
|
625
|
+
!tilmeld.config.userFields.includes('phone')) {
|
|
369
626
|
delete this.$data.phone;
|
|
370
627
|
}
|
|
371
628
|
if (this.$data.parent == null) {
|
|
@@ -383,46 +640,102 @@ class Group extends AbleObject_1.default {
|
|
|
383
640
|
if (!this.$data.unverifiedSecondary) {
|
|
384
641
|
delete this.$data.unverifiedSecondary;
|
|
385
642
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
643
|
+
// Groups should never have 'system/admin', 'tilmeld/admin',
|
|
644
|
+
// 'tilmeld/switch', or domain admin abilities.
|
|
645
|
+
this.$data.abilities = (this.$data.abilities ?? []).filter((ability) => {
|
|
646
|
+
if (['system/admin', 'tilmeld/admin', 'tilmeld/switch'].indexOf(ability) !==
|
|
647
|
+
-1) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
if (ability == null) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
if (ability.startsWith('tilmeld/domain/') && ability.endsWith('/admin')) {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
return true;
|
|
657
|
+
});
|
|
658
|
+
// Verification.
|
|
392
659
|
const unCheck = await this.$checkGroupname();
|
|
393
660
|
if (!unCheck.result) {
|
|
394
|
-
throw new
|
|
661
|
+
throw new BadUsernameError(unCheck.message);
|
|
395
662
|
}
|
|
396
|
-
if (!tilmeld.config.emailUsernames
|
|
663
|
+
if (!tilmeld.config.emailUsernames &&
|
|
664
|
+
tilmeld.config.userFields.includes('email')) {
|
|
397
665
|
const emCheck = await this.$checkEmail();
|
|
398
666
|
if (!emCheck.result) {
|
|
399
|
-
throw new
|
|
667
|
+
throw new BadEmailError(emCheck.message);
|
|
400
668
|
}
|
|
401
669
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
670
|
+
// Validate group parent. Make sure it's not a descendant of this group.
|
|
671
|
+
const parent = this.$data.parent;
|
|
672
|
+
await parent?.$wake();
|
|
673
|
+
if (parent &&
|
|
674
|
+
(parent.cdate == null ||
|
|
675
|
+
this.$is(parent) ||
|
|
676
|
+
(await parent.$isDescendant(this)))) {
|
|
677
|
+
throw new BadDataError("Group parent can't be itself or descendant of itself.");
|
|
407
678
|
}
|
|
408
679
|
try {
|
|
409
|
-
tilmeld.config.validatorGroup(this);
|
|
680
|
+
tilmeld.config.validatorGroup(tilmeld, this);
|
|
410
681
|
}
|
|
411
682
|
catch (e) {
|
|
412
|
-
throw new
|
|
683
|
+
throw new BadDataError(e?.message);
|
|
413
684
|
}
|
|
685
|
+
// Only one default primary group is allowed.
|
|
414
686
|
if (this.$data.defaultPrimary) {
|
|
415
687
|
const currentPrimary = await this.$nymph.getEntity({ class: tilmeld.Group }, { type: '&', truthy: 'defaultPrimary' });
|
|
416
688
|
if (currentPrimary != null && !this.$is(currentPrimary)) {
|
|
417
689
|
currentPrimary.defaultPrimary = false;
|
|
418
|
-
if (
|
|
419
|
-
|
|
690
|
+
if (this.$skipAcWhenSaving
|
|
691
|
+
? !(await currentPrimary.$saveSkipAC())
|
|
692
|
+
: !(await currentPrimary.$save())) {
|
|
693
|
+
throw new CouldNotChangeDefaultPrimaryGroupError('Could not change new user primary group from ' +
|
|
420
694
|
`${currentPrimary.groupname}.`);
|
|
421
695
|
}
|
|
422
696
|
}
|
|
423
697
|
}
|
|
424
|
-
|
|
698
|
+
const transaction = 'tilmeld-save-group-' + (this.guid || nanoid());
|
|
699
|
+
const nymph = this.$nymph;
|
|
700
|
+
const tnymph = await nymph.startTransaction(transaction);
|
|
701
|
+
this.$setNymph(tnymph);
|
|
702
|
+
tilmeld = enforceTilmeld(this);
|
|
703
|
+
let preGuid = this.guid;
|
|
704
|
+
let preCdate = this.cdate;
|
|
705
|
+
let preMdate = this.mdate;
|
|
706
|
+
try {
|
|
707
|
+
for (let callback of this.constructor
|
|
708
|
+
.beforeSaveCallbacks) {
|
|
709
|
+
if (callback) {
|
|
710
|
+
await callback(this);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
let ret = await super.$save();
|
|
714
|
+
if (ret) {
|
|
715
|
+
this.$originalGroupname = this.$data.groupname;
|
|
716
|
+
}
|
|
717
|
+
for (let callback of this.constructor
|
|
718
|
+
.afterSaveCallbacks) {
|
|
719
|
+
if (callback) {
|
|
720
|
+
await callback(this);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
ret = await tnymph.commit(transaction);
|
|
724
|
+
this.$setNymph(nymph);
|
|
725
|
+
return ret;
|
|
726
|
+
}
|
|
727
|
+
catch (e) {
|
|
728
|
+
await tnymph.rollback(transaction);
|
|
729
|
+
this.guid = preGuid;
|
|
730
|
+
this.cdate = preCdate;
|
|
731
|
+
this.mdate = preMdate;
|
|
732
|
+
this.$setNymph(nymph);
|
|
733
|
+
throw e;
|
|
734
|
+
}
|
|
425
735
|
}
|
|
736
|
+
/*
|
|
737
|
+
* This should *never* be accessible on the client.
|
|
738
|
+
*/
|
|
426
739
|
async $saveSkipAC() {
|
|
427
740
|
this.$skipAcWhenSaving = true;
|
|
428
741
|
return await this.$save();
|
|
@@ -435,73 +748,133 @@ class Group extends AbleObject_1.default {
|
|
|
435
748
|
return false;
|
|
436
749
|
}
|
|
437
750
|
async $delete() {
|
|
438
|
-
let tilmeld = this
|
|
439
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
440
|
-
throw new
|
|
751
|
+
let tilmeld = enforceTilmeld(this);
|
|
752
|
+
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
753
|
+
throw new AccessControlError("You don't have the authority to delete groups.");
|
|
441
754
|
}
|
|
442
|
-
|
|
755
|
+
if (this.$data.groupname !== this.$originalGroupname) {
|
|
756
|
+
throw new AccessControlError("You can't delete a group while switching its groupname.");
|
|
757
|
+
}
|
|
758
|
+
const transaction = 'tilmeld-delete-group-' + this.guid;
|
|
443
759
|
const nymph = this.$nymph;
|
|
444
760
|
const tnymph = await nymph.startTransaction(transaction);
|
|
445
|
-
this.$
|
|
446
|
-
tilmeld = this
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
for (let
|
|
450
|
-
|
|
761
|
+
this.$setNymph(tnymph);
|
|
762
|
+
tilmeld = enforceTilmeld(this);
|
|
763
|
+
const User = tnymph.getEntityClass(UserClass);
|
|
764
|
+
try {
|
|
765
|
+
for (let callback of this.constructor
|
|
766
|
+
.beforeDeleteCallbacks) {
|
|
767
|
+
if (callback) {
|
|
768
|
+
await callback(this);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
// Delete descendants.
|
|
772
|
+
const descendants = await this.$getDescendants();
|
|
773
|
+
if (descendants.length) {
|
|
774
|
+
for (let curGroup of descendants) {
|
|
775
|
+
if (this.$skipAcWhenDeleting
|
|
776
|
+
? !(await curGroup.$deleteSkipAC())
|
|
777
|
+
: !(await curGroup.$delete())) {
|
|
778
|
+
await tnymph.rollback(transaction);
|
|
779
|
+
this.$setNymph(nymph);
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
// Remove users from this primary group.
|
|
785
|
+
const primaryUsers = await tnymph.getEntities({
|
|
786
|
+
class: User,
|
|
787
|
+
skipAc: true,
|
|
788
|
+
}, {
|
|
789
|
+
type: '&',
|
|
790
|
+
ref: ['group', this],
|
|
791
|
+
});
|
|
792
|
+
for (let user of primaryUsers) {
|
|
793
|
+
delete user.group;
|
|
794
|
+
if (this.$skipAcWhenDeleting
|
|
795
|
+
? !(await user.$saveSkipAC())
|
|
796
|
+
: !(await user.$save())) {
|
|
451
797
|
await tnymph.rollback(transaction);
|
|
452
|
-
this.$nymph
|
|
798
|
+
this.$setNymph(nymph);
|
|
453
799
|
return false;
|
|
454
800
|
}
|
|
455
801
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
802
|
+
// Remove users from this secondary group.
|
|
803
|
+
const secondaryUsers = await tnymph.getEntities({
|
|
804
|
+
class: User,
|
|
805
|
+
skipAc: true,
|
|
806
|
+
}, {
|
|
807
|
+
type: '&',
|
|
808
|
+
ref: ['groups', this],
|
|
809
|
+
});
|
|
810
|
+
for (let user of secondaryUsers) {
|
|
811
|
+
user.$delGroup(this);
|
|
812
|
+
if (this.$skipAcWhenDeleting
|
|
813
|
+
? !(await user.$saveSkipAC())
|
|
814
|
+
: !(await user.$save())) {
|
|
815
|
+
await tnymph.rollback(transaction);
|
|
816
|
+
this.$setNymph(nymph);
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
470
819
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
820
|
+
// Delete the group.
|
|
821
|
+
let success = await super.$delete();
|
|
822
|
+
if (success) {
|
|
823
|
+
for (let callback of this.constructor
|
|
824
|
+
.afterDeleteCallbacks) {
|
|
825
|
+
if (callback) {
|
|
826
|
+
await callback(this);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
success = await tnymph.commit(transaction);
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
482
832
|
await tnymph.rollback(transaction);
|
|
483
|
-
return false;
|
|
484
833
|
}
|
|
834
|
+
this.$setNymph(nymph);
|
|
835
|
+
return success;
|
|
485
836
|
}
|
|
486
|
-
|
|
487
|
-
if (success) {
|
|
488
|
-
await tnymph.commit(transaction);
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
837
|
+
catch (e) {
|
|
491
838
|
await tnymph.rollback(transaction);
|
|
839
|
+
this.$setNymph(nymph);
|
|
840
|
+
throw e;
|
|
492
841
|
}
|
|
493
|
-
|
|
494
|
-
|
|
842
|
+
}
|
|
843
|
+
/*
|
|
844
|
+
* This should *never* be accessible on the client.
|
|
845
|
+
*/
|
|
846
|
+
async $deleteSkipAC() {
|
|
847
|
+
this.$skipAcWhenDeleting = true;
|
|
848
|
+
return await this.$delete();
|
|
849
|
+
}
|
|
850
|
+
$tilmeldDeleteSkipAC() {
|
|
851
|
+
if (this.$skipAcWhenDeleting) {
|
|
852
|
+
this.$skipAcWhenDeleting = false;
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
static on(event, callback) {
|
|
858
|
+
const prop = (event + 'Callbacks');
|
|
859
|
+
if (!(prop in this)) {
|
|
860
|
+
throw new Error('Invalid event type.');
|
|
861
|
+
}
|
|
862
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
863
|
+
this[prop].push(callback);
|
|
864
|
+
return () => this.off(event, callback);
|
|
865
|
+
}
|
|
866
|
+
static off(event, callback) {
|
|
867
|
+
const prop = (event + 'Callbacks');
|
|
868
|
+
if (!(prop in this)) {
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
872
|
+
const i = this[prop].indexOf(callback);
|
|
873
|
+
if (i > -1) {
|
|
874
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
875
|
+
this[prop].splice(i, 1);
|
|
876
|
+
}
|
|
877
|
+
return true;
|
|
495
878
|
}
|
|
496
879
|
}
|
|
497
|
-
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
880
|
//# sourceMappingURL=Group.js.map
|