@nymphjs/tilmeld 1.0.0-beta.122 → 1.0.0-beta.124

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/src/User.ts CHANGED
@@ -1,13 +1,12 @@
1
1
  import crypto from 'node:crypto';
2
2
  import {
3
- type Nymph,
4
3
  type EntityData,
5
4
  type EntityJson,
6
5
  type EntityPatch,
7
6
  type Selector,
8
7
  type SerializedEntityData,
9
- MethodFailedError,
10
- HttpError,
8
+ transaction,
9
+ transactional,
11
10
  } from '@nymphjs/nymph';
12
11
  import { humanSecret, nanoid } from '@nymphjs/guid';
13
12
  import type { EmailOptions } from 'email-templates';
@@ -374,9 +373,7 @@ export default class User extends AbleObject<UserData> {
374
373
  const entity = new this();
375
374
  if (username != null) {
376
375
  const entity = await this.nymph.getEntity(
377
- {
378
- class: this,
379
- },
376
+ { class: this },
380
377
  {
381
378
  type: '&',
382
379
  ilike: ['username', username.replace(/([\\%_])/g, (s) => `\\${s}`)],
@@ -415,10 +412,7 @@ export default class User extends AbleObject<UserData> {
415
412
  }): Promise<{ result: boolean; message: string }> {
416
413
  const tilmeld = enforceTilmeld(this);
417
414
  if (!tilmeld.config.pwRecovery) {
418
- return {
419
- result: false,
420
- message: 'Account recovery is not allowed.',
421
- };
415
+ return { result: false, message: 'Account recovery is not allowed.' };
422
416
  }
423
417
 
424
418
  let user: User & UserData;
@@ -446,10 +440,7 @@ export default class User extends AbleObject<UserData> {
446
440
  user = getUser;
447
441
  options.template = 'RecoverUsername';
448
442
  options.message = {
449
- to: {
450
- name: user.name ?? '',
451
- address: user.email ?? '',
452
- },
443
+ to: { name: user.name ?? '', address: user.email ?? '' },
453
444
  };
454
445
  options.locals = {};
455
446
  } else if (data.recoveryType === 'password') {
@@ -477,10 +468,7 @@ export default class User extends AbleObject<UserData> {
477
468
  user = getUser;
478
469
  options.template = 'RecoverPassword';
479
470
  options.message = {
480
- to: {
481
- name: user.name ?? '',
482
- address: user.email ?? '',
483
- },
471
+ to: { name: user.name ?? '', address: user.email ?? '' },
484
472
  };
485
473
  options.locals = {
486
474
  recoverCode: getUser.recoverSecret,
@@ -521,10 +509,7 @@ export default class User extends AbleObject<UserData> {
521
509
  }): Promise<{ result: boolean; message: string }> {
522
510
  const tilmeld = enforceTilmeld(this);
523
511
  if (!tilmeld.config.pwRecovery) {
524
- return {
525
- result: false,
526
- message: 'Account recovery is not allowed.',
527
- };
512
+ return { result: false, message: 'Account recovery is not allowed.' };
528
513
  }
529
514
 
530
515
  const user = await tilmeld.User.factoryUsername(data.username);
@@ -540,10 +525,7 @@ export default class User extends AbleObject<UserData> {
540
525
  1000 <
541
526
  Date.now()
542
527
  ) {
543
- return {
544
- result: false,
545
- message: 'The secret code does not match.',
546
- };
528
+ return { result: false, message: 'The secret code does not match.' };
547
529
  }
548
530
 
549
531
  if (!('password' in data) || !data.password.length) {
@@ -641,26 +623,10 @@ export default class User extends AbleObject<UserData> {
641
623
  return await this.nymph.getEntities(
642
624
  {
643
625
  class: this.nymph.getEntityClass(User),
644
- ...(options?.limit != null
645
- ? {
646
- limit: options.limit,
647
- }
648
- : {}),
649
- ...(options?.offset != null
650
- ? {
651
- offset: options.offset,
652
- }
653
- : {}),
654
- ...(options?.sort != null
655
- ? {
656
- sort: options.sort,
657
- }
658
- : {}),
659
- ...(options?.reverse != null
660
- ? {
661
- reverse: options.reverse,
662
- }
663
- : {}),
626
+ ...(options?.limit != null ? { limit: options.limit } : {}),
627
+ ...(options?.offset != null ? { offset: options.offset } : {}),
628
+ ...(options?.sort != null ? { sort: options.sort } : {}),
629
+ ...(options?.reverse != null ? { reverse: options.reverse } : {}),
664
630
  },
665
631
  {
666
632
  type: '&',
@@ -745,10 +711,7 @@ export default class User extends AbleObject<UserData> {
745
711
  }
746
712
  }
747
713
  } catch (e: any) {
748
- return {
749
- result: false,
750
- message: e.message,
751
- };
714
+ return { result: false, message: e.message };
752
715
  }
753
716
 
754
717
  this.$nymph.config.debugLog(
@@ -772,7 +735,9 @@ export default class User extends AbleObject<UserData> {
772
735
  return { result: true, message: 'You are logged in.' };
773
736
  }
774
737
 
775
- public async $switchUser(data?: { additionalData?: { [k: string]: any } }) {
738
+ public async $switchUser(data?: {
739
+ additionalData?: { [k: string]: any };
740
+ }): Promise<{ result: boolean; message: string }> {
776
741
  const tilmeld = enforceTilmeld(this);
777
742
  if (this.guid == null) {
778
743
  return { result: false, message: 'Incorrect login/password.' };
@@ -822,10 +787,7 @@ export default class User extends AbleObject<UserData> {
822
787
  }
823
788
  }
824
789
  } catch (e: any) {
825
- return {
826
- result: false,
827
- message: e.message,
828
- };
790
+ return { result: false, message: e.message };
829
791
  }
830
792
 
831
793
  this.$nymph.config.debugLog(
@@ -853,7 +815,7 @@ export default class User extends AbleObject<UserData> {
853
815
  * Log a user out of the system.
854
816
  * @returns An object with a boolean 'result' entry and a 'message' entry.
855
817
  */
856
- public async $logout() {
818
+ public async $logout(): Promise<{ result: boolean; message: string }> {
857
819
  const tilmeld = enforceTilmeld(this);
858
820
 
859
821
  try {
@@ -864,10 +826,7 @@ export default class User extends AbleObject<UserData> {
864
826
  }
865
827
  }
866
828
  } catch (e: any) {
867
- return {
868
- result: false,
869
- message: e.message,
870
- };
829
+ return { result: false, message: e.message };
871
830
  }
872
831
 
873
832
  this.$nymph.config.debugLog(
@@ -887,7 +846,7 @@ export default class User extends AbleObject<UserData> {
887
846
  return { result: true, message: 'You have been logged out.' };
888
847
  }
889
848
 
890
- public $getAvatar() {
849
+ public $getAvatar(): string {
891
850
  if (this.$data.avatar != null && this.$data.avatar !== '') {
892
851
  return this.$data.avatar;
893
852
  }
@@ -1121,7 +1080,6 @@ export default class User extends AbleObject<UserData> {
1121
1080
  this.$clientEnabledMethods.push('$changePassword');
1122
1081
  this.$clientEnabledMethods.push('$revokeCurrentTokens');
1123
1082
  this.$clientEnabledMethods.push('$logout');
1124
- this.$clientEnabledMethods.push('$sendEmailVerification');
1125
1083
  this.$clientEnabledMethods.push('$hasTOTPSecret');
1126
1084
  this.$clientEnabledMethods.push('$getNewTOTPSecret');
1127
1085
  this.$clientEnabledMethods.push('$saveTOTPSecret');
@@ -1335,7 +1293,7 @@ export default class User extends AbleObject<UserData> {
1335
1293
  *
1336
1294
  * @returns True on success, false on failure.
1337
1295
  */
1338
- public async $sendEmailVerification() {
1296
+ public async $sendEmailVerification(): Promise<boolean> {
1339
1297
  const tilmeld = enforceTilmeld(this);
1340
1298
  if (this.guid == null) {
1341
1299
  return false;
@@ -1362,9 +1320,7 @@ export default class User extends AbleObject<UserData> {
1362
1320
  address: this.$data.email ?? '',
1363
1321
  },
1364
1322
  },
1365
- locals: {
1366
- verifyLink: link,
1367
- },
1323
+ locals: { verifyLink: link },
1368
1324
  },
1369
1325
  this,
1370
1326
  ));
@@ -1644,7 +1600,7 @@ export default class User extends AbleObject<UserData> {
1644
1600
  }
1645
1601
  }
1646
1602
 
1647
- public async $hasTOTPSecret() {
1603
+ public $hasTOTPSecret(): boolean {
1648
1604
  return this.$data.totpSecret != null;
1649
1605
  }
1650
1606
 
@@ -1678,7 +1634,11 @@ export default class User extends AbleObject<UserData> {
1678
1634
  *
1679
1635
  * @returns An object with 'uri', 'qrcode', and 'secret'.
1680
1636
  */
1681
- public async $getNewTOTPSecret() {
1637
+ public async $getNewTOTPSecret(): Promise<{
1638
+ uri: string;
1639
+ qrcode: string;
1640
+ secret: string;
1641
+ }> {
1682
1642
  const tilmeld = enforceTilmeld(this);
1683
1643
 
1684
1644
  if (this.$data.totpSecret != null) {
@@ -1700,11 +1660,7 @@ export default class User extends AbleObject<UserData> {
1700
1660
 
1701
1661
  const qrcode = await toDataURL(uri);
1702
1662
 
1703
- return {
1704
- uri,
1705
- qrcode,
1706
- secret: secret.base32,
1707
- };
1663
+ return { uri, qrcode, secret: secret.base32 };
1708
1664
  }
1709
1665
 
1710
1666
  /**
@@ -1820,10 +1776,7 @@ export default class User extends AbleObject<UserData> {
1820
1776
 
1821
1777
  try {
1822
1778
  await this.$save();
1823
- return {
1824
- result: true,
1825
- message: 'Two factor secret has been removed.',
1826
- };
1779
+ return { result: true, message: 'Two factor secret has been removed.' };
1827
1780
  } catch (e: any) {
1828
1781
  return { result: false, message: "Couldn't remove two factor secret." };
1829
1782
  }
@@ -1884,16 +1837,10 @@ export default class User extends AbleObject<UserData> {
1884
1837
  difference(username.split(''), tilmeld.config.validChars.split(''))
1885
1838
  .length
1886
1839
  ) {
1887
- return {
1888
- result: false,
1889
- message: tilmeld.config.validCharsNotice,
1890
- };
1840
+ return { result: false, message: tilmeld.config.validCharsNotice };
1891
1841
  }
1892
1842
  if (!tilmeld.config.validRegex.test(username)) {
1893
- return {
1894
- result: false,
1895
- message: tilmeld.config.validRegexNotice,
1896
- };
1843
+ return { result: false, message: tilmeld.config.validRegexNotice };
1897
1844
  }
1898
1845
  if (domain != null) {
1899
1846
  if (!tilmeld.config.validDomainRegex.test(domain)) {
@@ -1999,10 +1946,7 @@ export default class User extends AbleObject<UserData> {
1999
1946
  }
2000
1947
  }
2001
1948
  if (!tilmeld.config.validEmailRegex.test(this.$data.email)) {
2002
- return {
2003
- result: false,
2004
- message: tilmeld.config.validEmailRegexNotice,
2005
- };
1949
+ return { result: false, message: tilmeld.config.validEmailRegexNotice };
2006
1950
  }
2007
1951
  const selector: Selector = {
2008
1952
  type: '&',
@@ -2122,185 +2066,157 @@ export default class User extends AbleObject<UserData> {
2122
2066
  this.$data.email = this.$data.username;
2123
2067
  }
2124
2068
 
2125
- // Start transaction.
2126
- const transaction = 'tilmeld-register-' + nanoid();
2127
- const nymph = this.$nymph;
2128
- const tnymph = await this.$nymph.startTransaction(transaction);
2129
- this.$setNymph(tnymph);
2130
- tilmeld = enforceTilmeld(this);
2069
+ try {
2070
+ const result = await transaction(
2071
+ this.$nymph,
2072
+ 'tilmeld-register-' + nanoid(),
2073
+ async (nymph) => {
2074
+ this.$setNymph(nymph);
2075
+ const tilmeld = enforceTilmeld(this);
2131
2076
 
2132
- let message = '';
2133
- let loggedin = false;
2077
+ let message = '';
2078
+ let loggedin = false;
2134
2079
 
2135
- try {
2136
- try {
2137
- for (let callback of (this.constructor as typeof User)
2138
- .beforeRegisterCallbacks) {
2139
- if (callback) {
2140
- await callback(this, data);
2080
+ for (let callback of (this.constructor as typeof User)
2081
+ .beforeRegisterCallbacks) {
2082
+ if (callback) {
2083
+ await callback(this, data);
2084
+ }
2141
2085
  }
2142
- }
2143
- } catch (e: any) {
2144
- await tnymph.rollback(transaction);
2145
- this.$setNymph(nymph);
2146
- return {
2147
- result: false,
2148
- loggedin: false,
2149
- message: e.message,
2150
- };
2151
- }
2152
2086
 
2153
- if (
2154
- tilmeld.config.userFields.includes('email') &&
2155
- tilmeld.config.verifyEmail
2156
- ) {
2157
- // The user will be enabled after verifying their e-mail address.
2158
- if (!tilmeld.config.unverifiedAccess) {
2159
- this.$data.enabled = false;
2160
- }
2161
- } else {
2162
- this.$data.enabled = true;
2163
- }
2087
+ if (
2088
+ tilmeld.config.userFields.includes('email') &&
2089
+ tilmeld.config.verifyEmail
2090
+ ) {
2091
+ // The user will be enabled after verifying their e-mail address.
2092
+ if (!tilmeld.config.unverifiedAccess) {
2093
+ this.$data.enabled = false;
2094
+ }
2095
+ } else {
2096
+ this.$data.enabled = true;
2097
+ }
2164
2098
 
2165
- // If createAdmin is true and there are no other users, grant
2166
- // "system/admin".
2167
- let madeAdmin = false;
2168
- if (tilmeld.config.createAdmin) {
2169
- const otherUsers = await tnymph.getEntities({
2170
- class: tilmeld.User,
2171
- skipAc: true,
2172
- limit: 1,
2173
- return: 'guid',
2174
- });
2175
- // Make sure it's not just null, cause that means an error.
2176
- if (otherUsers == null) {
2177
- await tnymph.rollback(transaction);
2178
- this.$setNymph(nymph);
2179
- return {
2180
- result: false,
2181
- loggedin: false,
2182
- message: 'An error occurred.',
2183
- };
2184
- }
2185
- if (!otherUsers.length) {
2186
- this.$grant('system/admin');
2187
- this.$data.enabled = true;
2188
- madeAdmin = true;
2189
- }
2190
- }
2099
+ // If createAdmin is true and there are no other users, grant
2100
+ // "system/admin".
2101
+ let madeAdmin = false;
2102
+ if (tilmeld.config.createAdmin) {
2103
+ const otherUsers = await this.$nymph.getEntities({
2104
+ class: tilmeld.User,
2105
+ skipAc: true,
2106
+ limit: 1,
2107
+ return: 'guid',
2108
+ });
2109
+ // Make sure it's not just null, cause that means an error.
2110
+ if (otherUsers == null) {
2111
+ throw new Error('An error occurred.');
2112
+ }
2113
+ if (!otherUsers.length) {
2114
+ this.$grant('system/admin');
2115
+ this.$data.enabled = true;
2116
+ madeAdmin = true;
2117
+ }
2118
+ }
2191
2119
 
2192
- this.$nymph.config.debugLog(
2193
- 'tilmeld',
2194
- `Registering new user "${this.$data.username}".`,
2195
- );
2196
- try {
2197
- await this.$saveSkipAC();
2198
- this.$nymph.config.debugLog(
2199
- 'tilmeld',
2200
- `New user registered "${this.$data.username}".`,
2201
- );
2202
- // Send the new user registered email.
2203
- if (tilmeld.config.userRegisteredRecipient != null) {
2204
- await tilmeld.config.sendEmail(
2205
- tilmeld,
2206
- {
2207
- template: 'UserRegistered',
2208
- message: {
2209
- to: tilmeld.config.userRegisteredRecipient,
2210
- },
2211
- locals: {
2212
- userUsername: this.$data.username,
2213
- userName: this.$data.name,
2214
- userFirstName: this.$data.nameFirst,
2215
- userLastName: this.$data.nameLast,
2216
- userEmail: this.$data.email,
2217
- userPhone: this.$data.phone,
2218
- },
2219
- },
2220
- this,
2120
+ this.$nymph.config.debugLog(
2121
+ 'tilmeld',
2122
+ `Registering new user "${this.$data.username}".`,
2221
2123
  );
2222
- }
2223
2124
 
2224
- // Finish up.
2225
- if (
2226
- tilmeld.config.verifyEmail &&
2227
- !tilmeld.config.unverifiedAccess &&
2228
- !madeAdmin &&
2229
- tilmeld.config.userFields.includes('email')
2230
- ) {
2231
- message +=
2232
- `Almost there. An email has been sent to ${this.$data.email} ` +
2233
- 'with a verification link for you to finish registration.';
2234
- } else if (
2235
- tilmeld.config.verifyEmail &&
2236
- tilmeld.config.unverifiedAccess &&
2237
- !madeAdmin &&
2238
- tilmeld.config.userFields.includes('email')
2239
- ) {
2240
- if (!(await tilmeld.login(this, true))) {
2241
- throw new Error('An error occurred trying to log you in.');
2125
+ await this.$saveSkipAC();
2126
+
2127
+ // Finish up.
2128
+ if (
2129
+ tilmeld.config.verifyEmail &&
2130
+ !tilmeld.config.unverifiedAccess &&
2131
+ !madeAdmin &&
2132
+ tilmeld.config.userFields.includes('email')
2133
+ ) {
2134
+ message +=
2135
+ `Almost there. An email has been sent to ${this.$data.email} ` +
2136
+ 'with a verification link for you to finish registration.';
2137
+ } else if (
2138
+ tilmeld.config.verifyEmail &&
2139
+ tilmeld.config.unverifiedAccess &&
2140
+ !madeAdmin &&
2141
+ tilmeld.config.userFields.includes('email')
2142
+ ) {
2143
+ if (!(await tilmeld.login(this, true))) {
2144
+ throw new Error('An error occurred trying to log you in.');
2145
+ }
2146
+ message +=
2147
+ "You're now logged in! An email has been sent to " +
2148
+ `${this.$data.email} with a verification link for you to finish ` +
2149
+ 'registration.';
2150
+ loggedin = true;
2151
+ } else {
2152
+ if (!(await tilmeld.login(this, true))) {
2153
+ throw new Error('An error occurred trying to log you in.');
2154
+ }
2155
+ message += "You're now registered and logged in!";
2156
+ loggedin = true;
2242
2157
  }
2243
- message +=
2244
- "You're now logged in! An email has been sent to " +
2245
- `${this.$data.email} with a verification link for you to finish ` +
2246
- 'registration.';
2247
- loggedin = true;
2248
- } else {
2249
- if (!(await tilmeld.login(this, true))) {
2250
- throw new Error('An error occurred trying to log you in.');
2158
+
2159
+ for (let callback of (this.constructor as typeof User)
2160
+ .afterRegisterCallbacks) {
2161
+ if (callback) {
2162
+ await callback(this, { loggedin, message });
2163
+ }
2251
2164
  }
2252
- message += "You're now registered and logged in!";
2253
- loggedin = true;
2254
- }
2255
2165
 
2256
- for (let callback of (this.constructor as typeof User)
2257
- .afterRegisterCallbacks) {
2258
- if (callback) {
2259
- await callback(this, {
2260
- loggedin,
2261
- message,
2262
- });
2166
+ return { result: true, loggedin, message };
2167
+ },
2168
+ async (nymph) => {
2169
+ this.$setNymph(nymph);
2170
+ },
2171
+ );
2172
+
2173
+ // Send the new user registered email.
2174
+ if (tilmeld.config.userRegisteredRecipient != null) {
2175
+ try {
2176
+ if (
2177
+ !(await tilmeld.config.sendEmail(
2178
+ tilmeld,
2179
+ {
2180
+ template: 'UserRegistered',
2181
+ message: { to: tilmeld.config.userRegisteredRecipient },
2182
+ locals: {
2183
+ userUsername: this.$data.username,
2184
+ userName: this.$data.name,
2185
+ userFirstName: this.$data.nameFirst,
2186
+ userLastName: this.$data.nameLast,
2187
+ userEmail: this.$data.email,
2188
+ userPhone: this.$data.phone,
2189
+ },
2190
+ },
2191
+ this,
2192
+ ))
2193
+ ) {
2194
+ throw new Error('Failed to send.');
2263
2195
  }
2196
+ } catch (e: any) {
2197
+ this.$nymph.config.debugError(
2198
+ 'tilmeld',
2199
+ `Couldn't send new user registered email for "${this.$data.username}": ${e.message}`,
2200
+ );
2264
2201
  }
2265
- } catch (e: any) {
2266
- if (e instanceof HttpError) {
2267
- throw e;
2202
+ }
2203
+
2204
+ if (result.loggedin) {
2205
+ try {
2206
+ await this.$nymph.tilmeld?.fillSession(this);
2207
+ } catch (e: any) {
2208
+ return {
2209
+ result: true,
2210
+ loggedin: false,
2211
+ message: `You are registered, but an error occurred logging you in: ${e.message}`,
2212
+ };
2268
2213
  }
2269
- this.$nymph.config.debugError(
2270
- 'tilmeld',
2271
- `Error registering new user "${this.$data.username}".`,
2272
- );
2273
- await tnymph.rollback(transaction);
2274
- this.$setNymph(nymph);
2275
- return {
2276
- result: false,
2277
- loggedin: false,
2278
- message: 'Error registering user.',
2279
- };
2280
2214
  }
2281
- } catch (e: any) {
2282
- this.$nymph.config.debugError(
2283
- 'tilmeld',
2284
- `Error registering new user "${this.$data.username}": ${e}`,
2285
- );
2286
- await tnymph.rollback(transaction);
2287
- this.$setNymph(nymph);
2288
- throw e;
2289
- }
2290
2215
 
2291
- try {
2292
- await tnymph.commit(transaction);
2293
- this.$setNymph(nymph);
2294
- await this.$nymph.tilmeld?.fillSession(this);
2216
+ return result;
2295
2217
  } catch (e: any) {
2296
- throw e;
2218
+ return { result: false, loggedin: false, message: e.message };
2297
2219
  }
2298
-
2299
- return {
2300
- result: true,
2301
- loggedin,
2302
- message,
2303
- };
2304
2220
  }
2305
2221
 
2306
2222
  public async $save() {
@@ -2590,201 +2506,167 @@ export default class User extends AbleObject<UserData> {
2590
2506
  throw new BadDataError('A password is required.');
2591
2507
  }
2592
2508
 
2593
- // Start transaction.
2594
- const transaction = 'tilmeld-save-user-' + (this.guid || nanoid());
2595
- const nymph = this.$nymph;
2596
- const tnymph = await this.$nymph.startTransaction(transaction);
2597
- this.$setNymph(tnymph);
2598
- tilmeld = enforceTilmeld(this);
2599
- const Group = tnymph.getEntityClass(GroupClass);
2509
+ let preGuid = this.guid;
2510
+ let preCdate = this.cdate;
2511
+ let preMdate = this.mdate;
2512
+ return await transaction(
2513
+ this.$nymph,
2514
+ 'tilmeld-save-user-' + (this.guid || nanoid()),
2515
+ async (nymph) => {
2516
+ this.$setNymph(nymph);
2517
+ const tilmeld = enforceTilmeld(this);
2518
+ const Group = this.$nymph.getEntityClass(GroupClass);
2600
2519
 
2601
- try {
2602
- let group = this.$data.group;
2603
- if (group != null) {
2604
- // Make sure to get fresh group data.
2605
- if (group.$asleep()) {
2606
- await group.$wake();
2607
- } else {
2608
- if (!(await group.$refresh())) {
2609
- throw new Error("Couldn't retrieve primary group info.");
2520
+ let group = this.$data.group;
2521
+ if (group != null) {
2522
+ // Make sure to get fresh group data.
2523
+ if (group.$asleep()) {
2524
+ await group.$wake();
2525
+ } else {
2526
+ if (!(await group.$refresh())) {
2527
+ throw new Error("Couldn't retrieve primary group info.");
2528
+ }
2610
2529
  }
2611
2530
  }
2612
- }
2613
- if (group == null && this.guid == null) {
2614
- if (tilmeld.config.generatePrimary) {
2615
- // Generate a new primary group for the user.
2616
- group = await Group.factory();
2617
- group.user = this;
2618
- const parent = await tnymph.getEntity(
2619
- { class: Group },
2620
- {
2621
- type: '&',
2622
- equal: ['defaultPrimary', true],
2623
- },
2624
- );
2625
- if (parent != null) {
2626
- group.parent = parent;
2627
- }
2628
- } else {
2629
- // Add the default primary.
2630
- const group = await tnymph.getEntity(
2631
- { class: Group },
2632
- {
2633
- type: '&',
2634
- equal: ['defaultPrimary', true],
2635
- },
2636
- );
2637
- if (group != null) {
2638
- this.$data.group = group;
2531
+ if (group == null && this.guid == null) {
2532
+ if (tilmeld.config.generatePrimary) {
2533
+ // Generate a new primary group for the user.
2534
+ group = await Group.factory();
2535
+ group.user = this;
2536
+ const parent = await this.$nymph.getEntity(
2537
+ { class: Group },
2538
+ { type: '&', equal: ['defaultPrimary', true] },
2539
+ );
2540
+ if (parent != null) {
2541
+ group.parent = parent;
2542
+ }
2543
+ } else {
2544
+ // Add the default primary.
2545
+ const group = await this.$nymph.getEntity(
2546
+ { class: Group },
2547
+ { type: '&', equal: ['defaultPrimary', true] },
2548
+ );
2549
+ if (group != null) {
2550
+ this.$data.group = group;
2551
+ }
2639
2552
  }
2640
2553
  }
2641
- }
2642
- if (group != null && group.user != null && this.$is(group.user)) {
2643
- // Update the user's generated primary group.
2644
- let changed = false;
2645
- if (group.groupname !== this.$data.username) {
2646
- group.groupname = this.$data.username;
2647
- changed = true;
2648
- }
2649
- if (group.avatar !== this.$data.avatar) {
2650
- group.avatar = this.$data.avatar;
2651
- changed = true;
2652
- }
2653
- if (
2654
- tilmeld.config.userFields.includes('email') &&
2655
- group.email !== this.$data.email
2656
- ) {
2657
- group.email = this.$data.email;
2658
- changed = true;
2659
- }
2660
- if (
2661
- tilmeld.config.userFields.includes('name') &&
2662
- group.name !== this.$data.name
2663
- ) {
2664
- group.name = this.$data.name;
2665
- changed = true;
2666
- }
2667
- if (
2668
- tilmeld.config.userFields.includes('phone') &&
2669
- group.phone !== this.$data.phone
2670
- ) {
2671
- group.phone = this.$data.phone;
2672
- changed = true;
2554
+ if (group != null && group.user != null && this.$is(group.user)) {
2555
+ // Update the user's generated primary group.
2556
+ let changed = false;
2557
+ if (group.groupname !== this.$data.username) {
2558
+ group.groupname = this.$data.username;
2559
+ changed = true;
2560
+ }
2561
+ if (group.avatar !== this.$data.avatar) {
2562
+ group.avatar = this.$data.avatar;
2563
+ changed = true;
2564
+ }
2565
+ if (
2566
+ tilmeld.config.userFields.includes('email') &&
2567
+ group.email !== this.$data.email
2568
+ ) {
2569
+ group.email = this.$data.email;
2570
+ changed = true;
2571
+ }
2572
+ if (
2573
+ tilmeld.config.userFields.includes('name') &&
2574
+ group.name !== this.$data.name
2575
+ ) {
2576
+ group.name = this.$data.name;
2577
+ changed = true;
2578
+ }
2579
+ if (
2580
+ tilmeld.config.userFields.includes('phone') &&
2581
+ group.phone !== this.$data.phone
2582
+ ) {
2583
+ group.phone = this.$data.phone;
2584
+ changed = true;
2585
+ }
2586
+ if (changed || group.guid == null) {
2587
+ await group.$saveSkipAC();
2588
+ }
2589
+ this.$data.group = group;
2673
2590
  }
2674
- if (changed || group.guid == null) {
2675
- await group.$saveSkipAC();
2591
+
2592
+ if (this.$data.groups == null || this.$data.groups.length === 0) {
2593
+ // Add secondary groups.
2594
+ if (
2595
+ tilmeld.config.userFields.includes('email') &&
2596
+ tilmeld.config.verifyEmail &&
2597
+ tilmeld.config.unverifiedAccess &&
2598
+ this.$data.secret != null
2599
+ ) {
2600
+ // Add the default secondaries for unverified users.
2601
+ this.$data.groups = await this.$nymph.getEntities(
2602
+ { class: Group },
2603
+ { type: '&', equal: ['unverifiedSecondary', true] },
2604
+ );
2605
+ } else {
2606
+ // Add the default secondaries.
2607
+ this.$data.groups = await this.$nymph.getEntities(
2608
+ { class: Group },
2609
+ { type: '&', equal: ['defaultSecondary', true] },
2610
+ );
2611
+ }
2676
2612
  }
2677
- this.$data.group = group;
2678
- }
2679
2613
 
2680
- if (this.$data.groups == null || this.$data.groups.length === 0) {
2681
- // Add secondary groups.
2682
- if (
2683
- tilmeld.config.userFields.includes('email') &&
2684
- tilmeld.config.verifyEmail &&
2685
- tilmeld.config.unverifiedAccess &&
2686
- this.$data.secret != null
2687
- ) {
2688
- // Add the default secondaries for unverified users.
2689
- this.$data.groups = await tnymph.getEntities(
2690
- { class: Group },
2691
- {
2692
- type: '&',
2693
- equal: ['unverifiedSecondary', true],
2694
- },
2695
- );
2696
- } else {
2697
- // Add the default secondaries.
2698
- this.$data.groups = await tnymph.getEntities(
2699
- { class: Group },
2700
- {
2701
- type: '&',
2702
- equal: ['defaultSecondary', true],
2703
- },
2704
- );
2614
+ try {
2615
+ tilmeld.config.validatorUser(tilmeld, this);
2616
+ } catch (e: any) {
2617
+ throw new BadDataError(e?.message);
2705
2618
  }
2706
- }
2707
2619
 
2708
- try {
2709
- tilmeld.config.validatorUser(tilmeld, this);
2710
- } catch (e: any) {
2711
- throw new BadDataError(e?.message);
2712
- }
2713
- } catch (e: any) {
2714
- await tnymph.rollback(transaction);
2715
- this.$setNymph(nymph);
2716
- throw e;
2717
- }
2620
+ for (let callback of (this.constructor as typeof User)
2621
+ .beforeSaveCallbacks) {
2622
+ if (callback) {
2623
+ await callback(this);
2624
+ }
2625
+ }
2718
2626
 
2719
- let preGuid = this.guid;
2720
- let preCdate = this.cdate;
2721
- let preMdate = this.mdate;
2627
+ await super.$save();
2722
2628
 
2723
- try {
2724
- for (let callback of (this.constructor as typeof User)
2725
- .beforeSaveCallbacks) {
2726
- if (callback) {
2727
- await callback(this);
2629
+ if (sendVerification) {
2630
+ // The email has changed, so send a new verification email.
2631
+ if (!(await this.$sendEmailVerification())) {
2632
+ throw new Error("Couldn't send verification email.");
2633
+ }
2728
2634
  }
2729
- }
2730
2635
 
2731
- await super.$save();
2732
- } catch (e: any) {
2733
- await tnymph.rollback(transaction);
2734
- this.guid = preGuid;
2735
- this.cdate = preCdate;
2736
- this.mdate = preMdate;
2737
- this.$setNymph(nymph);
2738
- throw e;
2739
- }
2740
-
2741
- if (sendVerification) {
2742
- // The email has changed, so send a new verification email.
2743
- if (!(await this.$sendEmailVerification())) {
2744
- await tnymph.rollback(transaction);
2745
- this.guid = preGuid;
2746
- this.cdate = preCdate;
2747
- this.mdate = preMdate;
2748
- this.$setNymph(nymph);
2749
- throw new Error("Couldn't send verification email.");
2750
- }
2751
- }
2752
-
2753
- this.$descendantGroups = undefined;
2754
- this.$gatekeeperCache = undefined;
2636
+ this.$descendantGroups = undefined;
2637
+ this.$gatekeeperCache = undefined;
2755
2638
 
2756
- try {
2757
- for (let callback of (this.constructor as typeof User)
2758
- .afterSaveCallbacks) {
2759
- if (callback) {
2760
- await callback(this);
2639
+ for (let callback of (this.constructor as typeof User)
2640
+ .afterSaveCallbacks) {
2641
+ if (callback) {
2642
+ await callback(this);
2643
+ }
2761
2644
  }
2762
- }
2763
- } catch (e: any) {
2764
- await tnymph.rollback(transaction);
2765
- this.guid = preGuid;
2766
- this.cdate = preCdate;
2767
- this.mdate = preMdate;
2768
- this.$setNymph(nymph);
2769
- throw e;
2770
- }
2771
-
2772
- const committed = await tnymph.commit(transaction);
2773
- this.$setNymph(nymph);
2645
+ },
2646
+ async (nymph, committed) => {
2647
+ this.$setNymph(nymph);
2774
2648
 
2775
- if (committed) {
2776
- this.$originalEmail = this.$data.email;
2777
- this.$originalUsername = this.$data.username;
2649
+ if (committed) {
2650
+ this.$originalEmail = this.$data.email;
2651
+ this.$originalUsername = this.$data.username;
2778
2652
 
2779
- const tilmeld = enforceTilmeld(nymph);
2653
+ try {
2654
+ const tilmeld = enforceTilmeld(nymph);
2780
2655
 
2781
- if (tilmeld.User.current(true).$is(this)) {
2782
- // Update the user in the session cache.
2783
- await tilmeld.fillSession(this);
2784
- }
2785
- } else {
2786
- throw new MethodFailedError('Transaction could not be committed.');
2787
- }
2656
+ if (tilmeld.User.current(true).$is(this)) {
2657
+ // Update the user in the session cache.
2658
+ await tilmeld.fillSession(this);
2659
+ }
2660
+ } catch (e: any) {
2661
+ // Ignore.
2662
+ }
2663
+ } else {
2664
+ this.guid = preGuid;
2665
+ this.cdate = preCdate;
2666
+ this.mdate = preMdate;
2667
+ }
2668
+ },
2669
+ );
2788
2670
  }
2789
2671
 
2790
2672
  /**
@@ -2803,6 +2685,7 @@ export default class User extends AbleObject<UserData> {
2803
2685
  return false;
2804
2686
  }
2805
2687
 
2688
+ @transactional
2806
2689
  public async $delete() {
2807
2690
  const tilmeld = enforceTilmeld(this);
2808
2691
  if (!this.$skipAcWhenDeleting && !tilmeld.gatekeeper('tilmeld/admin')) {
@@ -2825,88 +2708,52 @@ export default class User extends AbleObject<UserData> {
2825
2708
  );
2826
2709
  }
2827
2710
 
2828
- const transaction = 'tilmeld-delete-user-' + this.guid;
2829
- const nymph = this.$nymph;
2830
- const tnymph = await nymph.startTransaction(transaction);
2831
- this.$setNymph(tnymph);
2832
-
2833
- try {
2834
- if (!(await this.$refresh())) {
2835
- throw new BadDataError('User could not be refreshed.');
2836
- }
2711
+ if (!(await this.$refresh())) {
2712
+ throw new BadDataError('User could not be refreshed.');
2713
+ }
2837
2714
 
2838
- if (this.$data.group != null) {
2839
- if (this.$data.group.$asleep()) {
2840
- await this.$data.group.$wake();
2841
- } else {
2842
- if (!(await this.$data.group.$refresh())) {
2843
- throw new BadDataError('Group could not be refreshed.');
2844
- }
2715
+ if (this.$data.group != null) {
2716
+ if (this.$data.group.$asleep()) {
2717
+ await this.$data.group.$wake();
2718
+ } else {
2719
+ if (!(await this.$data.group.$refresh())) {
2720
+ throw new BadDataError('Group could not be refreshed.');
2845
2721
  }
2846
2722
  }
2723
+ }
2847
2724
 
2848
- for (let callback of (this.constructor as typeof User)
2849
- .beforeDeleteCallbacks) {
2850
- if (callback) {
2851
- await callback(this);
2852
- }
2725
+ for (let callback of (this.constructor as typeof User)
2726
+ .beforeDeleteCallbacks) {
2727
+ if (callback) {
2728
+ await callback(this);
2853
2729
  }
2730
+ }
2854
2731
 
2855
- if (tilmeld.User.current(true).$is(this)) {
2856
- await this.$logout();
2857
- }
2732
+ if (tilmeld.User.current(true).$is(this)) {
2733
+ await this.$logout();
2734
+ }
2858
2735
 
2859
- if (this.$data.group != null && this.$is(this.$data.group.user)) {
2860
- // Read the skip ac value here, because super.$delete changes it.
2861
- const $skipAcWhenDeleting = this.$skipAcWhenDeleting;
2736
+ if (this.$data.group != null && this.$is(this.$data.group.user)) {
2737
+ // Read the skip ac value here, because super.$delete changes it.
2738
+ const $skipAcWhenDeleting = this.$skipAcWhenDeleting;
2862
2739
 
2863
- // Delete the user.
2864
- await super.$delete();
2865
- // Delete the generated group.
2866
- if ($skipAcWhenDeleting) {
2867
- await this.$data.group.$deleteSkipAC();
2868
- } else {
2869
- await this.$data.group.$delete();
2870
- }
2740
+ // Delete the user.
2741
+ await super.$delete();
2742
+ // Delete the generated group.
2743
+ if ($skipAcWhenDeleting) {
2744
+ await this.$data.group.$deleteSkipAC();
2871
2745
  } else {
2872
- await super.$delete();
2746
+ await this.$data.group.$delete();
2873
2747
  }
2874
- } catch (e: any) {
2875
- await tnymph.rollback(transaction);
2876
- this.$setNymph(nymph);
2877
- throw e;
2748
+ } else {
2749
+ await super.$delete();
2878
2750
  }
2879
2751
 
2880
- try {
2881
- for (let callback of (this.constructor as typeof User)
2882
- .afterDeleteCallbacks) {
2883
- if (callback) {
2884
- await callback(this);
2885
- }
2886
- }
2887
- } catch (e: any) {
2888
- try {
2889
- await tnymph.rollback(transaction);
2890
- } catch (e: any) {
2891
- nymph.config.debugError(
2892
- 'tilmeld',
2893
- `Rollback of transaction ${transaction} failed, reason: ${e.message}`,
2894
- );
2752
+ for (let callback of (this.constructor as typeof User)
2753
+ .afterDeleteCallbacks) {
2754
+ if (callback) {
2755
+ await callback(this);
2895
2756
  }
2896
- this.$setNymph(nymph);
2897
- throw e;
2898
- }
2899
-
2900
- let committed = false;
2901
- try {
2902
- committed = await tnymph.commit(transaction);
2903
- } catch (e: any) {
2904
- committed = false;
2905
- }
2906
- this.$setNymph(nymph);
2907
-
2908
- if (!committed) {
2909
- throw new MethodFailedError('Transaction could not be committed.');
2910
2757
  }
2911
2758
  }
2912
2759