@vuu-ui/vuu-data-test 4.0.0-alpha.2 → 4.0.0-alpha.20

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 (51) hide show
  1. package/README.md +8 -0
  2. package/package.json +11 -9
  3. package/src/Table.js +5 -5
  4. package/src/core/filter/PermissionFilter.js +60 -0
  5. package/src/core/filter/PermissionFilteredTable.js +59 -0
  6. package/src/core/module/ModuleContainer.js +8 -0
  7. package/src/core/module/VuuModule.js +54 -6
  8. package/src/core/table/TableContainer.js +1 -1
  9. package/src/core/user/CurrentUser.js +13 -0
  10. package/src/index.js +7 -0
  11. package/src/local-datasource-provider/LocalDatasourceProvider.js +53 -43
  12. package/src/module-admin/ModuleAdminModule.js +124 -0
  13. package/src/module-admin/index.js +3 -0
  14. package/src/module-admin/module-admin-schemas.js +107 -0
  15. package/src/module-admin/snapshot-projection.js +29 -0
  16. package/src/notifications/NotificationModule.js +93 -0
  17. package/src/notifications/NotificationsSchema.js +52 -0
  18. package/src/notifications/index.js +6 -0
  19. package/src/notifications/simulated/DismissNotificationRpcHandler.js +36 -0
  20. package/src/notifications/simulated/SimulatedNotificationsModule.js +14 -0
  21. package/src/notifications/simulated/SimulatedNotificationsProvider.js +151 -0
  22. package/src/notifications/simulated/notificationsModule.js +3 -0
  23. package/src/schemas.js +8 -2
  24. package/src/user-admin/UserAdminModule.js +567 -0
  25. package/src/user-admin/index.js +3 -0
  26. package/src/user-admin/initialSnapshot.js +461 -0
  27. package/src/user-admin/snapshot-projection.js +151 -0
  28. package/types/core/filter/PermissionFilter.d.ts +41 -0
  29. package/types/core/filter/PermissionFilteredTable.d.ts +24 -0
  30. package/types/core/module/ModuleContainer.d.ts +6 -3
  31. package/types/core/module/VuuModule.d.ts +22 -0
  32. package/types/core/table/TableContainer.d.ts +5 -5
  33. package/types/core/user/CurrentUser.d.ts +14 -0
  34. package/types/index.d.ts +7 -0
  35. package/types/local-datasource-provider/LocalDatasourceProvider.d.ts +15 -1
  36. package/types/module-admin/ModuleAdminModule.d.ts +32 -0
  37. package/types/module-admin/index.d.ts +4 -0
  38. package/types/module-admin/module-admin-schemas.d.ts +4 -0
  39. package/types/module-admin/snapshot-projection.d.ts +13 -0
  40. package/types/notifications/NotificationModule.d.ts +39 -0
  41. package/types/notifications/NotificationsSchema.d.ts +20 -0
  42. package/types/notifications/index.d.ts +6 -0
  43. package/types/notifications/simulated/DismissNotificationRpcHandler.d.ts +8 -0
  44. package/types/notifications/simulated/SimulatedNotificationsModule.d.ts +2 -0
  45. package/types/notifications/simulated/SimulatedNotificationsProvider.d.ts +21 -0
  46. package/types/notifications/simulated/notificationsModule.d.ts +5 -0
  47. package/types/schemas.d.ts +5 -3
  48. package/types/user-admin/UserAdminModule.d.ts +54 -0
  49. package/types/user-admin/index.d.ts +3 -0
  50. package/types/user-admin/initialSnapshot.d.ts +2 -0
  51. package/types/user-admin/snapshot-projection.d.ts +9 -0
@@ -0,0 +1,567 @@
1
+ import { USER_ADMIN_RPC_CONTRACT, USER_ADMIN_TABLE_SCHEMAS } from "@heswell/user-admin/contracts";
2
+ import { InMemoryUserAdminStore } from "@heswell/user-admin/in-memory";
3
+ import { VuuModule } from "../core/module/VuuModule.js";
4
+ import TableContainer from "../core/table/TableContainer.js";
5
+ import { Table, buildDataColumnMapFromSchema } from "../Table.js";
6
+ import { USER_ADMIN_INITIAL_SNAPSHOT } from "./initialSnapshot.js";
7
+ import { reconcileUserAdminTables, resolveDisplayName } from "./snapshot-projection.js";
8
+ const createTables = ()=>({
9
+ clients: TableContainer.createTable(USER_ADMIN_TABLE_SCHEMAS.clients),
10
+ group_roles: TableContainer.createTable(USER_ADMIN_TABLE_SCHEMAS.group_roles),
11
+ groups: TableContainer.createTable(USER_ADMIN_TABLE_SCHEMAS.groups),
12
+ roles: TableContainer.createTable(USER_ADMIN_TABLE_SCHEMAS.roles),
13
+ user_group_roles: TableContainer.createTable(USER_ADMIN_TABLE_SCHEMAS.user_group_roles),
14
+ user_groups: TableContainer.createTable(USER_ADMIN_TABLE_SCHEMAS.user_groups),
15
+ users: TableContainer.createTable(USER_ADMIN_TABLE_SCHEMAS.users)
16
+ });
17
+ const errorResult = (error)=>({
18
+ errorMessage: error instanceof Error ? error.message : "User admin request failed",
19
+ type: "ERROR_RESULT"
20
+ });
21
+ const isRecord = (value)=>"object" == typeof value && null !== value && !Array.isArray(value);
22
+ const readParams = (request, rpcName)=>{
23
+ if ("RPC_REQUEST" !== request.type || request.rpcName !== rpcName) throw new Error(`Expected ${rpcName} RPC request`);
24
+ if (!isRecord(request.params)) throw new Error(`${rpcName} requires named parameters`);
25
+ const allowed = new Set(USER_ADMIN_RPC_CONTRACT[rpcName]);
26
+ for (const name of Object.keys(request.params))if (!allowed.has(name)) throw new Error(`${rpcName} received an unsupported parameter: ${name}`);
27
+ return request.params;
28
+ };
29
+ const requiredString = (params, name)=>{
30
+ const value = params[name];
31
+ if ("string" != typeof value || 0 === value.length) throw new Error(`${name} must be a non-empty string`);
32
+ return value;
33
+ };
34
+ const optionalString = (params, name)=>{
35
+ const value = params[name];
36
+ if (void 0 !== value && "string" != typeof value) throw new Error(`${name} must be a string`);
37
+ return value;
38
+ };
39
+ const optionalBoolean = (params, name)=>{
40
+ const value = params[name];
41
+ if (void 0 !== value && "boolean" != typeof value) throw new Error(`${name} must be a boolean`);
42
+ return value;
43
+ };
44
+ const optionalStringArray = (params, name)=>{
45
+ const value = params[name];
46
+ if (void 0 !== value && (!Array.isArray(value) || value.some((item)=>"string" != typeof item))) throw new Error(`${name} must be an array of strings`);
47
+ return value;
48
+ };
49
+ const reference = (params, idName, name)=>{
50
+ const id = optionalString(params, idName);
51
+ const label = optionalString(params, name);
52
+ if (!id && !label) throw new Error(`${idName} or ${name} is required`);
53
+ return {
54
+ id,
55
+ label
56
+ };
57
+ };
58
+ const parseAssignments = (value)=>{
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(value);
62
+ } catch {
63
+ throw new Error("assignments must be valid JSON");
64
+ }
65
+ if (!Array.isArray(parsed) || parsed.some((assignment)=>!isRecord(assignment) || "string" != typeof assignment.accessRole || 0 === assignment.accessRole.length || "string" != typeof assignment.groupId || 0 === assignment.groupId.length || Object.keys(assignment).some((name)=>"accessRole" !== name && "groupId" !== name))) throw new Error("assignments must be a JSON array of accessRole and groupId strings");
66
+ return parsed;
67
+ };
68
+ const parseSessionRoleAssignments = (value)=>{
69
+ if ("string" != typeof value) throw new Error("role_assignments must be a serialized array");
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(value);
73
+ } catch {
74
+ throw new Error("role_assignments must be a valid JSON array");
75
+ }
76
+ if (!Array.isArray(parsed) || parsed.some((roleId)=>"string" != typeof roleId || !roleId)) throw new Error("role_assignments must be an array of role ID strings");
77
+ return [
78
+ ...new Set(parsed)
79
+ ];
80
+ };
81
+ const parseSessionPermissions = (value)=>{
82
+ if ("string" != typeof value) throw new Error("permissions must be a serialized array");
83
+ let parsed;
84
+ try {
85
+ parsed = JSON.parse(value);
86
+ } catch {
87
+ throw new Error("permissions must be valid JSON");
88
+ }
89
+ if (!Array.isArray(parsed)) throw new Error("permissions must contain application group assignments");
90
+ const accessRoles = new Set();
91
+ const permissions = parsed.map((application, index)=>{
92
+ if (!isRecord(application) || Object.keys(application).some((field)=>"clientIdentifier" !== field && "accessRole" !== field && "groupIds" !== field) || "string" != typeof application.clientIdentifier || 0 === application.clientIdentifier.length || "string" != typeof application.accessRole || 0 === application.accessRole.length || !Array.isArray(application.groupIds) || application.groupIds.some((groupId)=>"string" != typeof groupId || 0 === groupId.length)) throw new Error(`permissions[${index}] must contain clientIdentifier, accessRole, and groupIds`);
93
+ if (accessRoles.has(application.accessRole)) throw new Error(`permissions contains duplicate accessRole: ${application.accessRole}`);
94
+ const groupIds = application.groupIds;
95
+ if (new Set(groupIds).size !== groupIds.length) throw new Error(`permissions contains duplicate group IDs for: ${application.accessRole}`);
96
+ accessRoles.add(application.accessRole);
97
+ return {
98
+ clientIdentifier: application.clientIdentifier,
99
+ accessRole: application.accessRole,
100
+ groupIds
101
+ };
102
+ });
103
+ return permissions.flatMap(({ groupIds, accessRole })=>groupIds.map((groupId)=>({
104
+ accessRole,
105
+ groupId
106
+ })));
107
+ };
108
+ class UserAdminModule extends VuuModule {
109
+ #tables;
110
+ store;
111
+ constructor(snapshot = USER_ADMIN_INITIAL_SNAPSHOT){
112
+ super("USER_ADMIN");
113
+ this.store = new InMemoryUserAdminStore(snapshot);
114
+ this.#tables = createTables();
115
+ reconcileUserAdminTables(snapshot, this.#tables);
116
+ }
117
+ get schemas() {
118
+ return USER_ADMIN_TABLE_SCHEMAS;
119
+ }
120
+ get tables() {
121
+ return this.#tables;
122
+ }
123
+ get menus() {
124
+ return {
125
+ clients: void 0,
126
+ group_roles: void 0,
127
+ groups: void 0,
128
+ roles: void 0,
129
+ user_group_roles: void 0,
130
+ user_groups: void 0,
131
+ users: void 0
132
+ };
133
+ }
134
+ get visualLinks() {}
135
+ get menuServices() {}
136
+ get services() {
137
+ const services = [
138
+ {
139
+ rpcName: "addUser",
140
+ service: this.addUser
141
+ },
142
+ {
143
+ rpcName: "updateUser",
144
+ service: this.updateUser
145
+ },
146
+ {
147
+ rpcName: "deleteUser",
148
+ service: this.deleteUser
149
+ },
150
+ {
151
+ rpcName: "addGroup",
152
+ service: this.addGroup
153
+ },
154
+ {
155
+ rpcName: "updateGroup",
156
+ service: this.updateGroup
157
+ },
158
+ {
159
+ rpcName: "deleteGroup",
160
+ service: this.deleteGroup
161
+ },
162
+ {
163
+ rpcName: "addClient",
164
+ service: this.addClient
165
+ },
166
+ {
167
+ rpcName: "updateClient",
168
+ service: this.updateClient
169
+ },
170
+ {
171
+ rpcName: "addRole",
172
+ service: this.addRole
173
+ },
174
+ {
175
+ rpcName: "addClientRole",
176
+ service: this.addClientRole
177
+ },
178
+ {
179
+ rpcName: "updateRole",
180
+ service: this.updateRole
181
+ },
182
+ {
183
+ rpcName: "assignGroupRole",
184
+ service: this.assignGroupRole
185
+ },
186
+ {
187
+ rpcName: "removeGroupRole",
188
+ service: this.removeGroupRole
189
+ },
190
+ {
191
+ rpcName: "assignUserToGroup",
192
+ service: this.assignUserToGroup
193
+ },
194
+ {
195
+ rpcName: "removeUserFromGroup",
196
+ service: this.removeUserFromGroup
197
+ },
198
+ {
199
+ rpcName: "getUserModuleAccessOptions",
200
+ service: this.getUserModuleAccessOptions
201
+ },
202
+ {
203
+ rpcName: "setUserModuleAccess",
204
+ service: this.setUserModuleAccess
205
+ }
206
+ ];
207
+ return {
208
+ clients: services,
209
+ group_roles: services,
210
+ groups: services,
211
+ roles: services,
212
+ user_group_roles: services,
213
+ user_groups: services,
214
+ users: services
215
+ };
216
+ }
217
+ async reconcile() {
218
+ reconcileUserAdminTables(await this.store.snapshot(), this.#tables);
219
+ }
220
+ createSessionTable(sourceTable, sessionTableName, editSessionMode, dataSource, sessionType) {
221
+ const sessionTable = super.createSessionTable(sourceTable, sessionTableName, editSessionMode, dataSource, sessionType);
222
+ const extraColumns = "users" === sourceTable.name ? [
223
+ {
224
+ name: "permissions",
225
+ serverDataType: "string"
226
+ }
227
+ ] : "groups" === sourceTable.name ? [
228
+ {
229
+ name: "group_name",
230
+ serverDataType: "string"
231
+ },
232
+ {
233
+ name: "role_assignments",
234
+ serverDataType: "string"
235
+ }
236
+ ] : [];
237
+ const columnsToAdd = extraColumns.filter(({ name })=>!sessionTable.schema.columns.some((column)=>column.name === name));
238
+ if (0 === columnsToAdd.length) return sessionTable;
239
+ const sessionSchema = {
240
+ ...sessionTable.schema,
241
+ columns: sessionTable.schema.columns.concat(columnsToAdd)
242
+ };
243
+ return new Table(sessionSchema, sessionTable.data.map((row)=>row.concat(columnsToAdd.map(()=>""))), buildDataColumnMapFromSchema(sessionSchema));
244
+ }
245
+ async beforeSessionSave(sourceTable, sessionTable) {
246
+ if ("roles" === sourceTable.name) return void await this.saveClientRolesFromSession(sessionTable);
247
+ if ("groups" === sourceTable.name) return void await this.saveGroupsFromSession(sessionTable);
248
+ if ("users" !== sourceTable.name) return;
249
+ const permissionsIndex = sessionTable.map.permissions;
250
+ const actionIndex = sessionTable.map.vuuAction;
251
+ const userIdIndex = sessionTable.map.user_id;
252
+ const usernameIndex = sessionTable.map.username;
253
+ for (const row of sessionTable.data){
254
+ if ("editCell" !== row[actionIndex]) continue;
255
+ const userId = String(row[userIdIndex]);
256
+ const sourceRow = sourceTable.findByKey(userId);
257
+ if (!sourceRow || sourceRow[sourceTable.map.username] !== row[usernameIndex]) throw new Error("username is read-only");
258
+ if ("string" == typeof row[permissionsIndex] && "" !== row[permissionsIndex]) await this.store.setUserModuleAccess(userId, parseSessionPermissions(row[permissionsIndex]));
259
+ }
260
+ }
261
+ async afterSessionSave(sourceTable, sessionTable) {
262
+ const roleCreationSaved = "roles" === sourceTable.name && sessionTable.data.some((row)=>"addRow" === row[sessionTable.map.vuuAction] && "client role created" === row[sessionTable.map.vuuMsg]);
263
+ const groupChangesSaved = "groups" === sourceTable.name && sessionTable.data.some((row)=>"group created" === row[sessionTable.map.vuuMsg] || "group roles updated" === row[sessionTable.map.vuuMsg]);
264
+ if ("users" === sourceTable.name || roleCreationSaved || groupChangesSaved) await this.reconcile();
265
+ }
266
+ async saveGroupsFromSession(sessionTable) {
267
+ const actionIndex = sessionTable.map.vuuAction;
268
+ const messageIndex = sessionTable.map.vuuMsg;
269
+ const keyIndex = sessionTable.map[sessionTable.schema.key];
270
+ const groupIdIndex = sessionTable.map.group_id;
271
+ const groupNameIndex = sessionTable.map.group_name;
272
+ const roleAssignmentsIndex = sessionTable.map.role_assignments;
273
+ for (const row of sessionTable.data)if ("addRow" !== row[actionIndex] || row[messageIndex]) {
274
+ if ("editCell" === row[actionIndex] && "string" == typeof row[roleAssignmentsIndex] && "" !== row[roleAssignmentsIndex]) {
275
+ const groupId = requiredString({
276
+ group_id: row[groupIdIndex]
277
+ }, "group_id");
278
+ const roleIds = parseSessionRoleAssignments(row[roleAssignmentsIndex]);
279
+ await this.saveGroupRoleAssignments(groupId, roleIds, await this.store.snapshot());
280
+ sessionTable.update(String(row[keyIndex]), "vuuMsg", "group roles updated");
281
+ }
282
+ } else {
283
+ const name = requiredString({
284
+ group_name: row[groupNameIndex]
285
+ }, "group_name").trim();
286
+ if (!name) throw new Error("group_name must be a non-empty string");
287
+ const roleIds = parseSessionRoleAssignments(row[roleAssignmentsIndex]);
288
+ const before = await this.store.snapshot();
289
+ this.resolveGroupRoleAssignments(roleIds, before);
290
+ const existingGroupIds = new Set(before.groups.map(({ id })=>id));
291
+ await this.store.addGroup({
292
+ name
293
+ });
294
+ const snapshot = await this.store.snapshot();
295
+ const group = snapshot.groups.find(({ id, name: groupName })=>!existingGroupIds.has(id) && groupName === name);
296
+ if (!group) throw new Error("Created group could not be found");
297
+ await this.saveGroupRoleAssignments(group.id, roleIds, snapshot);
298
+ sessionTable.update(String(row[keyIndex]), "vuuMsg", "group created");
299
+ }
300
+ }
301
+ async saveGroupRoleAssignments(groupId, desiredRoleIds, snapshot) {
302
+ if (!snapshot.groups.some(({ id })=>id === groupId)) throw new Error(`User admin group not found: ${groupId}`);
303
+ const groupRoles = snapshot.groupRoles.filter(({ group })=>group.id === groupId);
304
+ const roleAssignmentsById = new Map(snapshot.clientRoles.map((assignment)=>[
305
+ assignment.role.id,
306
+ assignment
307
+ ]));
308
+ const desiredRoleSet = new Set(desiredRoleIds);
309
+ const currentAssignments = groupRoles.map(({ role })=>{
310
+ const assignment = roleAssignmentsById.get(role.id);
311
+ if (!assignment) throw new Error(`Assigned user admin role no longer exists: ${role.id}`);
312
+ return assignment;
313
+ });
314
+ const currentRoleSet = new Set(groupRoles.map(({ role })=>role.id));
315
+ const desiredAssignments = this.resolveGroupRoleAssignments(desiredRoleIds, snapshot);
316
+ for (const assignment of currentAssignments)if (!desiredRoleSet.has(assignment.role.id)) {
317
+ const clientRef = assignment.role.clientRole && assignment.client?.clientId ? {
318
+ clientKey: assignment.client.clientId
319
+ } : void 0;
320
+ await this.store.removeRoleFromGroup({
321
+ groupId
322
+ }, {
323
+ roleId: assignment.role.id
324
+ }, clientRef);
325
+ }
326
+ for (const assignment of desiredAssignments)if (!currentRoleSet.has(assignment.role.id)) {
327
+ const clientRef = assignment.role.clientRole && assignment.client?.clientId ? {
328
+ clientKey: assignment.client.clientId
329
+ } : void 0;
330
+ await this.store.addRoleToGroup({
331
+ groupId
332
+ }, {
333
+ roleId: assignment.role.id
334
+ }, clientRef);
335
+ }
336
+ }
337
+ resolveGroupRoleAssignments(roleIds, snapshot) {
338
+ const roleAssignmentsById = new Map(snapshot.clientRoles.map((assignment)=>[
339
+ assignment.role.id,
340
+ assignment
341
+ ]));
342
+ return roleIds.map((roleId)=>{
343
+ const assignment = roleAssignmentsById.get(roleId);
344
+ if (!assignment) throw new Error(`User admin role not found: ${roleId}`);
345
+ if (assignment.role.clientRole && !assignment.client?.clientId) throw new Error(`User admin client role has no owning client: ${roleId}`);
346
+ return assignment;
347
+ });
348
+ }
349
+ async saveClientRolesFromSession(sessionTable) {
350
+ const actionIndex = sessionTable.map.vuuAction;
351
+ const messageIndex = sessionTable.map.vuuMsg;
352
+ const keyIndex = sessionTable.map[sessionTable.schema.key];
353
+ const snapshot = await this.store.snapshot();
354
+ const clientsById = new Map(snapshot.clients.map((client)=>[
355
+ client.id,
356
+ client
357
+ ]));
358
+ const pendingRoles = [
359
+ ...sessionTable.data
360
+ ].flatMap((row)=>{
361
+ if ("addRow" !== row[actionIndex] || row[messageIndex]) return [];
362
+ const params = Object.fromEntries(sessionTable.schema.columns.map(({ name })=>[
363
+ name,
364
+ row[sessionTable.map[name]]
365
+ ]));
366
+ const roleName = requiredString(params, "role_name").trim();
367
+ if (!roleName) throw new Error("role_name must be a non-empty string");
368
+ const clientId = requiredString(params, "client_id");
369
+ const clientIdentifier = requiredString(params, "client_identifier");
370
+ const client = clientsById.get(clientId);
371
+ if (!client || client.clientId !== clientIdentifier) throw new Error("client_id and client_identifier must identify the same client");
372
+ return [
373
+ {
374
+ client,
375
+ description: optionalString(params, "description") ?? "",
376
+ roleName,
377
+ row
378
+ }
379
+ ];
380
+ });
381
+ for (const { client, description, roleName, row } of pendingRoles){
382
+ await this.store.addClientRole({
383
+ clientKey: client.clientId
384
+ }, {
385
+ description,
386
+ name: roleName
387
+ });
388
+ sessionTable.update(String(row[keyIndex]), "vuuMsg", "client role created");
389
+ }
390
+ }
391
+ mutation = (rpcName, mutate)=>async (request)=>{
392
+ try {
393
+ await mutate(readParams(request, rpcName));
394
+ await this.reconcile();
395
+ return {
396
+ data: void 0,
397
+ type: "SUCCESS_RESULT"
398
+ };
399
+ } catch (error) {
400
+ return errorResult(error);
401
+ }
402
+ };
403
+ addUser = this.mutation("addUser", async (params)=>{
404
+ const groupIds = optionalStringArray(params, "group_ids");
405
+ const username = requiredString(params, "username");
406
+ await this.assertUsernameIsUnique(username);
407
+ await this.store.addUser({
408
+ email: optionalString(params, "email"),
409
+ emailVerified: optionalBoolean(params, "emailVerified"),
410
+ enabled: optionalBoolean(params, "enabled"),
411
+ firstName: optionalString(params, "firstName"),
412
+ lastName: optionalString(params, "lastName"),
413
+ temporary_password: optionalString(params, "temporary_password"),
414
+ username
415
+ });
416
+ if (groupIds) {
417
+ const user = await this.store.findUserByUsername(requiredString(params, "username"));
418
+ if (!user) throw new Error("Created user could not be found");
419
+ await this.store.syncUserGroups(user.id, groupIds);
420
+ }
421
+ });
422
+ updateUser = this.mutation("updateUser", async (params)=>{
423
+ const groupIds = optionalStringArray(params, "group_ids");
424
+ const userId = requiredString(params, "userId");
425
+ const username = optionalString(params, "username");
426
+ if (username) await this.assertUsernameIsUnique(username, userId);
427
+ await this.store.updateUser({
428
+ email: optionalString(params, "email"),
429
+ emailVerified: optionalBoolean(params, "emailVerified"),
430
+ enabled: optionalBoolean(params, "enabled"),
431
+ firstName: optionalString(params, "firstName"),
432
+ lastName: optionalString(params, "lastName"),
433
+ temporary_password: optionalString(params, "temporary_password"),
434
+ userId,
435
+ username
436
+ });
437
+ if (groupIds) await this.store.syncUserGroups(userId, groupIds);
438
+ });
439
+ deleteUser = this.mutation("deleteUser", (params)=>this.store.deleteUser(requiredString(params, "userId")));
440
+ addGroup = this.mutation("addGroup", (params)=>this.store.addGroup({
441
+ name: requiredString(params, "name")
442
+ }));
443
+ updateGroup = this.mutation("updateGroup", (params)=>this.store.updateGroup(requiredString(params, "groupId"), {
444
+ name: requiredString(params, "name")
445
+ }));
446
+ deleteGroup = this.mutation("deleteGroup", (params)=>this.store.deleteGroup(requiredString(params, "groupId")));
447
+ addClient = this.mutation("addClient", (params)=>this.store.addClient({
448
+ clientId: requiredString(params, "clientId"),
449
+ description: optionalString(params, "description"),
450
+ enabled: optionalBoolean(params, "enabled"),
451
+ name: optionalString(params, "name")
452
+ }));
453
+ updateClient = this.mutation("updateClient", (params)=>this.store.updateClient(requiredString(params, "clientId"), {
454
+ description: optionalString(params, "description"),
455
+ enabled: optionalBoolean(params, "enabled"),
456
+ name: optionalString(params, "name")
457
+ }));
458
+ addRole = this.mutation("addRole", (params)=>this.store.addRole({
459
+ description: optionalString(params, "description"),
460
+ name: requiredString(params, "name")
461
+ }));
462
+ addClientRole = this.mutation("addClientRole", (params)=>this.store.addClientRole({
463
+ clientKey: requiredString(params, "clientId")
464
+ }, {
465
+ description: optionalString(params, "description"),
466
+ name: requiredString(params, "name")
467
+ }));
468
+ updateRole = this.mutation("updateRole", async (params)=>{
469
+ const clientId = optionalString(params, "clientId");
470
+ const changes = {
471
+ description: optionalString(params, "description"),
472
+ name: optionalString(params, "name")
473
+ };
474
+ if (clientId) await this.store.updateClientRole(clientId, requiredString(params, "roleName"), changes);
475
+ else await this.store.updateRealmRole(requiredString(params, "roleId"), changes);
476
+ });
477
+ assignGroupRole = this.mutation("assignGroupRole", (params)=>{
478
+ const group = reference(params, "groupId", "groupName");
479
+ const role = reference(params, "roleId", "roleName");
480
+ const clientId = optionalString(params, "clientId");
481
+ return this.store.addRoleToGroup({
482
+ groupId: group.id,
483
+ groupName: group.label
484
+ }, {
485
+ roleId: role.id,
486
+ roleName: role.label
487
+ }, clientId ? {
488
+ clientKey: clientId
489
+ } : void 0);
490
+ });
491
+ removeGroupRole = this.mutation("removeGroupRole", (params)=>{
492
+ const group = reference(params, "groupId", "groupName");
493
+ const role = reference(params, "roleId", "roleName");
494
+ const clientId = optionalString(params, "clientId");
495
+ return this.store.removeRoleFromGroup({
496
+ groupId: group.id,
497
+ groupName: group.label
498
+ }, {
499
+ roleId: role.id,
500
+ roleName: role.label
501
+ }, clientId ? {
502
+ clientKey: clientId
503
+ } : void 0);
504
+ });
505
+ assignUserToGroup = this.mutation("assignUserToGroup", (params)=>{
506
+ const user = reference(params, "userId", "username");
507
+ const group = reference(params, "groupId", "groupName");
508
+ return this.store.addUserToGroup({
509
+ userId: user.id,
510
+ username: user.label
511
+ }, {
512
+ groupId: group.id,
513
+ groupName: group.label
514
+ });
515
+ });
516
+ removeUserFromGroup = this.mutation("removeUserFromGroup", (params)=>{
517
+ const user = reference(params, "userId", "username");
518
+ const group = reference(params, "groupId", "groupName");
519
+ return this.store.removeUserFromGroup({
520
+ userId: user.id,
521
+ username: user.label
522
+ }, {
523
+ groupId: group.id,
524
+ groupName: group.label
525
+ });
526
+ });
527
+ getUserModuleAccessOptions = async (request)=>{
528
+ try {
529
+ const params = readParams(request, "getUserModuleAccessOptions");
530
+ const [options, snapshot] = await Promise.all([
531
+ this.store.getUserModuleAccessOptions(requiredString(params, "userId")),
532
+ this.store.snapshot()
533
+ ]);
534
+ const groupDisplayNames = new Map(snapshot.groups.map(({ groupDisplayName, id, name })=>[
535
+ id,
536
+ resolveDisplayName(name, groupDisplayName)
537
+ ]));
538
+ const roleDisplayNames = new Map(snapshot.clientRoles.map(({ role })=>[
539
+ role.id,
540
+ resolveDisplayName(role.name, role.roleDisplayName)
541
+ ]));
542
+ return {
543
+ data: {
544
+ modules: options.modules.map(({ groups, ...module })=>({
545
+ ...module,
546
+ groups: groups.map((group)=>({
547
+ ...group,
548
+ groupDisplayName: groupDisplayNames.get(group.groupId) ?? resolveDisplayName(group.groupName),
549
+ roleDisplayName: roleDisplayNames.get(group.roleId) ?? resolveDisplayName(group.roleName)
550
+ }))
551
+ }))
552
+ },
553
+ type: "SUCCESS_RESULT"
554
+ };
555
+ } catch (error) {
556
+ return errorResult(error);
557
+ }
558
+ };
559
+ setUserModuleAccess = this.mutation("setUserModuleAccess", (params)=>this.store.setUserModuleAccess(requiredString(params, "userId"), parseAssignments(requiredString(params, "assignments"))));
560
+ async assertUsernameIsUnique(username, userId) {
561
+ const normalizedUsername = username.toLocaleLowerCase();
562
+ const existingUser = (await this.store.snapshot()).users.find((user)=>user.id !== userId && user.username.toLocaleLowerCase() === normalizedUsername);
563
+ if (existingUser) throw new Error("username must be unique");
564
+ }
565
+ }
566
+ const userAdminModule = new UserAdminModule();
567
+ export { UserAdminModule, userAdminModule };
@@ -0,0 +1,3 @@
1
+ export { UserAdminModule, userAdminModule } from "./UserAdminModule.js";
2
+ export { USER_ADMIN_INITIAL_SNAPSHOT } from "./initialSnapshot.js";
3
+ export { projectUserAdminSnapshot, reconcileUserAdminTables } from "./snapshot-projection.js";