@casual-simulation/aux-records 3.2.3 → 3.2.4-alpha.5940763320

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/AIChatInterface.d.ts +8 -0
  2. package/AIChatInterface.js.map +1 -1
  3. package/AIController.d.ts +10 -4
  4. package/AIController.js +112 -3
  5. package/AIController.js.map +1 -1
  6. package/AuthController.d.ts +4 -8
  7. package/AuthController.js +39 -45
  8. package/AuthController.js.map +1 -1
  9. package/AuthStore.d.ts +236 -4
  10. package/ConfigurationStore.d.ts +12 -0
  11. package/ConfigurationStore.js +2 -0
  12. package/ConfigurationStore.js.map +1 -0
  13. package/DataRecordsController.d.ts +18 -6
  14. package/DataRecordsController.js +31 -5
  15. package/DataRecordsController.js.map +1 -1
  16. package/Errors.d.ts +4 -0
  17. package/EventRecordsController.d.ts +12 -3
  18. package/EventRecordsController.js +27 -5
  19. package/EventRecordsController.js.map +1 -1
  20. package/FileRecordsController.d.ts +13 -3
  21. package/FileRecordsController.js +46 -9
  22. package/FileRecordsController.js.map +1 -1
  23. package/MemoryFileRecordsLookup.d.ts +11 -0
  24. package/MemoryFileRecordsLookup.js +128 -0
  25. package/MemoryFileRecordsLookup.js.map +1 -0
  26. package/MemoryStore.d.ts +191 -0
  27. package/MemoryStore.js +1381 -0
  28. package/MemoryStore.js.map +1 -0
  29. package/MetricsStore.d.ts +186 -0
  30. package/MetricsStore.js +2 -0
  31. package/MetricsStore.js.map +1 -0
  32. package/OpenAIChatInterface.js +1 -0
  33. package/OpenAIChatInterface.js.map +1 -1
  34. package/PolicyController.d.ts +7 -4
  35. package/PolicyController.js +33 -8
  36. package/PolicyController.js.map +1 -1
  37. package/RecordsController.d.ts +185 -7
  38. package/RecordsController.js +588 -70
  39. package/RecordsController.js.map +1 -1
  40. package/RecordsHttpServer.d.ts +8 -0
  41. package/RecordsHttpServer.js +450 -8
  42. package/RecordsHttpServer.js.map +1 -1
  43. package/RecordsStore.d.ts +263 -5
  44. package/StripeInterface.d.ts +331 -0
  45. package/StripeInterface.js +37 -1
  46. package/StripeInterface.js.map +1 -1
  47. package/SubscriptionConfiguration.d.ts +2523 -33
  48. package/SubscriptionConfiguration.js +130 -1
  49. package/SubscriptionConfiguration.js.map +1 -1
  50. package/SubscriptionController.d.ts +23 -6
  51. package/SubscriptionController.js +344 -69
  52. package/SubscriptionController.js.map +1 -1
  53. package/TestUtils.d.ts +7 -6
  54. package/TestUtils.js +28 -14
  55. package/TestUtils.js.map +1 -1
  56. package/Utils.js +9 -0
  57. package/Utils.js.map +1 -1
  58. package/index.d.ts +5 -4
  59. package/index.js +5 -4
  60. package/index.js.map +1 -1
  61. package/package.json +2 -2
  62. package/MemoryAuthStore.d.ts +0 -33
  63. package/MemoryAuthStore.js +0 -186
  64. package/MemoryAuthStore.js.map +0 -1
  65. package/MemoryDataRecordsStore.d.ts +0 -10
  66. package/MemoryDataRecordsStore.js +0 -98
  67. package/MemoryDataRecordsStore.js.map +0 -1
  68. package/MemoryEventRecordsStore.d.ts +0 -10
  69. package/MemoryEventRecordsStore.js +0 -89
  70. package/MemoryEventRecordsStore.js.map +0 -1
  71. package/MemoryFileRecordsStore.d.ts +0 -25
  72. package/MemoryFileRecordsStore.js +0 -310
  73. package/MemoryFileRecordsStore.js.map +0 -1
  74. package/MemoryPolicyStore.d.ts +0 -43
  75. package/MemoryPolicyStore.js +0 -255
  76. package/MemoryPolicyStore.js.map +0 -1
  77. package/MemoryRecordsStore.d.ts +0 -13
  78. package/MemoryRecordsStore.js +0 -67
  79. package/MemoryRecordsStore.js.map +0 -1
@@ -11,13 +11,162 @@ import { toBase64String, fromBase64String } from './Utils';
11
11
  import { hashHighEntropyPasswordWithSalt, hashPasswordWithSalt, } from '@casual-simulation/crypto';
12
12
  import { randomBytes } from 'tweetnacl';
13
13
  import { fromByteArray } from 'base64-js';
14
+ import { v4 as uuid } from 'uuid';
15
+ import { getSubscriptionFeatures } from './SubscriptionConfiguration';
14
16
  /**
15
17
  * Defines a class that manages records and their keys.
16
18
  */
17
19
  export class RecordsController {
18
- constructor(store, auth) {
19
- this._store = store;
20
- this._auth = auth;
20
+ constructor(config) {
21
+ this._store = config.store;
22
+ this._auth = config.auth;
23
+ this._metrics = config.metrics;
24
+ this._config = config.config;
25
+ }
26
+ /**
27
+ * Creates a new record.
28
+ * @param request The request that should be used to create the record.
29
+ */
30
+ createRecord(request) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ try {
33
+ if (!request.userId) {
34
+ return {
35
+ success: false,
36
+ errorCode: 'not_logged_in',
37
+ errorMessage: 'The user must be logged in in order to create a record.',
38
+ };
39
+ }
40
+ const record = yield this._store.getRecordByName(request.recordName);
41
+ if (record) {
42
+ if (record.name === request.userId &&
43
+ record.ownerId !== request.userId &&
44
+ request.ownerId === request.userId) {
45
+ const allowed = yield this._doesSubscriptionAllowToCreateRecord({
46
+ ownerId: request.userId,
47
+ });
48
+ if (!allowed.success) {
49
+ return allowed;
50
+ }
51
+ console.log(`[RecordsController] [action: record.create recordName: ${record.name}, userId: ${request.userId}] Fixing record owner to match actual owner.`);
52
+ record.ownerId = request.userId;
53
+ record.studioId = null;
54
+ // Clear the hashes and re-create the salt so that access to the record is revoked for any record key that was created before.
55
+ record.secretHashes = [];
56
+ record.secretSalt = this._createSalt();
57
+ yield this._store.updateRecord(Object.assign({}, record));
58
+ return {
59
+ success: true,
60
+ };
61
+ }
62
+ let existingStudioMembers = yield this._store.listStudioAssignments(record.name);
63
+ if (existingStudioMembers.length > 0 &&
64
+ record.studioId !== record.name &&
65
+ request.studioId === record.name) {
66
+ const allowed = yield this._doesSubscriptionAllowToCreateRecord({
67
+ studioId: request.studioId,
68
+ });
69
+ if (!allowed.success) {
70
+ return allowed;
71
+ }
72
+ console.log(`[RecordsController] [action: record.create recordName: ${record.name}, userId: ${request.userId}, studioId: ${request.studioId}] Fixing record owner to match actual owner.`);
73
+ record.ownerId = null;
74
+ record.studioId = request.studioId;
75
+ // Clear the hashes and re-create the salt so that access to the record is revoked for any record key that was created before.
76
+ record.secretHashes = [];
77
+ record.secretSalt = this._createSalt();
78
+ yield this._store.updateRecord(Object.assign({}, record));
79
+ return {
80
+ success: true,
81
+ };
82
+ }
83
+ return {
84
+ success: false,
85
+ errorCode: 'record_already_exists',
86
+ errorMessage: 'A record with that name already exists.',
87
+ };
88
+ }
89
+ if (request.recordName !== request.ownerId &&
90
+ request.recordName !== request.studioId) {
91
+ const existingUser = yield this._auth.findUser(request.recordName);
92
+ if (existingUser) {
93
+ return {
94
+ success: false,
95
+ errorCode: 'record_already_exists',
96
+ errorMessage: 'A record with that name already exists.',
97
+ };
98
+ }
99
+ }
100
+ if (!request.ownerId && !request.studioId) {
101
+ return {
102
+ success: false,
103
+ errorCode: 'unacceptable_request',
104
+ errorMessage: 'You must provide an owner ID or a studio ID.',
105
+ };
106
+ }
107
+ if (request.ownerId) {
108
+ if (request.ownerId !== request.userId) {
109
+ return {
110
+ success: false,
111
+ errorCode: 'not_authorized',
112
+ errorMessage: 'You are not authorized to create a record for another user.',
113
+ };
114
+ }
115
+ const allowed = yield this._doesSubscriptionAllowToCreateRecord({
116
+ ownerId: request.userId,
117
+ });
118
+ if (!allowed.success) {
119
+ return allowed;
120
+ }
121
+ console.log(`[RecordsController] [action: record.create recordName: ${request.recordName}, userId: ${request.userId}, ownerId: ${request.ownerId}] Creating record.`);
122
+ yield this._store.addRecord({
123
+ name: request.recordName,
124
+ ownerId: request.ownerId,
125
+ secretHashes: [],
126
+ secretSalt: this._createSalt(),
127
+ studioId: null,
128
+ });
129
+ }
130
+ else {
131
+ const assignments = yield this._store.listStudioAssignments(request.studioId, {
132
+ userId: request.userId,
133
+ role: 'admin',
134
+ });
135
+ if (assignments.length <= 0) {
136
+ return {
137
+ success: false,
138
+ errorCode: 'not_authorized',
139
+ errorMessage: 'You are not authorized to create a record for this studio.',
140
+ };
141
+ }
142
+ const allowed = yield this._doesSubscriptionAllowToCreateRecord({
143
+ studioId: request.studioId,
144
+ });
145
+ if (!allowed.success) {
146
+ return allowed;
147
+ }
148
+ console.log(`[RecordsController] [action: record.create recordName: ${request.recordName}, userId: ${request.userId}, studioId: ${request.studioId}] Creating record.`);
149
+ yield this._store.addRecord({
150
+ name: request.recordName,
151
+ ownerId: null,
152
+ secretHashes: [],
153
+ secretSalt: this._createSalt(),
154
+ studioId: request.studioId,
155
+ });
156
+ }
157
+ return {
158
+ success: true,
159
+ };
160
+ }
161
+ catch (err) {
162
+ console.error('[RecordsController] [createRecord] An error occurred while creating a record:', err);
163
+ return {
164
+ success: false,
165
+ errorCode: 'server_error',
166
+ errorMessage: 'A server error occurred.',
167
+ };
168
+ }
169
+ });
21
170
  }
22
171
  /**
23
172
  * Creates a new public record key for the given bucket name.
@@ -27,6 +176,7 @@ export class RecordsController {
27
176
  * @returns
28
177
  */
29
178
  createPublicRecordKey(name, policy, userId) {
179
+ var _a;
30
180
  return __awaiter(this, void 0, void 0, function* () {
31
181
  try {
32
182
  if (!userId) {
@@ -37,7 +187,6 @@ export class RecordsController {
37
187
  errorReason: 'not_logged_in',
38
188
  };
39
189
  }
40
- const record = yield this._store.getRecordByName(name);
41
190
  if (!!policy &&
42
191
  policy !== 'subjectfull' &&
43
192
  policy !== 'subjectless') {
@@ -48,8 +197,32 @@ export class RecordsController {
48
197
  errorReason: 'invalid_policy',
49
198
  };
50
199
  }
51
- if (record) {
52
- if (record.ownerId !== userId && name !== userId) {
200
+ let existingStudioMembers = yield this._store.listStudioAssignments(name);
201
+ let createResult;
202
+ // recordName matches studioId
203
+ if (existingStudioMembers.length > 0) {
204
+ createResult = yield this.createRecord({
205
+ recordName: name,
206
+ userId: userId,
207
+ studioId: name,
208
+ });
209
+ }
210
+ else {
211
+ createResult = yield this.createRecord({
212
+ recordName: name,
213
+ userId: userId,
214
+ ownerId: userId,
215
+ });
216
+ }
217
+ if (createResult.success === false) {
218
+ if (createResult.errorCode !== 'record_already_exists') {
219
+ return Object.assign(Object.assign({}, createResult), { errorReason: 'not_authorized' });
220
+ }
221
+ }
222
+ const record = yield this._store.getRecordByName(name);
223
+ if (!record) {
224
+ if (createResult.success === false &&
225
+ createResult.errorCode === 'record_already_exists') {
53
226
  return {
54
227
  success: false,
55
228
  errorCode: 'unauthorized_to_create_record_key',
@@ -57,76 +230,70 @@ export class RecordsController {
57
230
  errorReason: 'record_owned_by_different_user',
58
231
  };
59
232
  }
60
- console.log(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Creating record key.`);
61
- if (name === userId) {
62
- // The user is not currently the owner of their own record.
63
- // This is an issue that needs to be fixed because users should always own the record that has the same name as their ID.
64
- console.log(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Fixing record owner to match actual owner.`);
65
- record.ownerId = userId;
66
- // Clear the hashes and re-create the salt so that access to the record is revoked for any record key that was created before.
67
- record.secretHashes = [];
68
- record.secretSalt = this._createSalt();
69
- yield this._store.updateRecord(Object.assign({}, record));
70
- }
71
- const passwordBytes = randomBytes(16);
72
- const password = fromByteArray(passwordBytes); // convert to human-readable string
73
- const salt = record.secretSalt;
74
- const passwordHash = hashHighEntropyPasswordWithSalt(password, salt);
75
- yield this._store.addRecordKey({
76
- recordName: name,
77
- secretHash: passwordHash,
78
- policy: policy !== null && policy !== void 0 ? policy : DEFAULT_RECORD_KEY_POLICY,
79
- creatorId: record.ownerId,
80
- });
233
+ console.error(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Unable to find record that was just created!`);
81
234
  return {
82
- success: true,
83
- recordKey: formatV2RecordKey(name, password, policy),
84
- recordName: name,
235
+ success: false,
236
+ errorCode: 'server_error',
237
+ errorMessage: 'A server error occurred.',
238
+ errorReason: 'server_error',
85
239
  };
86
240
  }
87
- else {
88
- if (name !== userId) {
89
- const user = yield this._auth.findUser(name);
90
- if (user) {
91
- // User exists for record. They should own the record and all record keys for it.
92
- return {
93
- success: false,
94
- errorCode: 'unauthorized_to_create_record_key',
95
- errorMessage: 'Another user has already created this record.',
96
- errorReason: 'record_owned_by_different_user',
97
- };
98
- }
241
+ if (record.ownerId) {
242
+ if (existingStudioMembers.length > 0) {
243
+ console.error(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Studio members exist for the record, but the record is owned by a user!`);
244
+ return {
245
+ success: false,
246
+ errorCode: 'server_error',
247
+ errorMessage: 'A server error occurred.',
248
+ errorReason: 'server_error',
249
+ };
99
250
  }
100
- console.log(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Creating record.`);
101
- const passwordBytes = randomBytes(16);
102
- const password = fromByteArray(passwordBytes); // convert to human-readable string
103
- const salt = this._createSalt();
104
- const passwordHash = hashHighEntropyPasswordWithSalt(password, salt);
105
- yield this._store.addRecord({
106
- name,
107
- ownerId: userId,
108
- secretHashes: [],
109
- secretSalt: salt,
110
- });
111
- yield this._store.addRecordKey({
112
- recordName: name,
113
- secretHash: passwordHash,
114
- policy: policy !== null && policy !== void 0 ? policy : DEFAULT_RECORD_KEY_POLICY,
115
- creatorId: userId,
251
+ if (record.ownerId !== userId) {
252
+ return {
253
+ success: false,
254
+ errorCode: 'unauthorized_to_create_record_key',
255
+ errorMessage: 'Another user has already created this record.',
256
+ errorReason: 'record_owned_by_different_user',
257
+ };
258
+ }
259
+ }
260
+ else if (record.studioId) {
261
+ let existingStudioMembers = yield this._store.listStudioAssignments(record.studioId, {
262
+ userId: userId,
263
+ role: 'admin',
116
264
  });
117
- return {
118
- success: true,
119
- recordKey: formatV2RecordKey(name, password, policy),
120
- recordName: name,
121
- };
265
+ if (existingStudioMembers.length <= 0) {
266
+ return {
267
+ success: false,
268
+ errorCode: 'unauthorized_to_create_record_key',
269
+ errorMessage: 'You are not authorized to create a record key for this record.',
270
+ errorReason: 'record_owned_by_different_user',
271
+ };
272
+ }
122
273
  }
274
+ console.log(`[RecordsController] [action: recordKey.create recordName: ${name}, userId: ${userId}] Creating record key.`);
275
+ const passwordBytes = randomBytes(16);
276
+ const password = fromByteArray(passwordBytes); // convert to human-readable string
277
+ const salt = record.secretSalt;
278
+ const passwordHash = hashHighEntropyPasswordWithSalt(password, salt);
279
+ yield this._store.addRecordKey({
280
+ recordName: name,
281
+ secretHash: passwordHash,
282
+ policy: policy !== null && policy !== void 0 ? policy : DEFAULT_RECORD_KEY_POLICY,
283
+ creatorId: (_a = record.ownerId) !== null && _a !== void 0 ? _a : userId,
284
+ });
285
+ return {
286
+ success: true,
287
+ recordKey: formatV2RecordKey(name, password, policy),
288
+ recordName: name,
289
+ };
123
290
  }
124
291
  catch (err) {
125
- console.error(err);
292
+ console.error('[RecordsController] [createPublicRecordKey] An error occurred while creating a public record key:', err);
126
293
  return {
127
294
  success: false,
128
295
  errorCode: 'server_error',
129
- errorMessage: err.toString(),
296
+ errorMessage: 'A server error occurred.',
130
297
  errorReason: 'server_error',
131
298
  };
132
299
  }
@@ -234,11 +401,11 @@ export class RecordsController {
234
401
  }
235
402
  }
236
403
  catch (err) {
237
- console.error(err);
404
+ console.error('[RecordsController] [validatePublicRecordKey] An error occurred while creating a public record key:', err);
238
405
  return {
239
406
  success: false,
240
407
  errorCode: 'server_error',
241
- errorMessage: err.toString(),
408
+ errorMessage: 'A server error occurred.',
242
409
  };
243
410
  }
244
411
  });
@@ -254,10 +421,17 @@ export class RecordsController {
254
421
  const record = yield this._store.getRecordByName(name);
255
422
  if (!record) {
256
423
  if (userId && name === userId) {
424
+ const allowed = yield this._doesSubscriptionAllowToCreateRecord({
425
+ ownerId: userId,
426
+ });
427
+ if (allowed.success === false) {
428
+ return allowed;
429
+ }
257
430
  console.log(`[RecordsController] [validateRecordName recordName: ${name}, userId: ${userId}] Creating record for user.`);
258
431
  yield this._store.addRecord({
259
432
  name,
260
433
  ownerId: userId,
434
+ studioId: null,
261
435
  secretHashes: [],
262
436
  secretSalt: this._createSalt(),
263
437
  });
@@ -265,6 +439,31 @@ export class RecordsController {
265
439
  success: true,
266
440
  recordName: name,
267
441
  ownerId: userId,
442
+ studioId: null,
443
+ };
444
+ }
445
+ let studioMembers = yield this._store.listStudioAssignments(name);
446
+ if (studioMembers.length > 0) {
447
+ const allowed = yield this._doesSubscriptionAllowToCreateRecord({
448
+ studioId: name,
449
+ });
450
+ if (allowed.success === false) {
451
+ return allowed;
452
+ }
453
+ console.log(`[RecordsController] [validateRecordName recordName: ${name}, userId: ${userId}, studioId: ${name}] Creating record for studio.`);
454
+ yield this._store.addRecord({
455
+ name,
456
+ ownerId: null,
457
+ studioId: name,
458
+ secretHashes: [],
459
+ secretSalt: this._createSalt(),
460
+ });
461
+ return {
462
+ success: true,
463
+ recordName: name,
464
+ ownerId: null,
465
+ studioId: name,
466
+ studioMembers,
268
467
  };
269
468
  }
270
469
  return {
@@ -276,6 +475,12 @@ export class RecordsController {
276
475
  else if (userId &&
277
476
  record.name === userId &&
278
477
  record.ownerId !== userId) {
478
+ const allowed = yield this._doesSubscriptionAllowToCreateRecord({
479
+ ownerId: userId,
480
+ });
481
+ if (allowed.success === false) {
482
+ return allowed;
483
+ }
279
484
  // The user is not currently the owner of their own record.
280
485
  // This is an issue that needs to be fixed because users should always own the record that has the same name as their ID.
281
486
  console.log(`[RecordsController] [validateRecordName recordName: ${name}, userId: ${userId}] Fixing record owner to match actual owner.`);
@@ -285,24 +490,85 @@ export class RecordsController {
285
490
  record.secretSalt = this._createSalt();
286
491
  yield this._store.updateRecord(Object.assign({}, record));
287
492
  }
493
+ let existingStudioMembers = yield this._store.listStudioAssignments(name);
494
+ if (existingStudioMembers.length > 0 &&
495
+ record.studioId !== name &&
496
+ record.ownerId !== null) {
497
+ const allowed = yield this._doesSubscriptionAllowToCreateRecord({
498
+ studioId: name,
499
+ });
500
+ if (allowed.success === false) {
501
+ return allowed;
502
+ }
503
+ console.log(`[RecordsController] [validateRecordName recordName: ${name}, userId: ${userId}, studioId: ${name}] Fixing record studio to match actual studio.`);
504
+ record.ownerId = null;
505
+ record.studioId = name;
506
+ record.secretHashes = [];
507
+ record.secretSalt = this._createSalt();
508
+ yield this._store.updateRecord(Object.assign({}, record));
509
+ }
510
+ let studioMembers = undefined;
511
+ if (existingStudioMembers.length > 0) {
512
+ studioMembers = existingStudioMembers;
513
+ }
514
+ else if (record.studioId) {
515
+ studioMembers = yield this._store.listStudioAssignments(record.studioId);
516
+ }
288
517
  return {
289
518
  success: true,
290
519
  recordName: name,
291
520
  ownerId: record.ownerId,
521
+ studioId: record.studioId,
522
+ studioMembers,
292
523
  };
293
524
  }
294
525
  catch (err) {
295
- console.error(err);
526
+ console.error('[RecordsController] [validateRecordName] An error occurred while creating a public record key:', err);
296
527
  return {
297
528
  success: false,
298
529
  errorCode: 'server_error',
299
- errorMessage: err.toString(),
530
+ errorMessage: 'A server error occurred.',
300
531
  };
301
532
  }
302
533
  });
303
534
  }
535
+ _doesSubscriptionAllowToCreateRecord(filter) {
536
+ return __awaiter(this, void 0, void 0, function* () {
537
+ const { features, metrics } = yield this._getSubscriptionFeatures(filter);
538
+ if (!features.records.allowed) {
539
+ return {
540
+ success: false,
541
+ errorCode: 'not_authorized',
542
+ errorMessage: 'Records are not allowed for this subscription.',
543
+ };
544
+ }
545
+ else if (features.records.maxRecords >= 0) {
546
+ if (features.records.maxRecords <= metrics.totalRecords + 1) {
547
+ return {
548
+ success: false,
549
+ errorCode: 'subscription_limit_reached',
550
+ errorMessage: 'This subscription has hit its record limit.',
551
+ };
552
+ }
553
+ }
554
+ return {
555
+ success: true,
556
+ };
557
+ });
558
+ }
559
+ _getSubscriptionFeatures(filter) {
560
+ return __awaiter(this, void 0, void 0, function* () {
561
+ const metrics = yield this._metrics.getSubscriptionRecordMetrics(filter);
562
+ const config = yield this._config.getSubscriptionConfiguration();
563
+ return {
564
+ metrics,
565
+ config,
566
+ features: getSubscriptionFeatures(config, metrics.subscriptionStatus, metrics.subscriptionId, metrics.ownerId ? 'user' : 'studio'),
567
+ };
568
+ });
569
+ }
304
570
  /**
305
- * Gets the list of records that the user with the given ID owns.
571
+ * Gets the list of records that the user with the given ID has access to.
306
572
  * @param userId The ID of the user.
307
573
  */
308
574
  listRecords(userId) {
@@ -331,6 +597,258 @@ export class RecordsController {
331
597
  }
332
598
  });
333
599
  }
600
+ /**
601
+ * Gets the list of records in the given studio that the user with the given ID has access to.
602
+ * @param studioId The ID of the studio.
603
+ * @param userId The ID of the user that is currently logged in.
604
+ */
605
+ listStudioRecords(studioId, userId) {
606
+ return __awaiter(this, void 0, void 0, function* () {
607
+ try {
608
+ if (!this._store.listRecordsByStudioIdAndUserId) {
609
+ return {
610
+ success: false,
611
+ errorCode: 'not_supported',
612
+ errorMessage: 'This operation is not supported.',
613
+ };
614
+ }
615
+ const records = yield this._store.listRecordsByStudioIdAndUserId(studioId, userId);
616
+ return {
617
+ success: true,
618
+ records: records,
619
+ };
620
+ }
621
+ catch (err) {
622
+ console.log('[RecordsController] [listStudioRecords] Error listing records: ', err);
623
+ return {
624
+ success: false,
625
+ errorCode: 'server_error',
626
+ errorMessage: 'A server error occurred.',
627
+ };
628
+ }
629
+ });
630
+ }
631
+ /**
632
+ * Attempts to create a new studio. That is, an entity that can be used to group records.
633
+ * @param studioName The name of the studio.
634
+ * @param userId The ID of the user that is creating the studio.
635
+ */
636
+ createStudio(studioName, userId) {
637
+ return __awaiter(this, void 0, void 0, function* () {
638
+ try {
639
+ const studioId = uuid();
640
+ yield this._store.createStudioForUser({
641
+ id: studioId,
642
+ displayName: studioName,
643
+ }, userId);
644
+ return {
645
+ success: true,
646
+ studioId: studioId,
647
+ };
648
+ }
649
+ catch (err) {
650
+ console.error('[RecordsController] [createStudio] An error occurred while creating a studio:', err);
651
+ return {
652
+ success: false,
653
+ errorCode: 'server_error',
654
+ errorMessage: 'A server error occurred.',
655
+ };
656
+ }
657
+ });
658
+ }
659
+ /**
660
+ * Gets the list of studios that the user with the given ID has access to.
661
+ * @param userId The ID of the user.
662
+ */
663
+ listStudios(userId) {
664
+ return __awaiter(this, void 0, void 0, function* () {
665
+ try {
666
+ const studios = yield this._store.listStudiosForUser(userId);
667
+ return {
668
+ success: true,
669
+ studios: studios.map((s) => ({
670
+ studioId: s.studioId,
671
+ displayName: s.displayName,
672
+ role: s.role,
673
+ isPrimaryContact: s.isPrimaryContact,
674
+ })),
675
+ };
676
+ }
677
+ catch (err) {
678
+ console.error('[RecordsController] [listStudios] An error occurred while listing studios:', err);
679
+ return {
680
+ success: false,
681
+ errorCode: 'server_error',
682
+ errorMessage: 'A server error occurred.',
683
+ };
684
+ }
685
+ });
686
+ }
687
+ /**
688
+ * Gets the list of members in a studio.
689
+ * @param studioId The ID of the studio.
690
+ * @param userId The ID of the user that is currently logged in.
691
+ */
692
+ listStudioMembers(studioId, userId) {
693
+ return __awaiter(this, void 0, void 0, function* () {
694
+ try {
695
+ const members = yield this._store.listStudioAssignments(studioId);
696
+ const userAssignment = members.find((m) => m.userId === userId);
697
+ if (!userAssignment) {
698
+ return {
699
+ success: false,
700
+ errorCode: 'not_authorized',
701
+ errorMessage: 'You are not authorized to access this studio.',
702
+ };
703
+ }
704
+ if (userAssignment.role === 'admin') {
705
+ return {
706
+ success: true,
707
+ members: members.map((m) => ({
708
+ studioId: m.studioId,
709
+ userId: m.userId,
710
+ isPrimaryContact: m.isPrimaryContact,
711
+ role: m.role,
712
+ user: {
713
+ id: m.user.id,
714
+ name: m.user.name,
715
+ email: m.user.email,
716
+ phoneNumber: m.user.phoneNumber,
717
+ },
718
+ })),
719
+ };
720
+ }
721
+ return {
722
+ success: true,
723
+ members: members.map((m) => ({
724
+ studioId: m.studioId,
725
+ userId: m.userId,
726
+ isPrimaryContact: m.isPrimaryContact,
727
+ role: m.role,
728
+ user: {
729
+ id: m.user.id,
730
+ name: m.user.name,
731
+ },
732
+ })),
733
+ };
734
+ }
735
+ catch (err) {
736
+ console.error('[RecordsController] [listStudioMembers] An error occurred while listing studio members:', err);
737
+ return {
738
+ success: false,
739
+ errorCode: 'server_error',
740
+ errorMessage: 'A server error occurred.',
741
+ };
742
+ }
743
+ });
744
+ }
745
+ addStudioMember(request) {
746
+ var _a;
747
+ return __awaiter(this, void 0, void 0, function* () {
748
+ try {
749
+ if (!request.userId) {
750
+ return {
751
+ success: false,
752
+ errorCode: 'not_logged_in',
753
+ errorMessage: 'You must be logged in to add a studio member.',
754
+ };
755
+ }
756
+ const list = yield this._store.listStudioAssignments(request.studioId, {
757
+ userId: request.userId,
758
+ role: 'admin',
759
+ });
760
+ if (list.length <= 0) {
761
+ return {
762
+ success: false,
763
+ errorCode: 'not_authorized',
764
+ errorMessage: 'You are not authorized to perform this operation.',
765
+ };
766
+ }
767
+ let addedUserId = null;
768
+ if (request.addedUserId) {
769
+ addedUserId = request.addedUserId;
770
+ }
771
+ else if (request.addedEmail || request.addedPhoneNumber) {
772
+ const addedUser = yield this._auth.findUserByAddress((_a = request.addedEmail) !== null && _a !== void 0 ? _a : request.addedPhoneNumber, request.addedEmail ? 'email' : 'phone');
773
+ if (!addedUser) {
774
+ return {
775
+ success: false,
776
+ errorCode: 'user_not_found',
777
+ errorMessage: 'The user was not able to be found.',
778
+ };
779
+ }
780
+ addedUserId = addedUser.id;
781
+ }
782
+ else {
783
+ return {
784
+ success: false,
785
+ errorCode: 'unacceptable_request',
786
+ errorMessage: 'You must provide an email, phone number, or user ID to add a studio member.',
787
+ };
788
+ }
789
+ yield this._store.addStudioAssignment({
790
+ studioId: request.studioId,
791
+ userId: addedUserId,
792
+ isPrimaryContact: false,
793
+ role: request.role,
794
+ });
795
+ return {
796
+ success: true,
797
+ };
798
+ }
799
+ catch (err) {
800
+ console.error('[RecordsController] [addStudioMember] An error occurred while adding a studio member:', err);
801
+ return {
802
+ success: false,
803
+ errorCode: 'server_error',
804
+ errorMessage: 'A server error occurred.',
805
+ };
806
+ }
807
+ });
808
+ }
809
+ removeStudioMember(request) {
810
+ return __awaiter(this, void 0, void 0, function* () {
811
+ try {
812
+ if (!request.userId) {
813
+ return {
814
+ success: false,
815
+ errorCode: 'not_logged_in',
816
+ errorMessage: 'You must be logged in to remove a studio member.',
817
+ };
818
+ }
819
+ if (request.userId === request.removedUserId) {
820
+ return {
821
+ success: false,
822
+ errorCode: 'not_authorized',
823
+ errorMessage: 'You are not authorized to perform this operation.',
824
+ };
825
+ }
826
+ const list = yield this._store.listStudioAssignments(request.studioId, {
827
+ userId: request.userId,
828
+ role: 'admin',
829
+ });
830
+ if (list.length <= 0) {
831
+ return {
832
+ success: false,
833
+ errorCode: 'not_authorized',
834
+ errorMessage: 'You are not authorized to perform this operation.',
835
+ };
836
+ }
837
+ yield this._store.removeStudioAssignment(request.studioId, request.removedUserId);
838
+ return {
839
+ success: true,
840
+ };
841
+ }
842
+ catch (err) {
843
+ console.error('[RecordsController] [removeStudioMember] An error occurred while removing a studio member:', err);
844
+ return {
845
+ success: false,
846
+ errorCode: 'server_error',
847
+ errorMessage: 'A server error occurred.',
848
+ };
849
+ }
850
+ });
851
+ }
334
852
  _createSalt() {
335
853
  return fromByteArray(randomBytes(16));
336
854
  }