@nymphjs/tilmeld 1.0.0-beta.11 → 1.0.0-beta.111
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 +504 -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 +571 -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 +1386 -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 +683 -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 +1628 -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,124 @@ 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
|
+
// TODO: Fix these being added by the drivers.
|
|
236
|
+
// @ts-ignore
|
|
237
|
+
delete this.$data.acRead;
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
delete this.$data.acWrite;
|
|
240
|
+
// @ts-ignore
|
|
241
|
+
delete this.$data.acFull;
|
|
242
|
+
if (source === 'server') {
|
|
243
|
+
this.$originalGroupname = this.$data.groupname;
|
|
244
|
+
}
|
|
121
245
|
this.$updateDataProtection();
|
|
122
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Update the data protection arrays for a user.
|
|
249
|
+
*
|
|
250
|
+
* @param givenUser User to update protection for. If undefined, will use the currently logged in user.
|
|
251
|
+
*/
|
|
123
252
|
$updateDataProtection(givenUser) {
|
|
124
|
-
const tilmeld = this
|
|
253
|
+
const tilmeld = enforceTilmeld(this);
|
|
125
254
|
let user = givenUser ?? tilmeld.User.current();
|
|
255
|
+
let [_groupname, domain] = tilmeld.config.domainSupport
|
|
256
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
257
|
+
: [this.$data.groupname, null];
|
|
258
|
+
// Lowercase the domain part.
|
|
259
|
+
if (domain) {
|
|
260
|
+
domain = domain.toLowerCase();
|
|
261
|
+
}
|
|
126
262
|
this.$privateData = [...Group.DEFAULT_PRIVATE_DATA];
|
|
127
263
|
this.$allowlistData = [...Group.DEFAULT_ALLOWLIST_DATA];
|
|
128
264
|
if (tilmeld.config.emailUsernames) {
|
|
129
265
|
this.$privateData.push('groupname');
|
|
130
266
|
}
|
|
131
267
|
if (user != null &&
|
|
132
|
-
(
|
|
133
|
-
|
|
268
|
+
(user.abilities?.includes('tilmeld/admin') ||
|
|
269
|
+
user.abilities?.includes('system/admin'))) {
|
|
270
|
+
// Users who can edit groups can see their data.
|
|
134
271
|
this.$privateData = [];
|
|
135
272
|
this.$allowlistData = undefined;
|
|
136
273
|
}
|
|
137
274
|
else if (this.$data.user != null &&
|
|
138
275
|
user != null &&
|
|
139
|
-
this.$data.user
|
|
276
|
+
this.$data.user.guid === user.guid) {
|
|
277
|
+
// Users can see their group's data.
|
|
278
|
+
this.$privateData = [];
|
|
279
|
+
}
|
|
280
|
+
else if (user != null &&
|
|
281
|
+
tilmeld.config.domainSupport &&
|
|
282
|
+
domain &&
|
|
283
|
+
user.abilities?.includes(`tilmeld/domain/${domain}/admin`)) {
|
|
284
|
+
// Domain admins can see their groups' data.
|
|
140
285
|
this.$privateData = [];
|
|
141
286
|
}
|
|
142
287
|
}
|
|
143
|
-
|
|
144
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Check whether the group is a descendant of a group.
|
|
290
|
+
*
|
|
291
|
+
* @param group The group, or the group's GUID.
|
|
292
|
+
* @returns True or false.
|
|
293
|
+
*/
|
|
294
|
+
async $isDescendant(givenGroup) {
|
|
295
|
+
const tilmeld = enforceTilmeld(this);
|
|
145
296
|
let group;
|
|
146
297
|
if (typeof givenGroup === 'string') {
|
|
147
|
-
group = tilmeld.Group.
|
|
298
|
+
group = await tilmeld.Group.factory(givenGroup);
|
|
148
299
|
}
|
|
149
300
|
else {
|
|
150
301
|
group = givenGroup;
|
|
@@ -152,27 +303,41 @@ class Group extends AbleObject_1.default {
|
|
|
152
303
|
if (group.guid == null) {
|
|
153
304
|
return false;
|
|
154
305
|
}
|
|
155
|
-
if
|
|
306
|
+
// Check to see if the group is a descendant of the given group.
|
|
307
|
+
const parent = this.$data.parent;
|
|
308
|
+
await parent?.$wake();
|
|
309
|
+
if (parent == null) {
|
|
156
310
|
return false;
|
|
157
311
|
}
|
|
158
|
-
if (
|
|
312
|
+
if (parent.$is(group)) {
|
|
159
313
|
return true;
|
|
160
314
|
}
|
|
161
|
-
if (
|
|
315
|
+
if (await parent.$isDescendant(group)) {
|
|
162
316
|
return true;
|
|
163
317
|
}
|
|
164
318
|
return false;
|
|
165
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Gets an array of the group's child groups.
|
|
322
|
+
*
|
|
323
|
+
* @returns An array of groups.
|
|
324
|
+
*/
|
|
166
325
|
async $getChildren() {
|
|
167
|
-
const tilmeld = this
|
|
326
|
+
const tilmeld = enforceTilmeld(this);
|
|
168
327
|
return await this.$nymph.getEntities({ class: tilmeld.Group }, {
|
|
169
328
|
type: '&',
|
|
170
329
|
equal: ['enabled', true],
|
|
171
330
|
ref: ['parent', this],
|
|
172
331
|
});
|
|
173
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Gets an array of the group's descendant groups.
|
|
335
|
+
*
|
|
336
|
+
* @param andSelf Include this group in the returned array.
|
|
337
|
+
* @returns An array of groups.
|
|
338
|
+
*/
|
|
174
339
|
async $getDescendants(andSelf = false) {
|
|
175
|
-
const tilmeld = this
|
|
340
|
+
const tilmeld = enforceTilmeld(this);
|
|
176
341
|
let groups = [];
|
|
177
342
|
const entities = await this.$nymph.getEntities({ class: tilmeld.Group }, {
|
|
178
343
|
type: '&',
|
|
@@ -187,39 +352,41 @@ class Group extends AbleObject_1.default {
|
|
|
187
352
|
}
|
|
188
353
|
return groups;
|
|
189
354
|
}
|
|
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);
|
|
355
|
+
/**
|
|
356
|
+
* Get the number of parents the group has.
|
|
357
|
+
*
|
|
358
|
+
* If the group is a top level group, this will return 0. If it is a child of
|
|
359
|
+
* a top level group, this will return 1. If it is a grandchild of a top level
|
|
360
|
+
* group, this will return 2, and so on.
|
|
361
|
+
*
|
|
362
|
+
* Levels will max out at 1024 to avoid recursive loops.
|
|
363
|
+
*
|
|
364
|
+
* @returns The level of the group.
|
|
365
|
+
*/
|
|
366
|
+
async $getLevel() {
|
|
367
|
+
const tilmeld = enforceTilmeld(this);
|
|
368
|
+
let group = await tilmeld.Group.factory(this.guid ?? undefined);
|
|
369
|
+
let parent = group.parent;
|
|
370
|
+
await parent?.$wake();
|
|
214
371
|
let level = 0;
|
|
215
|
-
while (
|
|
372
|
+
while (parent != null && parent.cdate != null && level < 1024) {
|
|
216
373
|
level++;
|
|
217
|
-
group =
|
|
374
|
+
group = parent;
|
|
375
|
+
parent = group.parent;
|
|
376
|
+
await parent?.$wake();
|
|
218
377
|
}
|
|
219
378
|
return level;
|
|
220
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Gets an array of users in the group.
|
|
382
|
+
*
|
|
383
|
+
* @param descendants Include users in all descendant groups too.
|
|
384
|
+
* @param limit The limit for the query.
|
|
385
|
+
* @param offset The offset for the query.
|
|
386
|
+
* @returns An array of users.
|
|
387
|
+
*/
|
|
221
388
|
async $getUsers(descendants = false, limit, offset) {
|
|
222
|
-
const tilmeld = this
|
|
389
|
+
const tilmeld = enforceTilmeld(this);
|
|
223
390
|
let groups = [];
|
|
224
391
|
if (descendants) {
|
|
225
392
|
groups = await this.$getDescendants();
|
|
@@ -247,14 +414,37 @@ class Group extends AbleObject_1.default {
|
|
|
247
414
|
],
|
|
248
415
|
});
|
|
249
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Check that a groupname is valid.
|
|
419
|
+
*
|
|
420
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
421
|
+
*/
|
|
250
422
|
async $checkGroupname() {
|
|
251
|
-
const tilmeld = this
|
|
423
|
+
const tilmeld = enforceTilmeld(this);
|
|
252
424
|
if (!tilmeld.config.emailUsernames) {
|
|
253
|
-
|
|
425
|
+
let [groupname, domain] = tilmeld.config.domainSupport
|
|
426
|
+
? splitn(this.$data.groupname ?? '', '@', 2)
|
|
427
|
+
: [this.$data.groupname, null];
|
|
428
|
+
// Lowercase the domain part.
|
|
429
|
+
if (domain) {
|
|
430
|
+
domain = domain.toLowerCase();
|
|
431
|
+
this.$data.groupname = `${groupname}@${domain}`;
|
|
432
|
+
}
|
|
433
|
+
if (this.$data.groupname == null ||
|
|
434
|
+
!this.$data.groupname.length ||
|
|
435
|
+
groupname == null ||
|
|
436
|
+
!groupname.length) {
|
|
254
437
|
return { result: false, message: 'Please specify a groupname.' };
|
|
255
438
|
}
|
|
256
|
-
if (tilmeld.config.
|
|
257
|
-
|
|
439
|
+
if (groupname.length < tilmeld.config.minUsernameLength) {
|
|
440
|
+
return {
|
|
441
|
+
result: false,
|
|
442
|
+
message: 'Groupnames must be at least ' +
|
|
443
|
+
tilmeld.config.minUsernameLength +
|
|
444
|
+
' characters.',
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (groupname.length > tilmeld.config.maxUsernameLength) {
|
|
258
448
|
return {
|
|
259
449
|
result: false,
|
|
260
450
|
message: 'Groupnames must not exceed ' +
|
|
@@ -262,18 +452,34 @@ class Group extends AbleObject_1.default {
|
|
|
262
452
|
' characters.',
|
|
263
453
|
};
|
|
264
454
|
}
|
|
265
|
-
if (
|
|
455
|
+
if (difference(groupname.split(''), tilmeld.config.validChars.split(''))
|
|
456
|
+
.length) {
|
|
266
457
|
return {
|
|
267
458
|
result: false,
|
|
268
459
|
message: tilmeld.config.validCharsNotice,
|
|
269
460
|
};
|
|
270
461
|
}
|
|
271
|
-
if (!tilmeld.config.validRegex.test(
|
|
462
|
+
if (!tilmeld.config.validRegex.test(groupname)) {
|
|
272
463
|
return {
|
|
273
464
|
result: false,
|
|
274
465
|
message: tilmeld.config.validRegexNotice,
|
|
275
466
|
};
|
|
276
467
|
}
|
|
468
|
+
if (domain != null) {
|
|
469
|
+
if (!tilmeld.config.validDomainRegex.test(domain)) {
|
|
470
|
+
return {
|
|
471
|
+
result: false,
|
|
472
|
+
message: tilmeld.config.validDomainRegexNotice,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (!this.$is(tilmeld.currentUser?.group) &&
|
|
476
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
477
|
+
return {
|
|
478
|
+
result: false,
|
|
479
|
+
message: "You don't have the authority to manage groups on this domain.",
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
277
483
|
const selector = {
|
|
278
484
|
type: '&',
|
|
279
485
|
ilike: [
|
|
@@ -288,6 +494,17 @@ class Group extends AbleObject_1.default {
|
|
|
288
494
|
if (test != null) {
|
|
289
495
|
return { result: false, message: 'That groupname is taken.' };
|
|
290
496
|
}
|
|
497
|
+
for (let callback of this.constructor
|
|
498
|
+
.checkGroupnameCallbacks) {
|
|
499
|
+
if (callback) {
|
|
500
|
+
const result = await callback(this, {
|
|
501
|
+
groupname: this.$data.groupname,
|
|
502
|
+
});
|
|
503
|
+
if (!result.result) {
|
|
504
|
+
return { message: 'Groupname is not available.', ...result };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
291
508
|
return {
|
|
292
509
|
result: true,
|
|
293
510
|
message: this.guid != null ? 'Groupname is valid.' : 'Groupname is available!',
|
|
@@ -298,8 +515,15 @@ class Group extends AbleObject_1.default {
|
|
|
298
515
|
if (this.$data.groupname == null || !this.$data.groupname.length) {
|
|
299
516
|
return { result: false, message: 'Please specify an email.' };
|
|
300
517
|
}
|
|
301
|
-
if (tilmeld.config.
|
|
302
|
-
|
|
518
|
+
if (this.$data.groupname.length < tilmeld.config.minUsernameLength) {
|
|
519
|
+
return {
|
|
520
|
+
result: false,
|
|
521
|
+
message: 'Emails must be at least ' +
|
|
522
|
+
tilmeld.config.minUsernameLength +
|
|
523
|
+
' characters.',
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (this.$data.groupname.length > tilmeld.config.maxUsernameLength) {
|
|
303
527
|
return {
|
|
304
528
|
result: false,
|
|
305
529
|
message: 'Emails must not exceed ' +
|
|
@@ -310,8 +534,13 @@ class Group extends AbleObject_1.default {
|
|
|
310
534
|
return await this.$checkEmail();
|
|
311
535
|
}
|
|
312
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Check that an email is unique.
|
|
539
|
+
*
|
|
540
|
+
* @returns An object with a boolean 'result' entry and a 'message' entry.
|
|
541
|
+
*/
|
|
313
542
|
async $checkEmail() {
|
|
314
|
-
const tilmeld = this
|
|
543
|
+
const tilmeld = enforceTilmeld(this);
|
|
315
544
|
if (this.$data.email === '') {
|
|
316
545
|
return { result: true, message: '' };
|
|
317
546
|
}
|
|
@@ -344,10 +573,42 @@ class Group extends AbleObject_1.default {
|
|
|
344
573
|
};
|
|
345
574
|
}
|
|
346
575
|
async $save() {
|
|
347
|
-
|
|
576
|
+
let tilmeld = enforceTilmeld(this);
|
|
348
577
|
if (this.$data.groupname == null || !this.$data.groupname.trim().length) {
|
|
349
578
|
return false;
|
|
350
579
|
}
|
|
580
|
+
// Lowercase the domain part.
|
|
581
|
+
if (tilmeld.config.domainSupport) {
|
|
582
|
+
const [groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
583
|
+
if (domain) {
|
|
584
|
+
this.$data.groupname = `${groupname}@${domain.toLowerCase()}`;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (tilmeld.config.domainSupport) {
|
|
588
|
+
const [_groupname, domain] = splitn(this.$data.groupname ?? '', '@', 2);
|
|
589
|
+
const [_originalGroupname, originalDomain] = splitn(this.$originalGroupname ?? '', '@', 2);
|
|
590
|
+
if (this.guid != null &&
|
|
591
|
+
((originalDomain == null && domain != null) ||
|
|
592
|
+
(domain == null && originalDomain != null))) {
|
|
593
|
+
throw new AccessControlError("Groups can't switch to or from having a domain once created.");
|
|
594
|
+
}
|
|
595
|
+
if (!this.$skipAcWhenSaving &&
|
|
596
|
+
!tilmeld.gatekeeper('tilmeld/admin') &&
|
|
597
|
+
!tilmeld.gatekeeper('system/admin')) {
|
|
598
|
+
if (domain != null &&
|
|
599
|
+
!this.$is(tilmeld.currentUser) &&
|
|
600
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`)) {
|
|
601
|
+
throw new AccessControlError("You don't have the authority to modify groups on this domain.");
|
|
602
|
+
}
|
|
603
|
+
if (originalDomain != null &&
|
|
604
|
+
originalDomain !== domain &&
|
|
605
|
+
(!tilmeld.gatekeeper(`tilmeld/domain/${domain}/admin`) ||
|
|
606
|
+
!tilmeld.gatekeeper(`tilmeld/domain/${originalDomain}/admin`))) {
|
|
607
|
+
throw new AccessControlError("You don't have the authority to modify groups on this domain.");
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// Formatting.
|
|
351
612
|
this.$data.groupname = this.$data.groupname.trim();
|
|
352
613
|
if (tilmeld.config.emailUsernames) {
|
|
353
614
|
this.$data.email = this.$data.groupname;
|
|
@@ -356,16 +617,19 @@ class Group extends AbleObject_1.default {
|
|
|
356
617
|
this.$data.name = (this.$data.name ?? '').trim();
|
|
357
618
|
this.$data.phone = (this.$data.phone ?? '').trim();
|
|
358
619
|
this.$data.enabled = !!this.$data.enabled;
|
|
359
|
-
|
|
620
|
+
// Clear empty values.
|
|
621
|
+
if (this.$data.name === '' || !tilmeld.config.userFields.includes('name')) {
|
|
360
622
|
delete this.$data.name;
|
|
361
623
|
}
|
|
362
|
-
if (this.$data.email === ''
|
|
624
|
+
if (this.$data.email === '' ||
|
|
625
|
+
!tilmeld.config.userFields.includes('email')) {
|
|
363
626
|
delete this.$data.email;
|
|
364
627
|
}
|
|
365
628
|
if (this.$data.avatar === '') {
|
|
366
629
|
delete this.$data.avatar;
|
|
367
630
|
}
|
|
368
|
-
if (this.$data.phone === ''
|
|
631
|
+
if (this.$data.phone === '' ||
|
|
632
|
+
!tilmeld.config.userFields.includes('phone')) {
|
|
369
633
|
delete this.$data.phone;
|
|
370
634
|
}
|
|
371
635
|
if (this.$data.parent == null) {
|
|
@@ -383,46 +647,102 @@ class Group extends AbleObject_1.default {
|
|
|
383
647
|
if (!this.$data.unverifiedSecondary) {
|
|
384
648
|
delete this.$data.unverifiedSecondary;
|
|
385
649
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
650
|
+
// Groups should never have 'system/admin', 'tilmeld/admin',
|
|
651
|
+
// 'tilmeld/switch', or domain admin abilities.
|
|
652
|
+
this.$data.abilities = (this.$data.abilities ?? []).filter((ability) => {
|
|
653
|
+
if (['system/admin', 'tilmeld/admin', 'tilmeld/switch'].indexOf(ability) !==
|
|
654
|
+
-1) {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
if (ability == null) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
if (ability.startsWith('tilmeld/domain/') && ability.endsWith('/admin')) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
return true;
|
|
664
|
+
});
|
|
665
|
+
// Verification.
|
|
392
666
|
const unCheck = await this.$checkGroupname();
|
|
393
667
|
if (!unCheck.result) {
|
|
394
|
-
throw new
|
|
668
|
+
throw new BadUsernameError(unCheck.message);
|
|
395
669
|
}
|
|
396
|
-
if (!tilmeld.config.emailUsernames
|
|
670
|
+
if (!tilmeld.config.emailUsernames &&
|
|
671
|
+
tilmeld.config.userFields.includes('email')) {
|
|
397
672
|
const emCheck = await this.$checkEmail();
|
|
398
673
|
if (!emCheck.result) {
|
|
399
|
-
throw new
|
|
674
|
+
throw new BadEmailError(emCheck.message);
|
|
400
675
|
}
|
|
401
676
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
677
|
+
// Validate group parent. Make sure it's not a descendant of this group.
|
|
678
|
+
const parent = this.$data.parent;
|
|
679
|
+
await parent?.$wake();
|
|
680
|
+
if (parent &&
|
|
681
|
+
(parent.cdate == null ||
|
|
682
|
+
this.$is(parent) ||
|
|
683
|
+
(await parent.$isDescendant(this)))) {
|
|
684
|
+
throw new BadDataError("Group parent can't be itself or descendant of itself.");
|
|
407
685
|
}
|
|
408
686
|
try {
|
|
409
|
-
tilmeld.config.validatorGroup(this);
|
|
687
|
+
tilmeld.config.validatorGroup(tilmeld, this);
|
|
410
688
|
}
|
|
411
689
|
catch (e) {
|
|
412
|
-
throw new
|
|
690
|
+
throw new BadDataError(e?.message);
|
|
413
691
|
}
|
|
692
|
+
// Only one default primary group is allowed.
|
|
414
693
|
if (this.$data.defaultPrimary) {
|
|
415
694
|
const currentPrimary = await this.$nymph.getEntity({ class: tilmeld.Group }, { type: '&', truthy: 'defaultPrimary' });
|
|
416
695
|
if (currentPrimary != null && !this.$is(currentPrimary)) {
|
|
417
696
|
currentPrimary.defaultPrimary = false;
|
|
418
|
-
if (
|
|
419
|
-
|
|
697
|
+
if (this.$skipAcWhenSaving
|
|
698
|
+
? !(await currentPrimary.$saveSkipAC())
|
|
699
|
+
: !(await currentPrimary.$save())) {
|
|
700
|
+
throw new CouldNotChangeDefaultPrimaryGroupError('Could not change new user primary group from ' +
|
|
420
701
|
`${currentPrimary.groupname}.`);
|
|
421
702
|
}
|
|
422
703
|
}
|
|
423
704
|
}
|
|
424
|
-
|
|
705
|
+
const transaction = 'tilmeld-save-group-' + (this.guid || nanoid());
|
|
706
|
+
const nymph = this.$nymph;
|
|
707
|
+
const tnymph = await nymph.startTransaction(transaction);
|
|
708
|
+
this.$setNymph(tnymph);
|
|
709
|
+
tilmeld = enforceTilmeld(this);
|
|
710
|
+
let preGuid = this.guid;
|
|
711
|
+
let preCdate = this.cdate;
|
|
712
|
+
let preMdate = this.mdate;
|
|
713
|
+
try {
|
|
714
|
+
for (let callback of this.constructor
|
|
715
|
+
.beforeSaveCallbacks) {
|
|
716
|
+
if (callback) {
|
|
717
|
+
await callback(this);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
let ret = await super.$save();
|
|
721
|
+
if (ret) {
|
|
722
|
+
this.$originalGroupname = this.$data.groupname;
|
|
723
|
+
}
|
|
724
|
+
for (let callback of this.constructor
|
|
725
|
+
.afterSaveCallbacks) {
|
|
726
|
+
if (callback) {
|
|
727
|
+
await callback(this);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
ret = await tnymph.commit(transaction);
|
|
731
|
+
this.$setNymph(nymph);
|
|
732
|
+
return ret;
|
|
733
|
+
}
|
|
734
|
+
catch (e) {
|
|
735
|
+
await tnymph.rollback(transaction);
|
|
736
|
+
this.guid = preGuid;
|
|
737
|
+
this.cdate = preCdate;
|
|
738
|
+
this.mdate = preMdate;
|
|
739
|
+
this.$setNymph(nymph);
|
|
740
|
+
throw e;
|
|
741
|
+
}
|
|
425
742
|
}
|
|
743
|
+
/*
|
|
744
|
+
* This should *never* be accessible on the client.
|
|
745
|
+
*/
|
|
426
746
|
async $saveSkipAC() {
|
|
427
747
|
this.$skipAcWhenSaving = true;
|
|
428
748
|
return await this.$save();
|
|
@@ -435,73 +755,133 @@ class Group extends AbleObject_1.default {
|
|
|
435
755
|
return false;
|
|
436
756
|
}
|
|
437
757
|
async $delete() {
|
|
438
|
-
let tilmeld = this
|
|
439
|
-
if (!tilmeld.gatekeeper('tilmeld/admin')) {
|
|
440
|
-
throw new
|
|
758
|
+
let tilmeld = enforceTilmeld(this);
|
|
759
|
+
if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
|
|
760
|
+
throw new AccessControlError("You don't have the authority to delete groups.");
|
|
441
761
|
}
|
|
442
|
-
|
|
762
|
+
if (this.$data.groupname !== this.$originalGroupname) {
|
|
763
|
+
throw new AccessControlError("You can't delete a group while switching its groupname.");
|
|
764
|
+
}
|
|
765
|
+
const transaction = 'tilmeld-delete-group-' + this.guid;
|
|
443
766
|
const nymph = this.$nymph;
|
|
444
767
|
const tnymph = await nymph.startTransaction(transaction);
|
|
445
|
-
this.$
|
|
446
|
-
tilmeld = this
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
for (let
|
|
450
|
-
|
|
768
|
+
this.$setNymph(tnymph);
|
|
769
|
+
tilmeld = enforceTilmeld(this);
|
|
770
|
+
const User = tnymph.getEntityClass(UserClass);
|
|
771
|
+
try {
|
|
772
|
+
for (let callback of this.constructor
|
|
773
|
+
.beforeDeleteCallbacks) {
|
|
774
|
+
if (callback) {
|
|
775
|
+
await callback(this);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// Delete descendants.
|
|
779
|
+
const descendants = await this.$getDescendants();
|
|
780
|
+
if (descendants.length) {
|
|
781
|
+
for (let curGroup of descendants) {
|
|
782
|
+
if (this.$skipAcWhenDeleting
|
|
783
|
+
? !(await curGroup.$deleteSkipAC())
|
|
784
|
+
: !(await curGroup.$delete())) {
|
|
785
|
+
await tnymph.rollback(transaction);
|
|
786
|
+
this.$setNymph(nymph);
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
// Remove users from this primary group.
|
|
792
|
+
const primaryUsers = await tnymph.getEntities({
|
|
793
|
+
class: User,
|
|
794
|
+
skipAc: true,
|
|
795
|
+
}, {
|
|
796
|
+
type: '&',
|
|
797
|
+
ref: ['group', this],
|
|
798
|
+
});
|
|
799
|
+
for (let user of primaryUsers) {
|
|
800
|
+
delete user.group;
|
|
801
|
+
if (this.$skipAcWhenDeleting
|
|
802
|
+
? !(await user.$saveSkipAC())
|
|
803
|
+
: !(await user.$save())) {
|
|
451
804
|
await tnymph.rollback(transaction);
|
|
452
|
-
this.$nymph
|
|
805
|
+
this.$setNymph(nymph);
|
|
453
806
|
return false;
|
|
454
807
|
}
|
|
455
808
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
809
|
+
// Remove users from this secondary group.
|
|
810
|
+
const secondaryUsers = await tnymph.getEntities({
|
|
811
|
+
class: User,
|
|
812
|
+
skipAc: true,
|
|
813
|
+
}, {
|
|
814
|
+
type: '&',
|
|
815
|
+
ref: ['groups', this],
|
|
816
|
+
});
|
|
817
|
+
for (let user of secondaryUsers) {
|
|
818
|
+
user.$delGroup(this);
|
|
819
|
+
if (this.$skipAcWhenDeleting
|
|
820
|
+
? !(await user.$saveSkipAC())
|
|
821
|
+
: !(await user.$save())) {
|
|
822
|
+
await tnymph.rollback(transaction);
|
|
823
|
+
this.$setNymph(nymph);
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
470
826
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
827
|
+
// Delete the group.
|
|
828
|
+
let success = await super.$delete();
|
|
829
|
+
if (success) {
|
|
830
|
+
for (let callback of this.constructor
|
|
831
|
+
.afterDeleteCallbacks) {
|
|
832
|
+
if (callback) {
|
|
833
|
+
await callback(this);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
success = await tnymph.commit(transaction);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
482
839
|
await tnymph.rollback(transaction);
|
|
483
|
-
return false;
|
|
484
840
|
}
|
|
841
|
+
this.$setNymph(nymph);
|
|
842
|
+
return success;
|
|
485
843
|
}
|
|
486
|
-
|
|
487
|
-
if (success) {
|
|
488
|
-
await tnymph.commit(transaction);
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
844
|
+
catch (e) {
|
|
491
845
|
await tnymph.rollback(transaction);
|
|
846
|
+
this.$setNymph(nymph);
|
|
847
|
+
throw e;
|
|
492
848
|
}
|
|
493
|
-
|
|
494
|
-
|
|
849
|
+
}
|
|
850
|
+
/*
|
|
851
|
+
* This should *never* be accessible on the client.
|
|
852
|
+
*/
|
|
853
|
+
async $deleteSkipAC() {
|
|
854
|
+
this.$skipAcWhenDeleting = true;
|
|
855
|
+
return await this.$delete();
|
|
856
|
+
}
|
|
857
|
+
$tilmeldDeleteSkipAC() {
|
|
858
|
+
if (this.$skipAcWhenDeleting) {
|
|
859
|
+
this.$skipAcWhenDeleting = false;
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
static on(event, callback) {
|
|
865
|
+
const prop = (event + 'Callbacks');
|
|
866
|
+
if (!(prop in this)) {
|
|
867
|
+
throw new Error('Invalid event type.');
|
|
868
|
+
}
|
|
869
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
870
|
+
this[prop].push(callback);
|
|
871
|
+
return () => this.off(event, callback);
|
|
872
|
+
}
|
|
873
|
+
static off(event, callback) {
|
|
874
|
+
const prop = (event + 'Callbacks');
|
|
875
|
+
if (!(prop in this)) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
879
|
+
const i = this[prop].indexOf(callback);
|
|
880
|
+
if (i > -1) {
|
|
881
|
+
// @ts-ignore: The callback should always be the right type here.
|
|
882
|
+
this[prop].splice(i, 1);
|
|
883
|
+
}
|
|
884
|
+
return true;
|
|
495
885
|
}
|
|
496
886
|
}
|
|
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
887
|
//# sourceMappingURL=Group.js.map
|