@jskit-ai/users-core 0.1.4

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 (148) hide show
  1. package/package.descriptor.mjs +464 -0
  2. package/package.json +35 -0
  3. package/src/server/UsersCoreServiceProvider.js +74 -0
  4. package/src/server/accountNotifications/accountNotificationsActions.js +39 -0
  5. package/src/server/accountNotifications/accountNotificationsService.js +41 -0
  6. package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +41 -0
  7. package/src/server/accountNotifications/registerAccountNotifications.js +39 -0
  8. package/src/server/accountPreferences/accountPreferencesActions.js +39 -0
  9. package/src/server/accountPreferences/accountPreferencesService.js +41 -0
  10. package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +41 -0
  11. package/src/server/accountPreferences/registerAccountPreferences.js +39 -0
  12. package/src/server/accountProfile/accountProfileActions.js +137 -0
  13. package/src/server/accountProfile/accountProfileService.js +124 -0
  14. package/src/server/accountProfile/avatarService.js +141 -0
  15. package/src/server/accountProfile/avatarStorageService.js +132 -0
  16. package/src/server/accountProfile/bootAccountProfileRoutes.js +166 -0
  17. package/src/server/accountProfile/registerAccountProfile.js +62 -0
  18. package/src/server/accountProfile/registerAvatarMultipartSupport.js +43 -0
  19. package/src/server/accountSecurity/accountSecurityActions.js +144 -0
  20. package/src/server/accountSecurity/accountSecurityService.js +103 -0
  21. package/src/server/accountSecurity/bootAccountSecurityRoutes.js +183 -0
  22. package/src/server/accountSecurity/registerAccountSecurity.js +31 -0
  23. package/src/server/common/README.md +21 -0
  24. package/src/server/common/contributors/README.md +11 -0
  25. package/src/server/common/contributors/workspaceActionContextContributor.js +79 -0
  26. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
  27. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +79 -0
  28. package/src/server/common/diTokens.js +21 -0
  29. package/src/server/common/formatters/README.md +11 -0
  30. package/src/server/common/formatters/accountAvatarFormatter.js +42 -0
  31. package/src/server/common/formatters/accountSecurityStatusFormatter.js +71 -0
  32. package/src/server/common/formatters/accountSettingsResponseFormatter.js +62 -0
  33. package/src/server/common/formatters/workspaceFormatter.js +46 -0
  34. package/src/server/common/registerCommonRepositories.js +45 -0
  35. package/src/server/common/registerSharedApi.js +9 -0
  36. package/src/server/common/repositories/README.md +24 -0
  37. package/src/server/common/repositories/repositoryUtils.js +50 -0
  38. package/src/server/common/repositories/userProfilesRepository.js +251 -0
  39. package/src/server/common/repositories/userSettingsRepository.js +179 -0
  40. package/src/server/common/repositories/workspaceInvitesRepository.js +172 -0
  41. package/src/server/common/repositories/workspaceMembershipsRepository.js +157 -0
  42. package/src/server/common/repositories/workspacesRepository.js +183 -0
  43. package/src/server/common/routes/README.md +11 -0
  44. package/src/server/common/services/README.md +12 -0
  45. package/src/server/common/services/accountContextService.js +31 -0
  46. package/src/server/common/services/authProfileSyncService.js +128 -0
  47. package/src/server/common/services/workspaceContextService.js +270 -0
  48. package/src/server/common/support/deepFreeze.js +17 -0
  49. package/src/server/common/support/realtimeServiceEvents.js +94 -0
  50. package/src/server/common/support/resolveActionUser.js +11 -0
  51. package/src/server/common/support/workspaceRoutePaths.js +17 -0
  52. package/src/server/common/validators/README.md +11 -0
  53. package/src/server/common/validators/authenticatedUserValidator.js +42 -0
  54. package/src/server/common/validators/routeParamsValidator.js +62 -0
  55. package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +64 -0
  56. package/src/server/consoleSettings/consoleService.js +36 -0
  57. package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
  58. package/src/server/consoleSettings/consoleSettingsRepository.js +111 -0
  59. package/src/server/consoleSettings/consoleSettingsService.js +40 -0
  60. package/src/server/consoleSettings/registerConsoleSettings.js +57 -0
  61. package/src/server/registerWorkspaceBootstrap.js +36 -0
  62. package/src/server/registerWorkspaceCore.js +95 -0
  63. package/src/server/support/resolveWorkspace.js +16 -0
  64. package/src/server/support/workspaceActionSurfaces.js +135 -0
  65. package/src/server/support/workspaceInvitationsPolicy.js +45 -0
  66. package/src/server/support/workspaceRouteInput.js +22 -0
  67. package/src/server/workspaceBootstrapContributor.js +401 -0
  68. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +73 -0
  69. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
  70. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +65 -0
  71. package/src/server/workspaceMembers/bootWorkspaceMembers.js +238 -0
  72. package/src/server/workspaceMembers/registerWorkspaceMembers.js +112 -0
  73. package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
  74. package/src/server/workspaceMembers/workspaceMembersService.js +210 -0
  75. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +63 -0
  76. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +128 -0
  77. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
  78. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +137 -0
  79. package/src/server/workspaceSettings/bootWorkspaceSettings.js +77 -0
  80. package/src/server/workspaceSettings/registerWorkspaceSettings.js +67 -0
  81. package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
  82. package/src/server/workspaceSettings/workspaceSettingsRepository.js +135 -0
  83. package/src/server/workspaceSettings/workspaceSettingsService.js +65 -0
  84. package/src/shared/events/usersEvents.js +19 -0
  85. package/src/shared/index.js +91 -0
  86. package/src/shared/operationMessages.js +16 -0
  87. package/src/shared/resources/consoleSettingsFields.js +55 -0
  88. package/src/shared/resources/consoleSettingsResource.js +139 -0
  89. package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
  90. package/src/shared/resources/userProfileResource.js +148 -0
  91. package/src/shared/resources/userSettingsFields.js +71 -0
  92. package/src/shared/resources/userSettingsResource.js +416 -0
  93. package/src/shared/resources/workspaceMembersResource.js +352 -0
  94. package/src/shared/resources/workspacePendingInvitationsResource.js +87 -0
  95. package/src/shared/resources/workspaceResource.js +149 -0
  96. package/src/shared/resources/workspaceSettingsFields.js +60 -0
  97. package/src/shared/resources/workspaceSettingsResource.js +178 -0
  98. package/src/shared/roles.js +136 -0
  99. package/src/shared/settings.js +31 -0
  100. package/src/shared/support/usersApiPaths.js +34 -0
  101. package/src/shared/support/usersVisibility.js +45 -0
  102. package/src/shared/support/workspacePathModel.js +145 -0
  103. package/src/shared/tenancyMode.js +35 -0
  104. package/src/shared/tenancyProfile.js +73 -0
  105. package/templates/config/workspaceRoles.js +30 -0
  106. package/templates/migrations/users_core_console_owner.cjs +39 -0
  107. package/templates/migrations/users_core_initial.cjs +118 -0
  108. package/templates/migrations/users_core_profile_username.cjs +98 -0
  109. package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
  110. package/templates/packages/main/src/shared/resources/userSettingsFields.js +138 -0
  111. package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +105 -0
  112. package/test/authProfileSyncService.test.js +119 -0
  113. package/test/avatarService.test.js +114 -0
  114. package/test/avatarStorageService.test.js +61 -0
  115. package/test/consoleService.test.js +57 -0
  116. package/test/consoleSettingsService.test.js +86 -0
  117. package/test/exportsContract.test.js +38 -0
  118. package/test/registerAvatarMultipartSupport.test.js +64 -0
  119. package/test/registerServiceRealtimeEvents.test.js +160 -0
  120. package/test/registerWorkspaceDirectory.test.js +26 -0
  121. package/test/registerWorkspaceSettings.test.js +44 -0
  122. package/test/resourcesCanonical.test.js +90 -0
  123. package/test/roles.test.js +74 -0
  124. package/test/settingsFieldRegistriesSingleton.test.js +24 -0
  125. package/test/tenancyProfile.test.js +67 -0
  126. package/test/userSettingsResource.test.js +31 -0
  127. package/test/usersApiPaths.test.js +31 -0
  128. package/test/usersRouteRequestInputValidator.test.js +556 -0
  129. package/test/usersRouteResources.test.js +113 -0
  130. package/test/usersRouteValidators.test.js +49 -0
  131. package/test/usersVisibility.test.js +22 -0
  132. package/test/workspaceActionContextContributor.test.js +251 -0
  133. package/test/workspaceActionSurfaces.test.js +105 -0
  134. package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
  135. package/test/workspaceBootstrapContributor.test.js +466 -0
  136. package/test/workspaceInvitationsPolicy.test.js +71 -0
  137. package/test/workspaceInvitesRepository.test.js +111 -0
  138. package/test/workspaceMembersService.test.js +400 -0
  139. package/test/workspacePathModel.test.js +93 -0
  140. package/test/workspacePendingInvitationsResource.test.js +38 -0
  141. package/test/workspacePendingInvitationsService.test.js +151 -0
  142. package/test/workspaceRouteVisibilityResolver.test.js +83 -0
  143. package/test/workspaceService.test.js +480 -0
  144. package/test/workspaceSettingsActions.test.js +42 -0
  145. package/test/workspaceSettingsRepository.test.js +156 -0
  146. package/test/workspaceSettingsResource.test.js +156 -0
  147. package/test/workspaceSettingsService.test.js +120 -0
  148. package/test-support/registerDefaultSettingsFields.js +3 -0
@@ -0,0 +1,67 @@
1
+ import { KERNEL_TOKENS } from "@jskit-ai/kernel/shared/support/tokens";
2
+ import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
3
+ import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
4
+ import {
5
+ WORKSPACE_SETTINGS_CHANGED_EVENT
6
+ } from "../../shared/events/usersEvents.js";
7
+ import { deepFreeze } from "../common/support/deepFreeze.js";
8
+ import { createRepository as createWorkspaceSettingsRepository } from "./workspaceSettingsRepository.js";
9
+ import { createService as createWorkspaceSettingsService } from "./workspaceSettingsService.js";
10
+ import { workspaceSettingsActions } from "./workspaceSettingsActions.js";
11
+ import { createWorkspaceRoleCatalog } from "../../shared/roles.js";
12
+ import { USERS_WORKSPACE_INVITATIONS_ENABLED_TOKEN } from "../common/diTokens.js";
13
+ import { createWorkspaceEntityAndBootstrapEvents } from "../common/support/realtimeServiceEvents.js";
14
+
15
+ function resolveWorkspaceSettingsDefaultInvitesEnabled(appConfig = {}) {
16
+ const defaultInvitesEnabled = appConfig?.workspaceSettings?.defaults?.invitesEnabled;
17
+
18
+ if (typeof defaultInvitesEnabled !== "boolean") {
19
+ throw new TypeError("users.core requires appConfig.workspaceSettings.defaults.invitesEnabled.");
20
+ }
21
+
22
+ return defaultInvitesEnabled;
23
+ }
24
+
25
+ function registerWorkspaceSettings(app) {
26
+ if (!app || typeof app.singleton !== "function" || typeof app.actions !== "function" || typeof app.service !== "function") {
27
+ throw new Error("registerWorkspaceSettings requires application singleton()/service()/actions().");
28
+ }
29
+
30
+ app.singleton("workspaceSettingsRepository", (scope) => {
31
+ const knex = scope.make(KERNEL_TOKENS.Knex);
32
+ const appConfig = resolveAppConfig(scope);
33
+ return createWorkspaceSettingsRepository(knex, {
34
+ defaultInvitesEnabled: resolveWorkspaceSettingsDefaultInvitesEnabled(appConfig)
35
+ });
36
+ });
37
+
38
+ app.service(
39
+ "users.workspace.settings.service",
40
+ (scope) =>
41
+ createWorkspaceSettingsService({
42
+ workspaceSettingsRepository: scope.make("workspaceSettingsRepository"),
43
+ workspaceInvitationsEnabled: scope.make(USERS_WORKSPACE_INVITATIONS_ENABLED_TOKEN),
44
+ roleCatalog: createWorkspaceRoleCatalog(resolveAppConfig(scope))
45
+ }),
46
+ {
47
+ events: deepFreeze({
48
+ updateWorkspaceSettings: createWorkspaceEntityAndBootstrapEvents({
49
+ workspaceEntity: "settings",
50
+ workspaceOperation: "updated",
51
+ workspaceRealtimeEvent: WORKSPACE_SETTINGS_CHANGED_EVENT
52
+ })
53
+ })
54
+ }
55
+ );
56
+
57
+ app.actions(
58
+ withActionDefaults(workspaceSettingsActions, {
59
+ domain: "workspace",
60
+ dependencies: {
61
+ workspaceSettingsService: "users.workspace.settings.service"
62
+ }
63
+ })
64
+ );
65
+ }
66
+
67
+ export { registerWorkspaceSettings };
@@ -0,0 +1,72 @@
1
+ import { workspaceSettingsResource } from "../../shared/resources/workspaceSettingsResource.js";
2
+ import { workspaceSlugParamsValidator } from "../common/validators/routeParamsValidator.js";
3
+ import { resolveWorkspace } from "../support/resolveWorkspace.js";
4
+
5
+ const workspaceSettingsActions = Object.freeze([
6
+ {
7
+ id: "workspace.settings.read",
8
+ version: 1,
9
+ kind: "query",
10
+ channels: ["api", "automation", "internal"],
11
+ surfacesFrom: "workspace",
12
+ permission: {
13
+ require: "any",
14
+ permissions: ["workspace.settings.view", "workspace.settings.update"]
15
+ },
16
+ inputValidator: workspaceSlugParamsValidator,
17
+ outputValidator: workspaceSettingsResource.operations.view.outputValidator,
18
+ idempotency: "none",
19
+ audit: {
20
+ actionName: "workspace.settings.read"
21
+ },
22
+ observability: {},
23
+ async execute(input, context, deps) {
24
+ const response = await deps.workspaceSettingsService.getWorkspaceSettings(resolveWorkspace(context, input), {
25
+ context
26
+ });
27
+
28
+ return response;
29
+ }
30
+ },
31
+ {
32
+ id: "workspace.settings.update",
33
+ version: 1,
34
+ kind: "command",
35
+ channels: ["api", "assistant_tool", "automation", "internal"],
36
+ surfacesFrom: "workspace",
37
+ permission: {
38
+ require: "all",
39
+ permissions: ["workspace.settings.update"]
40
+ },
41
+ inputValidator: [
42
+ workspaceSlugParamsValidator,
43
+ {
44
+ patch: workspaceSettingsResource.operations.patch.bodyValidator
45
+ }
46
+ ],
47
+ outputValidator: workspaceSettingsResource.operations.patch.outputValidator,
48
+ idempotency: "optional",
49
+ audit: {
50
+ actionName: "workspace.settings.update"
51
+ },
52
+ observability: {},
53
+ extensions: {
54
+ assistant: {
55
+ description: "Update workspace settings."
56
+ }
57
+ },
58
+ async execute(input, context, deps) {
59
+ const response = await deps.workspaceSettingsService.updateWorkspaceSettings(
60
+ resolveWorkspace(context, input),
61
+ input.patch,
62
+ {
63
+ context
64
+ }
65
+ );
66
+
67
+ return response;
68
+ }
69
+ }
70
+ ]);
71
+
72
+ export { workspaceSettingsActions };
@@ -0,0 +1,135 @@
1
+ import {
2
+ toIsoString,
3
+ nowDb,
4
+ isDuplicateEntryError
5
+ } from "../common/repositories/repositoryUtils.js";
6
+ import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
7
+ import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
8
+ import {
9
+ workspaceSettingsFields,
10
+ resolveWorkspaceSettingsFieldKeys
11
+ } from "../../shared/resources/workspaceSettingsFields.js";
12
+
13
+ function resolveWorkspaceSettingsSeed(workspace = {}, { defaultInvitesEnabled = true } = {}) {
14
+ const source = normalizeObjectInput(workspace);
15
+ const seed = {};
16
+ for (const field of workspaceSettingsFields) {
17
+ const rawValue = Object.hasOwn(source, field.key)
18
+ ? source[field.key]
19
+ : field.resolveDefault({
20
+ workspace: source,
21
+ defaultInvitesEnabled
22
+ });
23
+ seed[field.key] = field.normalizeOutput(rawValue, {
24
+ workspace: source,
25
+ defaultInvitesEnabled
26
+ });
27
+ }
28
+ return seed;
29
+ }
30
+
31
+ function createRepository(knex, { defaultInvitesEnabled } = {}) {
32
+ if (typeof knex !== "function") {
33
+ throw new TypeError("workspaceSettingsRepository requires knex.");
34
+ }
35
+
36
+ function mapRow(row) {
37
+ if (!row) {
38
+ return null;
39
+ }
40
+
41
+ const settings = {
42
+ workspaceId: Number(row.workspace_id)
43
+ };
44
+ for (const field of workspaceSettingsFields) {
45
+ const rawValue = Object.hasOwn(row, field.dbColumn)
46
+ ? row[field.dbColumn]
47
+ : field.resolveDefault({
48
+ defaultInvitesEnabled
49
+ });
50
+ settings[field.key] = field.normalizeOutput(rawValue, {
51
+ defaultInvitesEnabled
52
+ });
53
+ }
54
+
55
+ settings.createdAt = toIsoString(row.created_at);
56
+ settings.updatedAt = toIsoString(row.updated_at);
57
+ return settings;
58
+ }
59
+
60
+ async function findByWorkspaceId(workspaceId, options = {}) {
61
+ const client = options?.trx || knex;
62
+ const row = await client("workspace_settings").where({ workspace_id: Number(workspaceId) }).first();
63
+ return mapRow(row);
64
+ }
65
+
66
+ async function ensureForWorkspaceId(workspaceId, options = {}) {
67
+ const client = options?.trx || knex;
68
+ const numericWorkspaceId = Number(workspaceId);
69
+ const seed = resolveWorkspaceSettingsSeed(options?.workspace, {
70
+ defaultInvitesEnabled
71
+ });
72
+ const existing = await findByWorkspaceId(numericWorkspaceId, { trx: client });
73
+ if (existing) {
74
+ return existing;
75
+ }
76
+
77
+ try {
78
+ const insertPayload = {
79
+ workspace_id: numericWorkspaceId,
80
+ created_at: nowDb(),
81
+ updated_at: nowDb()
82
+ };
83
+ for (const field of workspaceSettingsFields) {
84
+ insertPayload[field.dbColumn] = seed[field.key];
85
+ }
86
+ await client("workspace_settings").insert(insertPayload);
87
+ } catch (error) {
88
+ if (!isDuplicateEntryError(error)) {
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ return findByWorkspaceId(numericWorkspaceId, { trx: client });
94
+ }
95
+
96
+ async function updateSettingsByWorkspaceId(workspaceId, patch = {}, options = {}) {
97
+ const client = options?.trx || knex;
98
+ const ensured = await ensureForWorkspaceId(workspaceId, {
99
+ trx: client,
100
+ workspace: options?.workspace
101
+ });
102
+ const source = normalizeObjectInput(patch);
103
+ const settingsPatch = pickOwnProperties(source, resolveWorkspaceSettingsFieldKeys());
104
+
105
+ if (Object.keys(settingsPatch).length === 0) {
106
+ return ensured;
107
+ }
108
+
109
+ const dbPatch = {
110
+ updated_at: nowDb()
111
+ };
112
+
113
+ for (const field of workspaceSettingsFields) {
114
+ if (!Object.hasOwn(settingsPatch, field.key)) {
115
+ continue;
116
+ }
117
+ dbPatch[field.dbColumn] = field.normalizeInput(settingsPatch[field.key], {
118
+ payload: source
119
+ });
120
+ }
121
+
122
+ await client("workspace_settings").where({ workspace_id: Number(workspaceId) }).update({
123
+ ...dbPatch
124
+ });
125
+ return findByWorkspaceId(workspaceId, { trx: client });
126
+ }
127
+
128
+ return Object.freeze({
129
+ findByWorkspaceId,
130
+ ensureForWorkspaceId,
131
+ updateSettingsByWorkspaceId
132
+ });
133
+ }
134
+
135
+ export { createRepository };
@@ -0,0 +1,65 @@
1
+ import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
2
+ import { pickOwnProperties } from "@jskit-ai/kernel/shared/support";
3
+ import {
4
+ workspaceSettingsFields,
5
+ resolveWorkspaceSettingsFieldKeys
6
+ } from "../../shared/resources/workspaceSettingsFields.js";
7
+ import { createWorkspaceRoleCatalog, cloneWorkspaceRoleCatalog } from "../../shared/roles.js";
8
+
9
+ function createService({
10
+ workspaceSettingsRepository,
11
+ workspaceInvitationsEnabled = true,
12
+ roleCatalog = null
13
+ } = {}) {
14
+ if (!workspaceSettingsRepository) {
15
+ throw new Error("workspaceSettingsService requires workspaceSettingsRepository.");
16
+ }
17
+ const resolvedRoleCatalog = roleCatalog && typeof roleCatalog === "object" ? roleCatalog : createWorkspaceRoleCatalog();
18
+ const invitesAvailable = workspaceInvitationsEnabled === true;
19
+
20
+ async function getWorkspaceSettings(workspace, options = {}) {
21
+ const settingsRecord = await workspaceSettingsRepository.ensureForWorkspaceId(workspace.id, {
22
+ ...options,
23
+ workspace
24
+ });
25
+ const settings = {};
26
+ for (const field of workspaceSettingsFields) {
27
+ settings[field.key] = settingsRecord[field.key];
28
+ }
29
+ const invitesEnabled = invitesAvailable && settings.invitesEnabled !== false;
30
+ settings.invitesEnabled = invitesEnabled;
31
+ settings.invitesAvailable = invitesAvailable;
32
+ settings.invitesEffective = invitesAvailable && invitesEnabled;
33
+
34
+ return {
35
+ workspace: {
36
+ id: Number(workspace.id),
37
+ slug: String(workspace.slug || ""),
38
+ ownerUserId: Number(workspace.ownerUserId)
39
+ },
40
+ settings,
41
+ roleCatalog: cloneWorkspaceRoleCatalog(resolvedRoleCatalog)
42
+ };
43
+ }
44
+
45
+ async function updateWorkspaceSettings(workspace, payload = {}, options = {}) {
46
+ const source = normalizeObjectInput(payload);
47
+ const settingsPatch = pickOwnProperties(source, resolveWorkspaceSettingsFieldKeys());
48
+
49
+ if (Object.keys(settingsPatch).length > 0) {
50
+ await workspaceSettingsRepository.updateSettingsByWorkspaceId(workspace.id, settingsPatch, {
51
+ ...options,
52
+ workspace
53
+ });
54
+ }
55
+
56
+ return getWorkspaceSettings(workspace, options);
57
+ }
58
+
59
+ return Object.freeze({
60
+ getWorkspaceSettings,
61
+ updateWorkspaceSettings
62
+ });
63
+ }
64
+
65
+ export { createService };
@@ -0,0 +1,19 @@
1
+ const ACCOUNT_SETTINGS_CHANGED_EVENT = "account.settings.changed";
2
+ const CONSOLE_SETTINGS_CHANGED_EVENT = "console.settings.changed";
3
+ const USERS_BOOTSTRAP_CHANGED_EVENT = "users.bootstrap.changed";
4
+ const WORKSPACE_SETTINGS_CHANGED_EVENT = "workspace.settings.changed";
5
+ const WORKSPACE_MEMBERS_CHANGED_EVENT = "workspace.members.changed";
6
+ const WORKSPACE_INVITES_CHANGED_EVENT = "workspace.invites.changed";
7
+ const WORKSPACES_CHANGED_EVENT = "workspaces.changed";
8
+ const WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT = "workspace.invitations.pending.changed";
9
+
10
+ export {
11
+ ACCOUNT_SETTINGS_CHANGED_EVENT,
12
+ CONSOLE_SETTINGS_CHANGED_EVENT,
13
+ USERS_BOOTSTRAP_CHANGED_EVENT,
14
+ WORKSPACE_SETTINGS_CHANGED_EVENT,
15
+ WORKSPACE_MEMBERS_CHANGED_EVENT,
16
+ WORKSPACE_INVITES_CHANGED_EVENT,
17
+ WORKSPACES_CHANGED_EVENT,
18
+ WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
19
+ };
@@ -0,0 +1,91 @@
1
+ import {
2
+ OWNER_ROLE_ID,
3
+ ADMIN_ROLE_ID,
4
+ MEMBER_ROLE_ID,
5
+ resolveRolePermissions,
6
+ listRoleDescriptors,
7
+ hasPermission
8
+ } from "./roles.js";
9
+
10
+ import {
11
+ DEFAULT_WORKSPACE_COLOR,
12
+ DEFAULT_USER_SETTINGS,
13
+ coerceWorkspaceColor
14
+ } from "./settings.js";
15
+ import {
16
+ TENANCY_MODE_NONE,
17
+ TENANCY_MODE_PERSONAL,
18
+ TENANCY_MODE_WORKSPACE,
19
+ normalizeTenancyMode,
20
+ WORKSPACE_SLUG_POLICY_NONE,
21
+ WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
22
+ WORKSPACE_SLUG_POLICY_USER_SELECTED,
23
+ resolveTenancyProfile,
24
+ isWorkspaceTenancyMode
25
+ } from "./tenancyProfile.js";
26
+ import {
27
+ ACCOUNT_SETTINGS_CHANGED_EVENT,
28
+ CONSOLE_SETTINGS_CHANGED_EVENT,
29
+ WORKSPACE_SETTINGS_CHANGED_EVENT,
30
+ WORKSPACE_MEMBERS_CHANGED_EVENT,
31
+ WORKSPACE_INVITES_CHANGED_EVENT,
32
+ WORKSPACES_CHANGED_EVENT,
33
+ WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
34
+ } from "./events/usersEvents.js";
35
+
36
+ const USERS_SHARED_API = Object.freeze({
37
+ OWNER_ROLE_ID,
38
+ ADMIN_ROLE_ID,
39
+ MEMBER_ROLE_ID,
40
+ resolveRolePermissions,
41
+ listRoleDescriptors,
42
+ hasPermission,
43
+ DEFAULT_WORKSPACE_COLOR,
44
+ DEFAULT_USER_SETTINGS,
45
+ coerceWorkspaceColor,
46
+ TENANCY_MODE_NONE,
47
+ TENANCY_MODE_PERSONAL,
48
+ TENANCY_MODE_WORKSPACE,
49
+ normalizeTenancyMode,
50
+ WORKSPACE_SLUG_POLICY_NONE,
51
+ WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
52
+ WORKSPACE_SLUG_POLICY_USER_SELECTED,
53
+ resolveTenancyProfile,
54
+ isWorkspaceTenancyMode,
55
+ ACCOUNT_SETTINGS_CHANGED_EVENT,
56
+ CONSOLE_SETTINGS_CHANGED_EVENT,
57
+ WORKSPACE_SETTINGS_CHANGED_EVENT,
58
+ WORKSPACE_MEMBERS_CHANGED_EVENT,
59
+ WORKSPACE_INVITES_CHANGED_EVENT,
60
+ WORKSPACES_CHANGED_EVENT,
61
+ WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
62
+ });
63
+
64
+ export {
65
+ OWNER_ROLE_ID,
66
+ ADMIN_ROLE_ID,
67
+ MEMBER_ROLE_ID,
68
+ resolveRolePermissions,
69
+ listRoleDescriptors,
70
+ hasPermission,
71
+ DEFAULT_WORKSPACE_COLOR,
72
+ DEFAULT_USER_SETTINGS,
73
+ coerceWorkspaceColor,
74
+ TENANCY_MODE_NONE,
75
+ TENANCY_MODE_PERSONAL,
76
+ TENANCY_MODE_WORKSPACE,
77
+ normalizeTenancyMode,
78
+ WORKSPACE_SLUG_POLICY_NONE,
79
+ WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
80
+ WORKSPACE_SLUG_POLICY_USER_SELECTED,
81
+ resolveTenancyProfile,
82
+ isWorkspaceTenancyMode,
83
+ ACCOUNT_SETTINGS_CHANGED_EVENT,
84
+ CONSOLE_SETTINGS_CHANGED_EVENT,
85
+ WORKSPACE_SETTINGS_CHANGED_EVENT,
86
+ WORKSPACE_MEMBERS_CHANGED_EVENT,
87
+ WORKSPACE_INVITES_CHANGED_EVENT,
88
+ WORKSPACES_CHANGED_EVENT,
89
+ WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT,
90
+ USERS_SHARED_API
91
+ };
@@ -0,0 +1,16 @@
1
+ function createOperationMessages({
2
+ validationMessage = "Validation failed.",
3
+ apiValidationMessage = validationMessage
4
+ } = {}) {
5
+ const validation = String(validationMessage || "Validation failed.");
6
+ const apiValidation = String(apiValidationMessage || validation || "Validation failed.");
7
+
8
+ return Object.freeze({
9
+ validation,
10
+ apiValidation
11
+ });
12
+ }
13
+
14
+ export {
15
+ createOperationMessages
16
+ };
@@ -0,0 +1,55 @@
1
+ import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
2
+ import { resolveGlobalArrayRegistry } from "./resolveGlobalArrayRegistry.js";
3
+
4
+ const CONSOLE_SETTINGS_FIELDS_REGISTRY_KEY = Symbol.for("jskit.users-core.consoleSettingsFields");
5
+ const consoleSettingsFields = resolveGlobalArrayRegistry(CONSOLE_SETTINGS_FIELDS_REGISTRY_KEY);
6
+
7
+ function defineField(field = {}) {
8
+ const key = normalizeText(field.key);
9
+ if (!key) {
10
+ throw new TypeError("consoleSettingsFields.defineField requires field.key.");
11
+ }
12
+ if (consoleSettingsFields.some((entry) => entry.key === key)) {
13
+ throw new Error(`consoleSettingsFields.defineField duplicate key: ${key}`);
14
+ }
15
+ if (!field.inputSchema || typeof field.inputSchema !== "object") {
16
+ throw new TypeError(`consoleSettingsFields.defineField("${key}") requires inputSchema.`);
17
+ }
18
+ if (!field.outputSchema || typeof field.outputSchema !== "object") {
19
+ throw new TypeError(`consoleSettingsFields.defineField("${key}") requires outputSchema.`);
20
+ }
21
+ const dbColumn = normalizeText(field.dbColumn);
22
+ if (!dbColumn) {
23
+ throw new TypeError(`consoleSettingsFields.defineField("${key}") requires dbColumn.`);
24
+ }
25
+ if (typeof field.normalizeInput !== "function") {
26
+ throw new TypeError(`consoleSettingsFields.defineField("${key}") requires normalizeInput.`);
27
+ }
28
+ if (typeof field.normalizeOutput !== "function") {
29
+ throw new TypeError(`consoleSettingsFields.defineField("${key}") requires normalizeOutput.`);
30
+ }
31
+ if (typeof field.resolveDefault !== "function") {
32
+ throw new TypeError(`consoleSettingsFields.defineField("${key}") requires resolveDefault.`);
33
+ }
34
+
35
+ consoleSettingsFields.push({
36
+ key,
37
+ dbColumn,
38
+ required: field.required !== false,
39
+ inputSchema: field.inputSchema,
40
+ outputSchema: field.outputSchema,
41
+ normalizeInput: field.normalizeInput,
42
+ normalizeOutput: field.normalizeOutput,
43
+ resolveDefault: field.resolveDefault
44
+ });
45
+ }
46
+
47
+ function resetConsoleSettingsFields() {
48
+ consoleSettingsFields.splice(0, consoleSettingsFields.length);
49
+ }
50
+
51
+ export {
52
+ defineField,
53
+ resetConsoleSettingsFields,
54
+ consoleSettingsFields
55
+ };
@@ -0,0 +1,139 @@
1
+ import { Type } from "typebox";
2
+ import { createOperationMessages } from "../operationMessages.js";
3
+ import {
4
+ createCursorListValidator,
5
+ normalizeObjectInput
6
+ } from "@jskit-ai/kernel/shared/validators";
7
+ import { consoleSettingsFields } from "./consoleSettingsFields.js";
8
+
9
+ function buildCreateSchema() {
10
+ const properties = {};
11
+ for (const field of consoleSettingsFields) {
12
+ properties[field.key] = field.required === false ? Type.Optional(field.inputSchema) : field.inputSchema;
13
+ }
14
+ return Type.Object(properties, { additionalProperties: false });
15
+ }
16
+
17
+ function buildOutputSchema() {
18
+ const properties = {};
19
+ for (const field of consoleSettingsFields) {
20
+ properties[field.key] = field.outputSchema;
21
+ }
22
+ return Type.Object(properties, { additionalProperties: false });
23
+ }
24
+
25
+ function buildConsoleSettingsRecordSchema() {
26
+ return Type.Object(
27
+ {
28
+ settings: buildOutputSchema()
29
+ },
30
+ { additionalProperties: false }
31
+ );
32
+ }
33
+
34
+ function buildConsoleSettingsCreateSchema() {
35
+ return buildCreateSchema();
36
+ }
37
+
38
+ function buildConsoleSettingsReplaceSchema() {
39
+ return buildConsoleSettingsCreateSchema();
40
+ }
41
+
42
+ function buildConsoleSettingsPatchSchema() {
43
+ return Type.Partial(buildConsoleSettingsCreateSchema(), {
44
+ additionalProperties: false
45
+ });
46
+ }
47
+
48
+ function normalizeConsoleSettingsInput(payload = {}) {
49
+ const source = normalizeObjectInput(payload);
50
+ const normalized = {};
51
+ for (const field of consoleSettingsFields) {
52
+ if (!Object.hasOwn(source, field.key)) {
53
+ continue;
54
+ }
55
+ normalized[field.key] = field.normalizeInput(source[field.key], {
56
+ payload: source
57
+ });
58
+ }
59
+ return normalized;
60
+ }
61
+
62
+ const consoleSettingsOutputValidator = Object.freeze({
63
+ get schema() {
64
+ return buildConsoleSettingsRecordSchema();
65
+ },
66
+ normalize(payload = {}) {
67
+ const source = normalizeObjectInput(payload);
68
+ const settingsSource = normalizeObjectInput(source.settings);
69
+ const settings = {};
70
+
71
+ for (const field of consoleSettingsFields) {
72
+ const rawValue = Object.hasOwn(settingsSource, field.key)
73
+ ? settingsSource[field.key]
74
+ : field.resolveDefault({
75
+ settings: settingsSource
76
+ });
77
+ settings[field.key] = field.normalizeOutput(rawValue, {
78
+ settings: settingsSource
79
+ });
80
+ }
81
+
82
+ return {
83
+ settings
84
+ };
85
+ }
86
+ });
87
+
88
+ const CONSOLE_SETTINGS_OPERATION_MESSAGES = createOperationMessages();
89
+
90
+ const consoleSettingsResource = Object.freeze({
91
+ resource: "consoleSettings",
92
+ operations: Object.freeze({
93
+ view: Object.freeze({
94
+ method: "GET",
95
+ messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
96
+ outputValidator: consoleSettingsOutputValidator
97
+ }),
98
+ list: Object.freeze({
99
+ method: "GET",
100
+ messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
101
+ outputValidator: createCursorListValidator(consoleSettingsOutputValidator)
102
+ }),
103
+ create: Object.freeze({
104
+ method: "POST",
105
+ messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
106
+ bodyValidator: Object.freeze({
107
+ get schema() {
108
+ return buildConsoleSettingsCreateSchema();
109
+ },
110
+ normalize: normalizeConsoleSettingsInput
111
+ }),
112
+ outputValidator: consoleSettingsOutputValidator
113
+ }),
114
+ replace: Object.freeze({
115
+ method: "PUT",
116
+ messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
117
+ bodyValidator: Object.freeze({
118
+ get schema() {
119
+ return buildConsoleSettingsReplaceSchema();
120
+ },
121
+ normalize: normalizeConsoleSettingsInput
122
+ }),
123
+ outputValidator: consoleSettingsOutputValidator
124
+ }),
125
+ patch: Object.freeze({
126
+ method: "PATCH",
127
+ messages: CONSOLE_SETTINGS_OPERATION_MESSAGES,
128
+ bodyValidator: Object.freeze({
129
+ get schema() {
130
+ return buildConsoleSettingsPatchSchema();
131
+ },
132
+ normalize: normalizeConsoleSettingsInput
133
+ }),
134
+ outputValidator: consoleSettingsOutputValidator
135
+ })
136
+ })
137
+ });
138
+
139
+ export { consoleSettingsResource };
@@ -0,0 +1,6 @@
1
+ function resolveGlobalArrayRegistry(symbolKey) {
2
+ globalThis[symbolKey] = Array.isArray(globalThis[symbolKey]) ? globalThis[symbolKey] : [];
3
+ return globalThis[symbolKey];
4
+ }
5
+
6
+ export { resolveGlobalArrayRegistry };