@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,401 @@
1
+ import { AppError } from "@jskit-ai/kernel/server/runtime";
2
+ import { requireServiceMethod } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
3
+ import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
4
+ import {
5
+ TENANCY_MODE_NONE,
6
+ TENANCY_MODE_PERSONAL,
7
+ TENANCY_MODE_WORKSPACE,
8
+ WORKSPACE_SLUG_POLICY_NONE,
9
+ WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
10
+ WORKSPACE_SLUG_POLICY_USER_SELECTED,
11
+ resolveTenancyProfile
12
+ } from "../shared/tenancyProfile.js";
13
+ import { workspacePendingInvitationsResource } from "../shared/resources/workspacePendingInvitationsResource.js";
14
+ import {
15
+ mapMembershipSummary,
16
+ mapWorkspaceSettingsPublic,
17
+ mapWorkspaceSummary
18
+ } from "./common/formatters/workspaceFormatter.js";
19
+ import { accountAvatarFormatter } from "./common/formatters/accountAvatarFormatter.js";
20
+ import { authenticatedUserValidator } from "./common/validators/authenticatedUserValidator.js";
21
+ import { userSettingsFields } from "../shared/resources/userSettingsFields.js";
22
+
23
+ const REQUESTED_WORKSPACE_STATUS_RESOLVED = "resolved";
24
+ const REQUESTED_WORKSPACE_STATUS_NOT_FOUND = "not_found";
25
+ const REQUESTED_WORKSPACE_STATUS_FORBIDDEN = "forbidden";
26
+ const REQUESTED_WORKSPACE_STATUS_UNAUTHENTICATED = "unauthenticated";
27
+
28
+ function normalizePendingInvites(invites) {
29
+ return workspacePendingInvitationsResource.operations.list.outputValidator.normalize({
30
+ pendingInvites: invites
31
+ }).pendingInvites;
32
+ }
33
+
34
+ function getOAuthProviderCatalogPayload(authService) {
35
+ if (!authService || typeof authService.getOAuthProviderCatalog !== "function") {
36
+ return {
37
+ oauthProviders: [],
38
+ oauthDefaultProvider: null
39
+ };
40
+ }
41
+
42
+ const catalog = authService.getOAuthProviderCatalog();
43
+ const providers = Array.isArray(catalog?.providers)
44
+ ? catalog.providers
45
+ .map((provider) => ({
46
+ id: normalizeLowerText(provider?.id),
47
+ label: normalizeText(provider?.label)
48
+ }))
49
+ .filter((provider) => provider.id && provider.label)
50
+ : [];
51
+ const defaultProvider = normalizeLowerText(catalog?.defaultProvider);
52
+
53
+ return {
54
+ oauthProviders: providers,
55
+ oauthDefaultProvider: providers.some((provider) => provider.id === defaultProvider) ? defaultProvider : null
56
+ };
57
+ }
58
+
59
+ function normalizeBoolean(value, fallback) {
60
+ if (typeof value === "boolean") {
61
+ return value;
62
+ }
63
+ if (typeof value === "string") {
64
+ const normalized = normalizeLowerText(value);
65
+ if (normalized === "true") {
66
+ return true;
67
+ }
68
+ if (normalized === "false") {
69
+ return false;
70
+ }
71
+ }
72
+ return fallback;
73
+ }
74
+
75
+ function normalizeQueryPayload(value = {}) {
76
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
77
+ return {};
78
+ }
79
+ return value;
80
+ }
81
+
82
+ function resolveBootstrapWorkspaceSlug({ query = {}, request = null } = {}) {
83
+ const normalizedQuery = normalizeQueryPayload(query);
84
+ if (Object.hasOwn(normalizedQuery, "workspaceSlug")) {
85
+ return normalizeLowerText(normalizedQuery.workspaceSlug);
86
+ }
87
+
88
+ const normalizedInputQuery = normalizeQueryPayload(request?.input?.query);
89
+ if (Object.hasOwn(normalizedInputQuery, "workspaceSlug")) {
90
+ return normalizeLowerText(normalizedInputQuery.workspaceSlug);
91
+ }
92
+
93
+ const normalizedRequestQuery = normalizeQueryPayload(request?.query);
94
+ if (Object.hasOwn(normalizedRequestQuery, "workspaceSlug")) {
95
+ return normalizeLowerText(normalizedRequestQuery.workspaceSlug);
96
+ }
97
+
98
+ return "";
99
+ }
100
+
101
+ function normalizeRequestedWorkspaceStatus(value = "") {
102
+ const normalizedValue = normalizeLowerText(value);
103
+ if (
104
+ normalizedValue === REQUESTED_WORKSPACE_STATUS_RESOLVED ||
105
+ normalizedValue === REQUESTED_WORKSPACE_STATUS_NOT_FOUND ||
106
+ normalizedValue === REQUESTED_WORKSPACE_STATUS_FORBIDDEN ||
107
+ normalizedValue === REQUESTED_WORKSPACE_STATUS_UNAUTHENTICATED
108
+ ) {
109
+ return normalizedValue;
110
+ }
111
+ return "";
112
+ }
113
+
114
+ function createRequestedWorkspacePayload(workspaceSlug = "", status = "") {
115
+ const normalizedWorkspaceSlug = normalizeLowerText(workspaceSlug);
116
+ const normalizedStatus = normalizeRequestedWorkspaceStatus(status);
117
+ if (!normalizedWorkspaceSlug || !normalizedStatus) {
118
+ return null;
119
+ }
120
+ return {
121
+ slug: normalizedWorkspaceSlug,
122
+ status: normalizedStatus
123
+ };
124
+ }
125
+
126
+ function resolveRequestedWorkspaceStatusFromError(error) {
127
+ const statusCode = Number(error?.statusCode || error?.status || 0);
128
+ if (statusCode === 404) {
129
+ return REQUESTED_WORKSPACE_STATUS_NOT_FOUND;
130
+ }
131
+ if (statusCode === 403) {
132
+ return REQUESTED_WORKSPACE_STATUS_FORBIDDEN;
133
+ }
134
+ if (statusCode === 401) {
135
+ return REQUESTED_WORKSPACE_STATUS_UNAUTHENTICATED;
136
+ }
137
+ return "";
138
+ }
139
+
140
+ function resolveAppState(appConfig = {}, { workspaceInvitationsEnabled = true } = {}) {
141
+ const features = {
142
+ workspaceSwitching: normalizeBoolean(appConfig.workspaceSwitching, true),
143
+ workspaceInvites: workspaceInvitationsEnabled === true,
144
+ assistantEnabled: normalizeBoolean(appConfig.assistantEnabled, false),
145
+ assistantRequiredPermission: normalizeText(appConfig.assistantRequiredPermission),
146
+ socialEnabled: normalizeBoolean(appConfig.socialEnabled, false),
147
+ socialFederationEnabled: normalizeBoolean(appConfig.socialFederationEnabled, false)
148
+ };
149
+
150
+ return {
151
+ features
152
+ };
153
+ }
154
+
155
+ function normalizeSlugPolicy(value = "") {
156
+ const normalizedValue = normalizeLowerText(value);
157
+ if (
158
+ normalizedValue === WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME ||
159
+ normalizedValue === WORKSPACE_SLUG_POLICY_USER_SELECTED
160
+ ) {
161
+ return normalizedValue;
162
+ }
163
+ return WORKSPACE_SLUG_POLICY_NONE;
164
+ }
165
+
166
+ function isSupportedTenancyMode(value = "") {
167
+ return value === TENANCY_MODE_NONE || value === TENANCY_MODE_PERSONAL || value === TENANCY_MODE_WORKSPACE;
168
+ }
169
+
170
+ function resolveBootstrapTenancyProfile(tenancyProfile = null, appConfig = {}) {
171
+ const fallback = resolveTenancyProfile(appConfig);
172
+ const source = tenancyProfile && typeof tenancyProfile === "object" ? tenancyProfile : fallback;
173
+ const mode = isSupportedTenancyMode(source?.mode) ? source.mode : fallback.mode;
174
+ const workspace = source?.workspace && typeof source.workspace === "object" ? source.workspace : fallback.workspace;
175
+
176
+ return Object.freeze({
177
+ mode,
178
+ workspace: Object.freeze({
179
+ enabled: workspace.enabled === true,
180
+ autoProvision: workspace.autoProvision === true,
181
+ allowSelfCreate: workspace.allowSelfCreate === true,
182
+ slugPolicy: normalizeSlugPolicy(workspace.slugPolicy)
183
+ })
184
+ });
185
+ }
186
+
187
+ function createAnonymousBootstrapPayload({ appState, tenancyProfile }) {
188
+ return {
189
+ session: {
190
+ authenticated: false
191
+ },
192
+ profile: null,
193
+ tenancy: tenancyProfile,
194
+ app: appState,
195
+ workspaces: [],
196
+ pendingInvites: [],
197
+ activeWorkspace: null,
198
+ membership: null,
199
+ requestedWorkspace: null,
200
+ permissions: [],
201
+ surfaceAccess: {
202
+ consoleowner: false
203
+ },
204
+ workspaceSettings: null,
205
+ userSettings: null
206
+ };
207
+ }
208
+
209
+ function mapUserSettingsBootstrap(settings = {}) {
210
+ const source = settings && typeof settings === "object" ? settings : {};
211
+ const mapped = {};
212
+
213
+ for (const field of userSettingsFields) {
214
+ if (field.includeInBootstrap === false) {
215
+ continue;
216
+ }
217
+ const rawValue = Object.hasOwn(source, field.key)
218
+ ? source[field.key]
219
+ : field.resolveDefault({
220
+ settings: source
221
+ });
222
+ mapped[field.key] = field.normalizeOutput(rawValue, {
223
+ settings: source
224
+ });
225
+ }
226
+
227
+ return mapped;
228
+ }
229
+
230
+ function createWorkspaceBootstrapContributor({
231
+ workspaceService,
232
+ workspacePendingInvitationsService,
233
+ userProfilesRepository,
234
+ userSettingsRepository,
235
+ workspaceInvitationsEnabled = false,
236
+ appConfig = {},
237
+ tenancyProfile = null,
238
+ authService,
239
+ consoleService = null
240
+ } = {}) {
241
+ const contributorId = "users.bootstrap";
242
+ const appState = resolveAppState(appConfig, {
243
+ workspaceInvitationsEnabled
244
+ });
245
+ const resolvedTenancyProfile = resolveBootstrapTenancyProfile(tenancyProfile, appConfig);
246
+
247
+ requireServiceMethod(workspaceService, "listWorkspacesForUser", contributorId, {
248
+ serviceLabel: "workspaceService"
249
+ });
250
+ requireServiceMethod(workspaceService, "resolveWorkspaceContextForUserBySlug", contributorId, {
251
+ serviceLabel: "workspaceService"
252
+ });
253
+ if (workspaceInvitationsEnabled) {
254
+ requireServiceMethod(workspacePendingInvitationsService, "listPendingInvitesForUser", contributorId, {
255
+ serviceLabel: "workspacePendingInvitationsService"
256
+ });
257
+ }
258
+ requireServiceMethod(userProfilesRepository, "findByIdentity", contributorId, {
259
+ serviceLabel: "userProfilesRepository"
260
+ });
261
+ requireServiceMethod(userSettingsRepository, "ensureForUserId", contributorId, {
262
+ serviceLabel: "userSettingsRepository"
263
+ });
264
+
265
+ return Object.freeze({
266
+ contributorId,
267
+ async contribute({ request = null, reply = null, query = {} } = {}) {
268
+ const authResult = await request.executeAction({
269
+ actionId: "auth.session.read"
270
+ });
271
+
272
+ if (authResult?.clearSession === true && typeof authService?.clearSessionCookies === "function") {
273
+ authService.clearSessionCookies(reply);
274
+ }
275
+ if (authResult?.session && typeof authService?.writeSessionCookies === "function") {
276
+ authService.writeSessionCookies(reply, authResult.session);
277
+ }
278
+ if (authResult?.transientFailure === true) {
279
+ throw new AppError(503, "Authentication service temporarily unavailable. Please retry.");
280
+ }
281
+ let seededConsoleOwnerUserId = 0;
282
+ if (
283
+ authResult?.authenticated &&
284
+ authResult?.profile?.id != null &&
285
+ consoleService &&
286
+ typeof consoleService.ensureInitialConsoleMember === "function"
287
+ ) {
288
+ seededConsoleOwnerUserId = Number(await consoleService.ensureInitialConsoleMember(authResult.profile.id));
289
+ }
290
+
291
+ const user = authResult?.authenticated ? authResult.profile : null;
292
+ const normalizedUser = authenticatedUserValidator.normalize(user);
293
+ const pendingInvites =
294
+ workspaceInvitationsEnabled && normalizedUser
295
+ ? normalizePendingInvites(
296
+ await workspacePendingInvitationsService.listPendingInvitesForUser(normalizedUser, {
297
+ context: {
298
+ actor: normalizedUser
299
+ }
300
+ })
301
+ )
302
+ : [];
303
+ const normalizedWorkspaceSlug = resolveBootstrapWorkspaceSlug({ query, request });
304
+ let payload = createAnonymousBootstrapPayload({
305
+ appState,
306
+ tenancyProfile: resolvedTenancyProfile
307
+ });
308
+ if (normalizedWorkspaceSlug && !normalizedUser) {
309
+ payload = {
310
+ ...payload,
311
+ requestedWorkspace: createRequestedWorkspacePayload(
312
+ normalizedWorkspaceSlug,
313
+ REQUESTED_WORKSPACE_STATUS_UNAUTHENTICATED
314
+ )
315
+ };
316
+ }
317
+
318
+ if (normalizedUser) {
319
+ const latestProfile =
320
+ (await userProfilesRepository.findByIdentity({
321
+ provider: normalizedUser.authProvider,
322
+ providerUserId: normalizedUser.authProviderUserId
323
+ })) || normalizedUser;
324
+
325
+ const workspaces = await workspaceService.listWorkspacesForUser(latestProfile, { request });
326
+ let workspaceContext = null;
327
+ let requestedWorkspace = null;
328
+ if (normalizedWorkspaceSlug && resolvedTenancyProfile.mode !== TENANCY_MODE_NONE) {
329
+ try {
330
+ workspaceContext = await workspaceService.resolveWorkspaceContextForUserBySlug(
331
+ latestProfile,
332
+ normalizedWorkspaceSlug,
333
+ { request }
334
+ );
335
+ requestedWorkspace = createRequestedWorkspacePayload(
336
+ normalizedWorkspaceSlug,
337
+ REQUESTED_WORKSPACE_STATUS_RESOLVED
338
+ );
339
+ } catch (error) {
340
+ const requestedWorkspaceStatus = resolveRequestedWorkspaceStatusFromError(error);
341
+ if (!requestedWorkspaceStatus) {
342
+ throw error;
343
+ }
344
+ requestedWorkspace = createRequestedWorkspacePayload(normalizedWorkspaceSlug, requestedWorkspaceStatus);
345
+ }
346
+ }
347
+
348
+ const userSettings = await userSettingsRepository.ensureForUserId(latestProfile.id);
349
+ payload = {
350
+ session: {
351
+ authenticated: true,
352
+ userId: latestProfile.id
353
+ },
354
+ profile: {
355
+ displayName: latestProfile.displayName,
356
+ email: latestProfile.email,
357
+ avatar: accountAvatarFormatter(latestProfile, userSettings)
358
+ },
359
+ tenancy: resolvedTenancyProfile,
360
+ app: appState,
361
+ workspaces: [...workspaces],
362
+ pendingInvites,
363
+ activeWorkspace: workspaceContext
364
+ ? mapWorkspaceSummary(workspaceContext.workspace, {
365
+ roleId: workspaceContext.membership?.roleId,
366
+ status: workspaceContext.membership?.status
367
+ })
368
+ : null,
369
+ membership: mapMembershipSummary(workspaceContext?.membership, workspaceContext?.workspace),
370
+ requestedWorkspace,
371
+ permissions: workspaceContext ? [...workspaceContext.permissions] : [],
372
+ surfaceAccess: {
373
+ consoleowner: seededConsoleOwnerUserId > 0 && seededConsoleOwnerUserId === Number(latestProfile.id)
374
+ },
375
+ workspaceSettings: workspaceContext
376
+ ? mapWorkspaceSettingsPublic(workspaceContext.workspaceSettings, {
377
+ workspaceInvitationsEnabled
378
+ })
379
+ : null,
380
+ userSettings: mapUserSettingsBootstrap(userSettings),
381
+ requestMeta: {
382
+ hasRequest: Boolean(request)
383
+ }
384
+ };
385
+ }
386
+
387
+ const oauthCatalogPayload = getOAuthProviderCatalogPayload(authService);
388
+ const session = payload?.session && typeof payload.session === "object" ? payload.session : { authenticated: false };
389
+
390
+ return {
391
+ ...payload,
392
+ session: {
393
+ ...session,
394
+ ...oauthCatalogPayload
395
+ }
396
+ };
397
+ }
398
+ });
399
+ }
400
+
401
+ export { createWorkspaceBootstrapContributor };
@@ -0,0 +1,73 @@
1
+ import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
2
+ import { KERNEL_TOKENS } from "@jskit-ai/kernel/shared/support/tokens";
3
+ import { workspaceResource } from "../../shared/resources/workspaceResource.js";
4
+ import {
5
+ USERS_WORKSPACE_SELF_CREATE_ENABLED_TOKEN
6
+ } from "../common/diTokens.js";
7
+
8
+ function bootWorkspaceDirectoryRoutes(app) {
9
+ if (!app || typeof app.make !== "function" || typeof app.has !== "function") {
10
+ throw new Error("bootWorkspaceDirectoryRoutes requires application make()/has().");
11
+ }
12
+
13
+ const router = app.make(KERNEL_TOKENS.HttpRouter);
14
+ const workspaceSelfCreateEnabled = app.has(USERS_WORKSPACE_SELF_CREATE_ENABLED_TOKEN)
15
+ ? app.make(USERS_WORKSPACE_SELF_CREATE_ENABLED_TOKEN) === true
16
+ : false;
17
+
18
+ if (workspaceSelfCreateEnabled) {
19
+ router.register(
20
+ "POST",
21
+ "/api/workspaces",
22
+ {
23
+ auth: "required",
24
+ meta: {
25
+ tags: ["workspace"],
26
+ summary: "Create a workspace for the authenticated user"
27
+ },
28
+ bodyValidator: workspaceResource.operations.create.bodyValidator,
29
+ responseValidators: withStandardErrorResponses(
30
+ {
31
+ 200: workspaceResource.operations.create.outputValidator
32
+ },
33
+ { includeValidation400: true }
34
+ )
35
+ },
36
+ async function (request, reply) {
37
+ const body = request.input.body || {};
38
+ const response = await request.executeAction({
39
+ actionId: "workspace.workspaces.create",
40
+ input: {
41
+ name: body.name,
42
+ slug: body.slug
43
+ }
44
+ });
45
+ reply.code(200).send(response);
46
+ }
47
+ );
48
+ }
49
+
50
+ router.register(
51
+ "GET",
52
+ "/api/workspaces",
53
+ {
54
+ auth: "required",
55
+ meta: {
56
+ tags: ["workspace"],
57
+ summary: "List workspaces visible to authenticated user"
58
+ },
59
+ responseValidators: withStandardErrorResponses({
60
+ 200: workspaceResource.operations.list.outputValidator
61
+ })
62
+ },
63
+ async function (request, reply) {
64
+ const response = await request.executeAction({
65
+ actionId: "workspace.workspaces.list",
66
+ input: {}
67
+ });
68
+ reply.code(200).send(response);
69
+ }
70
+ );
71
+ }
72
+
73
+ export { bootWorkspaceDirectoryRoutes };
@@ -0,0 +1,19 @@
1
+ import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
2
+ import { workspaceDirectoryActions } from "./workspaceDirectoryActions.js";
3
+
4
+ function registerWorkspaceDirectory(app) {
5
+ if (!app || typeof app.singleton !== "function" || typeof app.actions !== "function") {
6
+ throw new Error("registerWorkspaceDirectory requires application singleton()/actions().");
7
+ }
8
+
9
+ app.actions(
10
+ withActionDefaults(workspaceDirectoryActions, {
11
+ domain: "workspace",
12
+ dependencies: {
13
+ workspaceService: "users.workspace.service"
14
+ }
15
+ })
16
+ );
17
+ }
18
+
19
+ export { registerWorkspaceDirectory };
@@ -0,0 +1,65 @@
1
+ import {
2
+ EMPTY_INPUT_VALIDATOR,
3
+ resolveRequest
4
+ } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
5
+ import { workspaceResource } from "../../shared/resources/workspaceResource.js";
6
+ import { resolveActionUser } from "../common/support/resolveActionUser.js";
7
+
8
+ const workspaceDirectoryActions = Object.freeze([
9
+ {
10
+ id: "workspace.workspaces.create",
11
+ version: 1,
12
+ kind: "command",
13
+ channels: ["api", "assistant_tool", "automation", "internal"],
14
+ surfacesFrom: "enabled",
15
+ permission: {
16
+ require: "authenticated"
17
+ },
18
+ inputValidator: workspaceResource.operations.create.bodyValidator,
19
+ outputValidator: workspaceResource.operations.create.outputValidator,
20
+ idempotency: "none",
21
+ audit: {
22
+ actionName: "workspace.workspaces.create"
23
+ },
24
+ observability: {},
25
+ extensions: {
26
+ assistant: {
27
+ description: "Create a workspace for the authenticated user."
28
+ }
29
+ },
30
+ async execute(input, context, deps) {
31
+ return deps.workspaceService.createWorkspaceForAuthenticatedUser(resolveActionUser(context, input), input, {
32
+ request: resolveRequest(context),
33
+ context
34
+ });
35
+ }
36
+ },
37
+ {
38
+ id: "workspace.workspaces.list",
39
+ version: 1,
40
+ kind: "query",
41
+ channels: ["api", "automation", "internal"],
42
+ surfacesFrom: "enabled",
43
+ permission: {
44
+ require: "authenticated"
45
+ },
46
+ inputValidator: EMPTY_INPUT_VALIDATOR,
47
+ outputValidator: workspaceResource.operations.list.outputValidator,
48
+ idempotency: "none",
49
+ audit: {
50
+ actionName: "workspace.workspaces.list"
51
+ },
52
+ observability: {},
53
+ async execute(input, context, deps) {
54
+ return {
55
+ items: await deps.workspaceService.listWorkspacesForAuthenticatedUser(resolveActionUser(context, input), {
56
+ request: resolveRequest(context),
57
+ context
58
+ }),
59
+ nextCursor: null
60
+ };
61
+ }
62
+ }
63
+ ]);
64
+
65
+ export { workspaceDirectoryActions };