@jskit-ai/users-core 0.1.48 → 0.1.49

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 (87) hide show
  1. package/package.descriptor.mjs +7 -7
  2. package/package.json +7 -17
  3. package/src/server/common/services/authProfileSyncService.js +28 -7
  4. package/src/server/common/support/realtimeServiceEvents.js +1 -59
  5. package/src/server/profileSyncLifecycleContributorRegistry.js +56 -0
  6. package/src/server/registerUsersBootstrap.js +0 -1
  7. package/src/server/registerUsersCore.js +2 -14
  8. package/src/server/usersBootstrapContributor.js +2 -64
  9. package/src/shared/index.js +2 -99
  10. package/src/shared/settings.js +1 -119
  11. package/test/authProfileSyncService.test.js +19 -10
  12. package/test/registerServiceRealtimeEvents.test.js +0 -86
  13. package/test/registerUsersCore.test.js +6 -15
  14. package/test/repositoryContracts.test.js +1 -9
  15. package/test/resourcesCanonical.test.js +0 -16
  16. package/test/settingsFieldRegistriesSingleton.test.js +0 -5
  17. package/test/usersBootstrapContributor.test.js +2 -26
  18. package/test/usersRouteResources.test.js +0 -16
  19. package/src/server/UsersWorkspacesServiceProvider.js +0 -44
  20. package/src/server/common/contributors/workspaceActionContextContributor.js +0 -88
  21. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +0 -34
  22. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +0 -78
  23. package/src/server/common/formatters/workspaceFormatter.js +0 -53
  24. package/src/server/common/repositories/workspaceInvitesRepository.js +0 -208
  25. package/src/server/common/repositories/workspaceMembershipsRepository.js +0 -190
  26. package/src/server/common/repositories/workspacesRepository.js +0 -202
  27. package/src/server/common/services/workspaceContextService.js +0 -281
  28. package/src/server/common/support/workspaceRoutePaths.js +0 -17
  29. package/src/server/common/validators/routeParamsValidator.js +0 -62
  30. package/src/server/registerWorkspaceBootstrap.js +0 -27
  31. package/src/server/registerWorkspaceCore.js +0 -73
  32. package/src/server/registerWorkspaceRepositories.js +0 -26
  33. package/src/server/support/resolveWorkspace.js +0 -16
  34. package/src/server/support/workspaceActionSurfaces.js +0 -118
  35. package/src/server/support/workspaceInvitationsPolicy.js +0 -45
  36. package/src/server/support/workspaceRouteInput.js +0 -22
  37. package/src/server/workspaceBootstrapContributor.js +0 -212
  38. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +0 -133
  39. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
  40. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +0 -133
  41. package/src/server/workspaceMembers/bootWorkspaceMembers.js +0 -236
  42. package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -108
  43. package/src/server/workspaceMembers/workspaceMembersActions.js +0 -186
  44. package/src/server/workspaceMembers/workspaceMembersService.js +0 -222
  45. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +0 -62
  46. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -119
  47. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +0 -74
  48. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +0 -138
  49. package/src/server/workspaceSettings/bootWorkspaceSettings.js +0 -76
  50. package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -62
  51. package/src/server/workspaceSettings/workspaceSettingsActions.js +0 -72
  52. package/src/server/workspaceSettings/workspaceSettingsRepository.js +0 -154
  53. package/src/server/workspaceSettings/workspaceSettingsService.js +0 -66
  54. package/src/shared/resources/workspaceMembersResource.js +0 -354
  55. package/src/shared/resources/workspacePendingInvitationsResource.js +0 -82
  56. package/src/shared/resources/workspaceResource.js +0 -176
  57. package/src/shared/resources/workspaceSettingsFields.js +0 -59
  58. package/src/shared/resources/workspaceSettingsResource.js +0 -169
  59. package/src/shared/roles.js +0 -161
  60. package/src/shared/support/usersApiPaths.js +0 -43
  61. package/src/shared/support/usersVisibility.js +0 -42
  62. package/src/shared/support/workspacePathModel.js +0 -145
  63. package/src/shared/tenancyMode.js +0 -35
  64. package/src/shared/tenancyProfile.js +0 -73
  65. package/test/registerWorkspaceDirectory.test.js +0 -31
  66. package/test/registerWorkspaceSettings.test.js +0 -40
  67. package/test/roles.test.js +0 -159
  68. package/test/tenancyProfile.test.js +0 -67
  69. package/test/usersApiPaths.test.js +0 -49
  70. package/test/usersRouteValidators.test.js +0 -49
  71. package/test/usersVisibility.test.js +0 -27
  72. package/test/workspaceActionContextContributor.test.js +0 -344
  73. package/test/workspaceActionSurfaces.test.js +0 -85
  74. package/test/workspaceAuthPolicyContextResolver.test.js +0 -119
  75. package/test/workspaceBootstrapContributor.test.js +0 -154
  76. package/test/workspaceInvitationsPolicy.test.js +0 -71
  77. package/test/workspaceInvitesRepository.test.js +0 -111
  78. package/test/workspaceMembersService.test.js +0 -398
  79. package/test/workspacePathModel.test.js +0 -93
  80. package/test/workspacePendingInvitationsResource.test.js +0 -38
  81. package/test/workspacePendingInvitationsService.test.js +0 -151
  82. package/test/workspaceRouteVisibilityResolver.test.js +0 -83
  83. package/test/workspaceService.test.js +0 -546
  84. package/test/workspaceSettingsActions.test.js +0 -52
  85. package/test/workspaceSettingsRepository.test.js +0 -202
  86. package/test/workspaceSettingsResource.test.js +0 -169
  87. package/test/workspaceSettingsService.test.js +0 -140
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { createService } from "../src/server/common/services/authProfileSyncService.js";
4
4
 
5
- test("authProfileSyncService.syncIdentityProfile uses shared transaction for profile upsert and provisioning", async () => {
5
+ test("authProfileSyncService.syncIdentityProfile uses shared transaction for profile upsert and lifecycle contributors", async () => {
6
6
  const calls = [];
7
7
  const transaction = { trxId: "tx-1" };
8
8
 
@@ -33,11 +33,14 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
33
33
  return { userId: Number(userId) };
34
34
  }
35
35
  },
36
- workspaceProvisioningService: {
37
- async provisionWorkspaceForNewUser(_profile, options = {}) {
38
- calls.push({ step: "provision", trx: options.trx || null });
36
+ lifecycleContributors: [
37
+ {
38
+ contributorId: "test.lifecycle",
39
+ async afterIdentityProfileSynced({ created, options = {} } = {}) {
40
+ calls.push({ step: "lifecycle", created, trx: options.trx || null });
41
+ }
39
42
  }
40
- }
43
+ ]
41
44
  });
42
45
 
43
46
  const profile = await service.syncIdentityProfile({
@@ -52,7 +55,8 @@ test("authProfileSyncService.syncIdentityProfile uses shared transaction for pro
52
55
  assert.equal(calls[1].step, "find");
53
56
  assert.equal(calls[2].step, "upsert");
54
57
  assert.equal(calls[3].step, "ensureUserSettings");
55
- assert.equal(calls[4].step, "provision");
58
+ assert.equal(calls[4].step, "lifecycle");
59
+ assert.equal(calls[4].created, true);
56
60
  assert.equal(calls[1].trx, transaction);
57
61
  assert.equal(calls[2].trx, transaction);
58
62
  assert.equal(calls[3].trx, transaction);
@@ -87,11 +91,16 @@ test("authProfileSyncService.syncIdentityProfile skips write path when profile i
87
91
  return { userId: "7" };
88
92
  }
89
93
  },
90
- workspaceProvisioningService: {
91
- async provisionWorkspaceForNewUser() {
92
- provisionCalls += 1;
94
+ lifecycleContributors: [
95
+ {
96
+ contributorId: "test.lifecycle",
97
+ async afterIdentityProfileSynced({ created } = {}) {
98
+ if (created) {
99
+ provisionCalls += 1;
100
+ }
101
+ }
93
102
  }
94
- }
103
+ ]
95
104
  });
96
105
 
97
106
  const profile = await service.syncIdentityProfile({
@@ -3,8 +3,6 @@ import test from "node:test";
3
3
  import { registerAccountProfile } from "../src/server/accountProfile/registerAccountProfile.js";
4
4
  import { registerAccountPreferences } from "../src/server/accountPreferences/registerAccountPreferences.js";
5
5
  import { registerAccountNotifications } from "../src/server/accountNotifications/registerAccountNotifications.js";
6
- import { registerWorkspaceMembers } from "../src/server/workspaceMembers/registerWorkspaceMembers.js";
7
- import { registerWorkspacePendingInvitations } from "../src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js";
8
6
 
9
7
  function createAppDouble() {
10
8
  const serviceCalls = [];
@@ -57,87 +55,3 @@ test("account register functions publish account.settings.changed for update ope
57
55
  assert.equal(notifications?.metadata?.events?.updateNotifications?.[0]?.realtime?.event, "account.settings.changed");
58
56
  assert.equal(notifications?.metadata?.events?.updateNotifications?.[1]?.realtime?.event, "users.bootstrap.changed");
59
57
  });
60
-
61
- test("workspace register functions publish members/invites/workspace-list realtime events", async () => {
62
- const membersApp = createAppDouble();
63
- registerWorkspaceMembers(membersApp.app);
64
- const members = findServiceCall(membersApp.serviceCalls, "users.workspace.members.service");
65
- assert.equal(members?.metadata?.events?.updateMemberRole?.[0]?.realtime?.event, "workspace.members.changed");
66
- assert.equal(members?.metadata?.events?.updateMemberRole?.[1]?.realtime?.event, "users.bootstrap.changed");
67
- assert.equal(members?.metadata?.events?.removeMember?.[0]?.realtime?.event, "workspace.members.changed");
68
- assert.equal(members?.metadata?.events?.removeMember?.[1]?.realtime?.event, "users.bootstrap.changed");
69
- assert.equal(members?.metadata?.events?.createInvite?.[0]?.realtime?.event, "workspace.invites.changed");
70
- assert.equal(members?.metadata?.events?.createInvite?.[1]?.realtime?.event, "users.bootstrap.changed");
71
- assert.equal(members?.metadata?.events?.createInvite?.[1]?.entityId?.({ result: { createdInviteId: "91" } }), "91");
72
- assert.equal(members?.metadata?.events?.createInvite?.[1]?.realtime?.audience?.preset, "event_scope");
73
- assert.equal(typeof members?.metadata?.events?.createInvite?.[1]?.realtime?.audience?.userQuery, "function");
74
- const createInviteAudienceQueryResult = await members?.metadata?.events?.createInvite?.[1]?.realtime?.audience?.userQuery({
75
- knex() {
76
- return {
77
- join() {
78
- return this;
79
- },
80
- where(field, value) {
81
- assert.equal(field, "wi.id");
82
- assert.equal(value, "91");
83
- return this;
84
- },
85
- async first() {
86
- return {
87
- user_id: 55
88
- };
89
- }
90
- };
91
- },
92
- event: {
93
- entityId: "91"
94
- }
95
- });
96
- assert.deepEqual(createInviteAudienceQueryResult, [{ userId: "55" }]);
97
- assert.equal(members?.metadata?.events?.revokeInvite?.[0]?.realtime?.event, "workspace.invites.changed");
98
- assert.equal(members?.metadata?.events?.revokeInvite?.[1]?.realtime?.event, "users.bootstrap.changed");
99
- assert.equal(members?.metadata?.events?.revokeInvite?.[1]?.entityId?.({ result: { revokedInviteId: "19" } }), "19");
100
- assert.equal(members?.metadata?.events?.revokeInvite?.[1]?.realtime?.audience?.preset, "event_scope");
101
- assert.equal(typeof members?.metadata?.events?.revokeInvite?.[1]?.realtime?.audience?.userQuery, "function");
102
-
103
- const pendingApp = createAppDouble();
104
- registerWorkspacePendingInvitations(pendingApp.app);
105
- const pending = findServiceCall(pendingApp.serviceCalls, "users.workspace.pending-invitations.service");
106
- const acceptInviteEvents = Array.isArray(pending?.metadata?.events?.acceptInviteByToken)
107
- ? pending.metadata.events.acceptInviteByToken
108
- : [];
109
- const acceptInviteRealtimeEvents = acceptInviteEvents.map((entry) => entry?.realtime?.event).filter(Boolean);
110
- assert.ok(acceptInviteRealtimeEvents.includes("workspace.invitations.pending.changed"));
111
- assert.ok(acceptInviteRealtimeEvents.includes("users.bootstrap.changed"));
112
- assert.ok(acceptInviteRealtimeEvents.includes("workspaces.changed"));
113
- assert.ok(acceptInviteRealtimeEvents.includes("workspace.members.changed"));
114
- assert.ok(acceptInviteRealtimeEvents.includes("workspace.invites.changed"));
115
-
116
- const acceptedMembersChange = acceptInviteEvents.find(
117
- (entry) => entry?.realtime?.event === "workspace.members.changed"
118
- );
119
- assert.equal(acceptedMembersChange?.entityId?.({ result: { workspaceId: "9" } }), "9");
120
- assert.deepEqual(
121
- acceptedMembersChange?.realtime?.audience?.({
122
- event: {
123
- entityId: "9"
124
- }
125
- }),
126
- {
127
- workspaceId: "9"
128
- }
129
- );
130
-
131
- const acceptedInvitesChange = acceptInviteEvents.find(
132
- (entry) => entry?.realtime?.event === "workspace.invites.changed"
133
- );
134
- assert.equal(acceptedInvitesChange?.entityId?.({ result: { workspaceId: "9" } }), "9");
135
-
136
- const refuseInviteEvents = Array.isArray(pending?.metadata?.events?.refuseInviteByToken)
137
- ? pending.metadata.events.refuseInviteByToken
138
- : [];
139
- const refuseInviteRealtimeEvents = refuseInviteEvents.map((entry) => entry?.realtime?.event).filter(Boolean);
140
- assert.ok(refuseInviteRealtimeEvents.includes("workspace.invitations.pending.changed"));
141
- assert.ok(refuseInviteRealtimeEvents.includes("users.bootstrap.changed"));
142
- assert.ok(refuseInviteRealtimeEvents.includes("workspace.invites.changed"));
143
- });
@@ -2,28 +2,19 @@ import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { registerUsersCore } from "../src/server/registerUsersCore.js";
4
4
 
5
- test("registerUsersCore registers the workspace action surface alias when action runtime is available", () => {
6
- const calls = [];
5
+ test("registerUsersCore only binds users-core singletons", () => {
6
+ const singletonTokens = [];
7
7
  const app = {
8
- singleton() {
9
- return this;
10
- },
11
- actionSurfaceSource(sourceName, resolver) {
12
- calls.push({
13
- sourceName: String(sourceName || ""),
14
- resolverType: typeof resolver
15
- });
8
+ singleton(token) {
9
+ singletonTokens.push(String(token || ""));
16
10
  return this;
17
11
  }
18
12
  };
19
13
 
20
14
  registerUsersCore(app);
21
15
 
22
- assert.deepEqual(calls, [
23
- {
24
- sourceName: "workspace",
25
- resolverType: "function"
26
- }
16
+ assert.deepEqual(singletonTokens.sort(), [
17
+ "users.profile.sync.service"
27
18
  ]);
28
19
  });
29
20
 
@@ -2,10 +2,6 @@ import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { createRepository as createUsersRepository } from "../src/server/common/repositories/usersRepository.js";
4
4
  import { createRepository as createUserSettingsRepository } from "../src/server/common/repositories/userSettingsRepository.js";
5
- import { createRepository as createWorkspaceInvitesRepository } from "../src/server/common/repositories/workspaceInvitesRepository.js";
6
- import { createRepository as createWorkspaceMembershipsRepository } from "../src/server/common/repositories/workspaceMembershipsRepository.js";
7
- import { createRepository as createWorkspacesRepository } from "../src/server/common/repositories/workspacesRepository.js";
8
- import { createRepository as createWorkspaceSettingsRepository } from "../src/server/workspaceSettings/workspaceSettingsRepository.js";
9
5
 
10
6
  function createKnexStub() {
11
7
  const knex = Object.assign(() => {
@@ -23,11 +19,7 @@ test("users-core repositories expose withTransaction", async () => {
23
19
  const knex = createKnexStub();
24
20
  const repositories = [
25
21
  createUsersRepository(knex),
26
- createUserSettingsRepository(knex),
27
- createWorkspaceInvitesRepository(knex),
28
- createWorkspaceMembershipsRepository(knex),
29
- createWorkspacesRepository(knex),
30
- createWorkspaceSettingsRepository(knex)
22
+ createUserSettingsRepository(knex)
31
23
  ];
32
24
 
33
25
  for (const repository of repositories) {
@@ -4,9 +4,6 @@ import path from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import "../test-support/registerDefaultSettingsFields.js";
7
- import { workspaceMembersResource } from "../src/shared/resources/workspaceMembersResource.js";
8
- import { workspaceResource } from "../src/shared/resources/workspaceResource.js";
9
- import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
10
7
  import { userProfileResource } from "../src/shared/resources/userProfileResource.js";
11
8
  import { userSettingsResource } from "../src/shared/resources/userSettingsResource.js";
12
9
 
@@ -30,8 +27,6 @@ function assertResourceOperationMessages(resource, operationName, label) {
30
27
 
31
28
  test("users-core resources expose messages for all operations", () => {
32
29
  const resources = {
33
- workspace: workspaceResource,
34
- workspaceSettings: workspaceSettingsResource,
35
30
  userProfile: userProfileResource,
36
31
  userSettings: userSettingsResource
37
32
  };
@@ -44,18 +39,7 @@ test("users-core resources expose messages for all operations", () => {
44
39
  });
45
40
 
46
41
  test("users-core specialized resource operations expose messages and validators", () => {
47
- const workspaceMembersOperationSpecs = [
48
- { label: "workspaceMembers.rolesList", operation: workspaceMembersResource.operations.rolesList },
49
- { label: "workspaceMembers.membersList", operation: workspaceMembersResource.operations.membersList },
50
- { label: "workspaceMembers.updateMemberRole", operation: workspaceMembersResource.operations.updateMemberRole },
51
- { label: "workspaceMembers.removeMember", operation: workspaceMembersResource.operations.removeMember },
52
- { label: "workspaceMembers.invitesList", operation: workspaceMembersResource.operations.invitesList },
53
- { label: "workspaceMembers.createInvite", operation: workspaceMembersResource.operations.createInvite },
54
- { label: "workspaceMembers.revokeInvite", operation: workspaceMembersResource.operations.revokeInvite },
55
- { label: "workspaceMembers.redeemInvite", operation: workspaceMembersResource.operations.redeemInvite }
56
- ];
57
42
  const operationSpecs = [
58
- ...workspaceMembersOperationSpecs,
59
43
  { label: "userProfile.avatarUpload", operation: userProfileResource.operations.avatarUpload },
60
44
  { label: "userProfile.avatarDelete", operation: userProfileResource.operations.avatarDelete },
61
45
  { label: "userSettings.passwordChange", operation: userSettingsResource.operations.passwordChange },
@@ -6,13 +6,8 @@ async function importWithIdentity(url, identity) {
6
6
  }
7
7
 
8
8
  test("settings field registries stay shared across module identities", async () => {
9
- const workspaceModuleUrl = new URL("../src/shared/resources/workspaceSettingsFields.js", import.meta.url);
10
9
  const userModuleUrl = new URL("../src/shared/resources/userSettingsFields.js", import.meta.url);
11
10
 
12
- const workspaceA = await importWithIdentity(workspaceModuleUrl, "workspace-a");
13
- const workspaceB = await importWithIdentity(workspaceModuleUrl, "workspace-b");
14
- assert.equal(workspaceA.workspaceSettingsFields, workspaceB.workspaceSettingsFields);
15
-
16
11
  const userA = await importWithIdentity(userModuleUrl, "user-a");
17
12
  const userB = await importWithIdentity(userModuleUrl, "user-b");
18
13
  assert.equal(userA.userSettingsFields, userB.userSettingsFields);
@@ -1,10 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { createUsersBootstrapContributor } from "../src/server/usersBootstrapContributor.js";
4
- import {
5
- TENANCY_MODE_PERSONAL,
6
- WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
7
- } from "../src/shared/tenancyProfile.js";
8
4
 
9
5
  function createAuthenticatedProfile(overrides = {}) {
10
6
  return {
@@ -93,7 +89,7 @@ test("users bootstrap contributor exposes the generic authenticated bootstrap pa
93
89
  assert.deepEqual(payload.surfaceAccess, {
94
90
  consoleowner: true
95
91
  });
96
- assert.equal(payload.app.features.workspaceSwitching, false);
92
+ assert.equal(payload.app.features.assistantEnabled, false);
97
93
  assert.deepEqual(payload.session.oauthProviders, [
98
94
  {
99
95
  id: "google",
@@ -101,12 +97,11 @@ test("users bootstrap contributor exposes the generic authenticated bootstrap pa
101
97
  }
102
98
  ]);
103
99
  assert.equal(payload.session.oauthDefaultProvider, "google");
104
- assert.deepEqual(payload.workspaces, []);
105
100
  assert.deepEqual(payload.userSettings, {});
106
101
  assert.equal(payload.requestMeta.hasRequest, true);
107
102
  });
108
103
 
109
- test("users bootstrap contributor emits canonical tenancy profile for anonymous bootstrap", async () => {
104
+ test("users bootstrap contributor emits anonymous bootstrap payload without workspace fields", async () => {
110
105
  const contributor = createUsersBootstrapContributor({
111
106
  usersRepository: {
112
107
  async findById() {
@@ -118,15 +113,6 @@ test("users bootstrap contributor emits canonical tenancy profile for anonymous
118
113
  return createUserSettings();
119
114
  }
120
115
  },
121
- tenancyProfile: {
122
- mode: TENANCY_MODE_PERSONAL,
123
- workspace: {
124
- enabled: true,
125
- autoProvision: true,
126
- allowSelfCreate: false,
127
- slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
128
- }
129
- },
130
116
  appConfig: {
131
117
  tenancyMode: "none"
132
118
  },
@@ -156,21 +142,11 @@ test("users bootstrap contributor emits canonical tenancy profile for anonymous
156
142
  reply: {}
157
143
  });
158
144
 
159
- assert.deepEqual(payload.tenancy, {
160
- mode: TENANCY_MODE_PERSONAL,
161
- workspace: {
162
- enabled: true,
163
- autoProvision: true,
164
- allowSelfCreate: false,
165
- slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
166
- }
167
- });
168
145
  assert.deepEqual(payload.session, {
169
146
  authenticated: false,
170
147
  oauthProviders: [],
171
148
  oauthDefaultProvider: null
172
149
  });
173
- assert.deepEqual(payload.workspaces, []);
174
150
  assert.deepEqual(payload.surfaceAccess, {
175
151
  consoleowner: false
176
152
  });
@@ -7,9 +7,6 @@ import { deriveResourceRequiredMetadata } from "@jskit-ai/kernel/_testable";
7
7
  import "../test-support/registerDefaultSettingsFields.js";
8
8
  import { userProfileResource } from "../src/shared/resources/userProfileResource.js";
9
9
  import { userSettingsResource } from "../src/shared/resources/userSettingsResource.js";
10
- import { workspaceMembersResource } from "../src/shared/resources/workspaceMembersResource.js";
11
- import { workspaceResource } from "../src/shared/resources/workspaceResource.js";
12
- import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
13
10
 
14
11
  function assertResourceShape(resource, label) {
15
12
  assert.ok(resource, `${label} resource must exist.`);
@@ -48,8 +45,6 @@ function assertResourceShape(resource, label) {
48
45
 
49
46
  test("workspace and account resources expose canonical validators", () => {
50
47
  const resourcesByLabel = {
51
- workspace: workspaceResource,
52
- workspaceSettings: workspaceSettingsResource,
53
48
  userProfile: userProfileResource,
54
49
  userSettings: userSettingsResource
55
50
  };
@@ -60,18 +55,7 @@ test("workspace and account resources expose canonical validators", () => {
60
55
  });
61
56
 
62
57
  test("specialized settings and invite operations expose canonical validators", () => {
63
- const workspaceMembersOperationSpecs = [
64
- { label: "workspaceMembers.rolesList", operation: workspaceMembersResource.operations.rolesList },
65
- { label: "workspaceMembers.membersList", operation: workspaceMembersResource.operations.membersList },
66
- { label: "workspaceMembers.updateMemberRole", operation: workspaceMembersResource.operations.updateMemberRole },
67
- { label: "workspaceMembers.removeMember", operation: workspaceMembersResource.operations.removeMember },
68
- { label: "workspaceMembers.invitesList", operation: workspaceMembersResource.operations.invitesList },
69
- { label: "workspaceMembers.createInvite", operation: workspaceMembersResource.operations.createInvite },
70
- { label: "workspaceMembers.revokeInvite", operation: workspaceMembersResource.operations.revokeInvite },
71
- { label: "workspaceMembers.redeemInvite", operation: workspaceMembersResource.operations.redeemInvite }
72
- ];
73
58
  const operationSpecs = [
74
- ...workspaceMembersOperationSpecs,
75
59
  { label: "userProfile.avatarUpload", operation: userProfileResource.operations.avatarUpload },
76
60
  { label: "userProfile.avatarDelete", operation: userProfileResource.operations.avatarDelete },
77
61
  { label: "userSettings.passwordChange", operation: userSettingsResource.operations.passwordChange },
@@ -1,44 +0,0 @@
1
- import { bootWorkspaceDirectoryRoutes } from "./workspaceDirectory/bootWorkspaceDirectoryRoutes.js";
2
- import { registerWorkspaceDirectory } from "./workspaceDirectory/registerWorkspaceDirectory.js";
3
- import {
4
- registerWorkspacePendingInvitations
5
- } from "./workspacePendingInvitations/registerWorkspacePendingInvitations.js";
6
- import { bootWorkspacePendingInvitations } from "./workspacePendingInvitations/bootWorkspacePendingInvitations.js";
7
- import { registerWorkspaceMembers } from "./workspaceMembers/registerWorkspaceMembers.js";
8
- import { bootWorkspaceMembers } from "./workspaceMembers/bootWorkspaceMembers.js";
9
- import { registerWorkspaceSettings } from "./workspaceSettings/registerWorkspaceSettings.js";
10
- import { bootWorkspaceSettings } from "./workspaceSettings/bootWorkspaceSettings.js";
11
- import { registerWorkspaceRepositories } from "./registerWorkspaceRepositories.js";
12
- import { registerWorkspaceCore } from "./registerWorkspaceCore.js";
13
- import { registerWorkspaceBootstrap } from "./registerWorkspaceBootstrap.js";
14
-
15
- class UsersWorkspacesServiceProvider {
16
- static id = "workspaces.core";
17
-
18
- static dependsOn = ["users.core"];
19
-
20
- register(app) {
21
- registerWorkspaceRepositories(app);
22
- registerWorkspaceCore(app);
23
- registerWorkspaceBootstrap(app);
24
- registerWorkspaceDirectory(app);
25
- registerWorkspaceMembers(app);
26
- registerWorkspaceSettings(app);
27
- registerWorkspacePendingInvitations(app);
28
- }
29
-
30
- async boot(app) {
31
- if (app.make("users.workspace.enabled") !== true) {
32
- return;
33
- }
34
-
35
- bootWorkspaceDirectoryRoutes(app);
36
- if (app.make("users.workspace.invitations.enabled") === true) {
37
- bootWorkspacePendingInvitations(app);
38
- }
39
- bootWorkspaceSettings(app);
40
- bootWorkspaceMembers(app);
41
- }
42
- }
43
-
44
- export { UsersWorkspacesServiceProvider };
@@ -1,88 +0,0 @@
1
- import {
2
- normalizeObject,
3
- requireServiceMethod
4
- } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
5
- import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
6
- import {
7
- checkRouteVisibility,
8
- USERS_ROUTE_VISIBILITY_PUBLIC,
9
- USERS_ROUTE_VISIBILITY_WORKSPACE,
10
- USERS_ROUTE_VISIBILITY_WORKSPACE_USER
11
- } from "../../../shared/support/usersVisibility.js";
12
- import { resolveActionUser } from "../support/resolveActionUser.js";
13
- const WORKSPACE_VISIBILITY_ACTION_CONTEXT_SET = new Set([
14
- USERS_ROUTE_VISIBILITY_WORKSPACE,
15
- USERS_ROUTE_VISIBILITY_WORKSPACE_USER
16
- ]);
17
-
18
- function normalizeWorkspaceSurfaceIds(surfaceIds = []) {
19
- const source = Array.isArray(surfaceIds) ? surfaceIds : [];
20
- const normalized = new Set();
21
-
22
- for (const entry of source) {
23
- const surfaceId = normalizeSurfaceId(entry);
24
- if (!surfaceId) {
25
- continue;
26
- }
27
- normalized.add(surfaceId);
28
- }
29
-
30
- return normalized;
31
- }
32
-
33
- function createWorkspaceActionContextContributor({ workspaceService, workspaceSurfaceIds = [] } = {}) {
34
- const contributorId = "users.workspace.context";
35
- const workspaceSurfaceIdSet = normalizeWorkspaceSurfaceIds(workspaceSurfaceIds);
36
-
37
- requireServiceMethod(workspaceService, "resolveWorkspaceContextForUserBySlug", contributorId);
38
-
39
- return Object.freeze({
40
- contributorId,
41
- async contribute({ definition = null, input, context, request } = {}) {
42
- const payload = normalizeObject(input);
43
- if (!Object.hasOwn(payload, "workspaceSlug")) {
44
- return {};
45
- }
46
-
47
- const actionSurfaces = Array.isArray(definition?.surfaces) ? definition.surfaces : [];
48
- const hasWorkspaceActionSurface = actionSurfaces.some((surfaceId) => workspaceSurfaceIdSet.has(surfaceId));
49
- const routeSurfaceId = normalizeSurfaceId(request?.routeOptions?.config?.surface);
50
- const hasWorkspaceSurface = workspaceSurfaceIdSet.has(routeSurfaceId);
51
- const routeVisibilityInput =
52
- request && request.routeOptions && request.routeOptions.config
53
- ? request.routeOptions.config.visibility
54
- : USERS_ROUTE_VISIBILITY_PUBLIC;
55
- const routeVisibility = checkRouteVisibility(routeVisibilityInput);
56
- const hasWorkspaceRouteVisibility = WORKSPACE_VISIBILITY_ACTION_CONTEXT_SET.has(routeVisibility);
57
- if (!hasWorkspaceActionSurface && !hasWorkspaceRouteVisibility && !hasWorkspaceSurface) {
58
- return {};
59
- }
60
-
61
- const resolvedWorkspaceContext = await workspaceService.resolveWorkspaceContextForUserBySlug(
62
- resolveActionUser(context, payload),
63
- payload.workspaceSlug,
64
- { request }
65
- );
66
-
67
- const contribution = {
68
- requestMeta: {
69
- resolvedWorkspaceContext
70
- }
71
- };
72
-
73
- if (!context?.workspace) {
74
- contribution.workspace = resolvedWorkspaceContext.workspace;
75
- }
76
- if (!context?.membership) {
77
- contribution.membership = resolvedWorkspaceContext.membership;
78
- }
79
- if (!Array.isArray(context?.permissions) || context.permissions.length < 1) {
80
- contribution.permissions = resolvedWorkspaceContext.permissions;
81
- }
82
-
83
- return contribution;
84
- }
85
- });
86
- }
87
-
88
- export { createWorkspaceActionContextContributor };
@@ -1,34 +0,0 @@
1
- import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
-
3
- function createWorkspaceAuthPolicyContextResolver({ workspaceService } = {}) {
4
- if (!workspaceService || typeof workspaceService.resolveWorkspaceContextForUserBySlug !== "function") {
5
- throw new Error(
6
- "workspace auth policy context resolver requires workspaceService.resolveWorkspaceContextForUserBySlug()."
7
- );
8
- }
9
-
10
- return async function resolveWorkspaceAuthPolicyContext({ request, actor, meta } = {}) {
11
- const contextPolicy = normalizeText(meta?.contextPolicy || "none").toLowerCase() || "none";
12
- const permission = normalizeText(meta?.permission);
13
- if (contextPolicy === "none" && !permission) {
14
- return {};
15
- }
16
-
17
- const workspaceSlug = normalizeText(request?.params?.workspaceSlug).toLowerCase();
18
- if (!workspaceSlug || !actor) {
19
- return {};
20
- }
21
-
22
- const resolvedWorkspaceContext = await workspaceService.resolveWorkspaceContextForUserBySlug(actor, workspaceSlug, {
23
- request
24
- });
25
-
26
- return {
27
- workspace: resolvedWorkspaceContext?.workspace || null,
28
- membership: resolvedWorkspaceContext?.membership || null,
29
- permissions: Array.isArray(resolvedWorkspaceContext?.permissions) ? resolvedWorkspaceContext.permissions : []
30
- };
31
- };
32
- }
33
-
34
- export { createWorkspaceAuthPolicyContextResolver };
@@ -1,78 +0,0 @@
1
- import { normalizeOpaqueId, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
-
3
- function buildVisibilityContribution({ visibility, scopeOwnerId = null, userId = null } = {}) {
4
- const requiresActorScope = visibility === "workspace_user";
5
- const contribution = {
6
- scopeKind: requiresActorScope ? "workspace_user" : "workspace",
7
- requiresActorScope
8
- };
9
-
10
- if (scopeOwnerId) {
11
- contribution.scopeOwnerId = scopeOwnerId;
12
- }
13
- if (requiresActorScope && userId != null) {
14
- contribution.userId = userId;
15
- }
16
-
17
- return contribution;
18
- }
19
-
20
- function createWorkspaceRouteVisibilityResolver({ workspaceService } = {}) {
21
- if (!workspaceService || typeof workspaceService.resolveWorkspaceContextForUserBySlug !== "function") {
22
- throw new Error("workspace route visibility resolver requires workspaceService.resolveWorkspaceContextForUserBySlug().");
23
- }
24
-
25
- return Object.freeze({
26
- resolverId: "users.workspace.visibility",
27
- async resolve({ visibility, context, request, input } = {}) {
28
- if (visibility !== "workspace" && visibility !== "workspace_user") {
29
- return {};
30
- }
31
-
32
- const actor = context?.actor || request?.user || null;
33
- const userId = normalizeOpaqueId(actor?.id);
34
- const workspace =
35
- context?.workspace || context?.requestMeta?.resolvedWorkspaceContext?.workspace || request?.workspace || null;
36
- const scopeOwnerId = normalizeRecordId(workspace?.id, { fallback: null });
37
- if (!scopeOwnerId) {
38
- const workspaceSlug = normalizeText(input?.workspaceSlug).toLowerCase();
39
-
40
- if (!workspaceSlug || !actor) {
41
- return visibility === "workspace_user"
42
- ? buildVisibilityContribution({
43
- visibility,
44
- userId
45
- })
46
- : {};
47
- }
48
-
49
- const resolvedWorkspaceContext = await workspaceService.resolveWorkspaceContextForUserBySlug(actor, workspaceSlug, {
50
- request
51
- });
52
- const resolvedWorkspaceOwnerId = normalizeRecordId(resolvedWorkspaceContext?.workspace?.id, { fallback: null });
53
- if (!resolvedWorkspaceOwnerId) {
54
- return visibility === "workspace_user"
55
- ? buildVisibilityContribution({
56
- visibility,
57
- userId
58
- })
59
- : {};
60
- }
61
-
62
- return buildVisibilityContribution({
63
- visibility,
64
- scopeOwnerId: resolvedWorkspaceOwnerId,
65
- userId
66
- });
67
- }
68
-
69
- return buildVisibilityContribution({
70
- visibility,
71
- scopeOwnerId,
72
- userId
73
- });
74
- }
75
- });
76
- }
77
-
78
- export { createWorkspaceRouteVisibilityResolver };