@jskit-ai/users-core 0.1.31 → 0.1.33

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 (55) hide show
  1. package/package.descriptor.mjs +21 -19
  2. package/package.json +6 -6
  3. package/src/server/UsersCoreServiceProvider.js +1 -3
  4. package/src/server/accountNotifications/accountNotificationsService.js +3 -3
  5. package/src/server/accountNotifications/registerAccountNotifications.js +1 -1
  6. package/src/server/accountPreferences/accountPreferencesService.js +3 -3
  7. package/src/server/accountPreferences/registerAccountPreferences.js +1 -1
  8. package/src/server/accountProfile/accountProfileActions.js +8 -2
  9. package/src/server/accountProfile/accountProfileService.js +10 -10
  10. package/src/server/accountProfile/avatarService.js +26 -67
  11. package/src/server/accountProfile/avatarStorageService.js +14 -95
  12. package/src/server/accountProfile/bootAccountProfileRoutes.js +13 -15
  13. package/src/server/accountProfile/registerAccountProfile.js +2 -2
  14. package/src/server/accountSecurity/accountSecurityService.js +3 -3
  15. package/src/server/accountSecurity/registerAccountSecurity.js +1 -1
  16. package/src/server/common/contributors/workspaceActionContextContributor.js +24 -17
  17. package/src/server/common/formatters/workspaceFormatter.js +2 -2
  18. package/src/server/common/registerCommonRepositories.js +3 -3
  19. package/src/server/common/repositories/{userProfilesRepository.js → usersRepository.js} +7 -7
  20. package/src/server/common/repositories/workspaceInvitesRepository.js +2 -2
  21. package/src/server/common/repositories/workspaceMembershipsRepository.js +9 -9
  22. package/src/server/common/repositories/workspacesRepository.js +2 -2
  23. package/src/server/common/services/accountContextService.js +4 -4
  24. package/src/server/common/services/authProfileSyncService.js +15 -15
  25. package/src/server/common/services/workspaceContextService.js +3 -3
  26. package/src/server/common/validators/authenticatedUserValidator.js +2 -2
  27. package/src/server/registerWorkspaceBootstrap.js +1 -1
  28. package/src/server/registerWorkspaceCore.js +5 -2
  29. package/src/server/workspaceBootstrapContributor.js +6 -6
  30. package/src/server/workspaceMembers/bootWorkspaceMembers.js +2 -2
  31. package/src/server/workspaceMembers/workspaceMembersActions.js +2 -2
  32. package/src/server/workspaceMembers/workspaceMembersService.js +11 -11
  33. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +1 -1
  34. package/src/shared/resources/workspaceMembersResource.js +11 -11
  35. package/src/shared/resources/workspacePendingInvitationsResource.js +2 -2
  36. package/src/shared/resources/workspaceResource.js +2 -2
  37. package/src/shared/roles.js +37 -12
  38. package/templates/config/roles.js +27 -0
  39. package/templates/migrations/users_core_initial.cjs +5 -5
  40. package/test/authProfileSyncService.test.js +8 -8
  41. package/test/avatarService.test.js +6 -6
  42. package/test/roles.test.js +90 -5
  43. package/test/usersRouteRequestInputValidator.test.js +4 -4
  44. package/test/workspaceActionContextContributor.test.js +107 -14
  45. package/test/workspaceAuthPolicyContextResolver.test.js +2 -2
  46. package/test/workspaceBootstrapContributor.test.js +8 -8
  47. package/test/workspaceInvitesRepository.test.js +3 -3
  48. package/test/workspaceMembersService.test.js +14 -12
  49. package/test/workspacePendingInvitationsResource.test.js +2 -2
  50. package/test/workspacePendingInvitationsService.test.js +3 -3
  51. package/test/workspaceService.test.js +22 -18
  52. package/test/workspaceSettingsResource.test.js +4 -2
  53. package/src/server/accountProfile/registerAvatarMultipartSupport.js +0 -40
  54. package/templates/config/workspaceRoles.js +0 -30
  55. package/test/registerAvatarMultipartSupport.test.js +0 -63
@@ -11,7 +11,7 @@ function registerAccountSecurity(app) {
11
11
  const authService = scope.has("authService") ? scope.make("authService") : null;
12
12
  return createAccountSecurityService({
13
13
  userSettingsRepository: scope.make("userSettingsRepository"),
14
- userProfilesRepository: scope.make("userProfilesRepository"),
14
+ usersRepository: scope.make("usersRepository"),
15
15
  authService
16
16
  });
17
17
  });
@@ -2,6 +2,7 @@ import {
2
2
  normalizeObject,
3
3
  requireServiceMethod
4
4
  } from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
5
+ import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
5
6
  import {
6
7
  checkRouteVisibility,
7
8
  USERS_ROUTE_VISIBILITY_PUBLIC,
@@ -9,45 +10,51 @@ import {
9
10
  USERS_ROUTE_VISIBILITY_WORKSPACE_USER
10
11
  } from "../../../shared/support/usersVisibility.js";
11
12
  import { resolveActionUser } from "../support/resolveActionUser.js";
12
-
13
- const WORKSPACE_CONTEXT_ACTION_IDS = Object.freeze([
14
- "workspace.roles.list",
15
- "workspace.settings.read",
16
- "workspace.settings.update",
17
- "workspace.members.list",
18
- "workspace.member.role.update",
19
- "workspace.member.remove",
20
- "workspace.invites.list",
21
- "workspace.invite.create",
22
- "workspace.invite.revoke"
23
- ]);
24
13
  const WORKSPACE_VISIBILITY_ACTION_CONTEXT_SET = new Set([
25
14
  USERS_ROUTE_VISIBILITY_WORKSPACE,
26
15
  USERS_ROUTE_VISIBILITY_WORKSPACE_USER
27
16
  ]);
28
17
 
29
- function createWorkspaceActionContextContributor({ workspaceService } = {}) {
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 = [] } = {}) {
30
34
  const contributorId = "users.workspace.context";
35
+ const workspaceSurfaceIdSet = normalizeWorkspaceSurfaceIds(workspaceSurfaceIds);
31
36
 
32
37
  requireServiceMethod(workspaceService, "resolveWorkspaceContextForUserBySlug", contributorId);
33
38
 
34
39
  return Object.freeze({
35
40
  contributorId,
36
- async contribute({ actionId, input, context, request } = {}) {
41
+ async contribute({ definition = null, input, context, request } = {}) {
37
42
  const payload = normalizeObject(input);
38
43
  if (!Object.hasOwn(payload, "workspaceSlug")) {
39
44
  return {};
40
45
  }
41
46
 
42
- const actionName = String(actionId || "").trim();
43
- const hasLegacyWorkspaceActionId = WORKSPACE_CONTEXT_ACTION_IDS.includes(actionName);
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);
44
51
  const routeVisibilityInput =
45
52
  request && request.routeOptions && request.routeOptions.config
46
53
  ? request.routeOptions.config.visibility
47
54
  : USERS_ROUTE_VISIBILITY_PUBLIC;
48
55
  const routeVisibility = checkRouteVisibility(routeVisibilityInput);
49
56
  const hasWorkspaceRouteVisibility = WORKSPACE_VISIBILITY_ACTION_CONTEXT_SET.has(routeVisibility);
50
- if (!hasLegacyWorkspaceActionId && !hasWorkspaceRouteVisibility) {
57
+ if (!hasWorkspaceActionSurface && !hasWorkspaceRouteVisibility && !hasWorkspaceSurface) {
51
58
  return {};
52
59
  }
53
60
 
@@ -7,7 +7,7 @@ function mapWorkspaceSummary(workspace, membership) {
7
7
  slug: normalizeText(workspace.slug),
8
8
  name: normalizeText(workspace.name),
9
9
  avatarUrl: normalizeText(workspace.avatarUrl),
10
- roleId: normalizeLowerText(membership?.roleId || "member") || "member",
10
+ roleSid: normalizeLowerText(membership?.roleSid || "member") || "member",
11
11
  isAccessible: normalizeLowerText(membership?.status || "active") === "active"
12
12
  };
13
13
  }
@@ -40,7 +40,7 @@ function mapMembershipSummary(membership, workspace) {
40
40
 
41
41
  return {
42
42
  workspaceId: Number(workspace?.id || membership.workspaceId),
43
- roleId: normalizeLowerText(membership.roleId || "member") || "member",
43
+ roleSid: normalizeLowerText(membership.roleSid || "member") || "member",
44
44
  status: normalizeLowerText(membership.status || "active") || "active"
45
45
  };
46
46
  }
@@ -1,4 +1,4 @@
1
- import { createRepository as createUserProfilesRepository } from "./repositories/userProfilesRepository.js";
1
+ import { createRepository as createUsersRepository } from "./repositories/usersRepository.js";
2
2
  import { createRepository as createUserSettingsRepository } from "./repositories/userSettingsRepository.js";
3
3
  import { createRepository as createWorkspacesRepository } from "./repositories/workspacesRepository.js";
4
4
  import { createRepository as createWorkspaceMembershipsRepository } from "./repositories/workspaceMembershipsRepository.js";
@@ -10,9 +10,9 @@ function registerCommonRepositories(app) {
10
10
  throw new Error("registerCommonRepositories requires application singleton().");
11
11
  }
12
12
 
13
- app.singleton("userProfilesRepository", (scope) => {
13
+ app.singleton("usersRepository", (scope) => {
14
14
  const knex = scope.make("jskit.database.knex");
15
- return createUserProfilesRepository(knex);
15
+ return createUsersRepository(knex);
16
16
  });
17
17
 
18
18
  app.singleton("userSettingsRepository", (scope) => {
@@ -13,7 +13,7 @@ const USERNAME_MAX_LENGTH = 120;
13
13
  function normalizeIdentity(identityLike) {
14
14
  const source = identityLike && typeof identityLike === "object" ? identityLike : {};
15
15
  const provider = normalizeLowerText(source.provider || source.authProvider);
16
- const providerUserId = normalizeText(source.providerUserId || source.authProviderUserId);
16
+ const providerUserId = normalizeText(source.providerUserId || source.authProviderUserSid);
17
17
  if (!provider || !providerUserId) {
18
18
  return null;
19
19
  }
@@ -57,7 +57,7 @@ function mapProfileRow(row) {
57
57
  return {
58
58
  id: Number(row.id),
59
59
  authProvider: normalizeLowerText(row.auth_provider),
60
- authProviderUserId: normalizeText(row.auth_provider_user_id),
60
+ authProviderUserSid: normalizeText(row.auth_provider_user_sid),
61
61
  email: normalizeLowerText(row.email),
62
62
  username: normalizeLowerText(row.username),
63
63
  displayName: normalizeText(row.display_name),
@@ -104,7 +104,7 @@ async function resolveUniqueUsername(client, baseUsername, { excludeUserId = 0 }
104
104
 
105
105
  function createRepository(knex) {
106
106
  if (typeof knex !== "function") {
107
- throw new TypeError("userProfilesRepository requires knex.");
107
+ throw new TypeError("usersRepository requires knex.");
108
108
  }
109
109
 
110
110
  async function findById(userId, options = {}) {
@@ -123,7 +123,7 @@ function createRepository(knex) {
123
123
  const row = await client("users")
124
124
  .where({
125
125
  auth_provider: identity.provider,
126
- auth_provider_user_id: identity.providerUserId
126
+ auth_provider_user_sid: identity.providerUserId
127
127
  })
128
128
  .first();
129
129
  return mapProfileRow(row);
@@ -168,7 +168,7 @@ function createRepository(knex) {
168
168
  const client = options?.trx || knex;
169
169
  const identity = normalizeIdentity(profileLike);
170
170
  if (!identity) {
171
- throw new TypeError("upsert requires provider/authProvider and providerUserId/authProviderUserId.");
171
+ throw new TypeError("upsert requires provider/authProvider and providerUserId/authProviderUserSid.");
172
172
  }
173
173
 
174
174
  const email = normalizeLowerText(profileLike.email);
@@ -181,7 +181,7 @@ function createRepository(knex) {
181
181
  const executeUpsert = async (trx) => {
182
182
  const where = {
183
183
  auth_provider: identity.provider,
184
- auth_provider_user_id: identity.providerUserId
184
+ auth_provider_user_sid: identity.providerUserId
185
185
  };
186
186
  const existing = await trx("users").where(where).first();
187
187
 
@@ -200,7 +200,7 @@ function createRepository(knex) {
200
200
  const username = await resolveUniqueUsername(trx, requestedUsername || usernameBaseFromEmail(email));
201
201
  await trx("users").insert({
202
202
  auth_provider: identity.provider,
203
- auth_provider_user_id: identity.providerUserId,
203
+ auth_provider_user_sid: identity.providerUserId,
204
204
  email,
205
205
  display_name: displayName,
206
206
  username
@@ -17,7 +17,7 @@ function mapRow(row) {
17
17
  id: Number(row.id),
18
18
  workspaceId: Number(row.workspace_id),
19
19
  email: normalizeLowerText(row.email),
20
- roleId: normalizeLowerText(row.role_id || "member") || "member",
20
+ roleSid: normalizeLowerText(row.role_sid || "member") || "member",
21
21
  status: normalizeLowerText(row.status || "pending") || "pending",
22
22
  tokenHash: normalizeText(row.token_hash),
23
23
  invitedByUserId: row.invited_by_user_id == null ? null : Number(row.invited_by_user_id),
@@ -86,7 +86,7 @@ function createRepository(knex) {
86
86
  const insertPayload = {
87
87
  workspace_id: Number(source.workspaceId),
88
88
  email: normalizeLowerText(source.email),
89
- role_id: normalizeLowerText(source.roleId || "member") || "member",
89
+ role_sid: normalizeLowerText(source.roleSid || "member") || "member",
90
90
  status: normalizeLowerText(source.status || "pending") || "pending",
91
91
  token_hash: normalizeText(source.tokenHash),
92
92
  invited_by_user_id: source.invitedByUserId == null ? null : Number(source.invitedByUserId),
@@ -16,7 +16,7 @@ function mapRow(row) {
16
16
  id: Number(row.id),
17
17
  workspaceId: Number(row.workspace_id),
18
18
  userId: Number(row.user_id),
19
- roleId: normalizeLowerText(row.role_id || "member") || "member",
19
+ roleSid: normalizeLowerText(row.role_sid || "member") || "member",
20
20
  status: normalizeLowerText(row.status || "active") || "active",
21
21
  createdAt: toIsoString(row.created_at),
22
22
  updatedAt: toIsoString(row.updated_at)
@@ -30,7 +30,7 @@ function mapMemberSummaryRow(row) {
30
30
 
31
31
  return {
32
32
  userId: Number(row.user_id),
33
- roleId: normalizeLowerText(row.role_id || "member") || "member",
33
+ roleSid: normalizeLowerText(row.role_sid || "member") || "member",
34
34
  status: normalizeLowerText(row.status || "active") || "active",
35
35
  displayName: normalizeText(row.display_name),
36
36
  email: normalizeLowerText(row.email)
@@ -54,11 +54,11 @@ function createRepository(knex) {
54
54
  const client = options?.trx || knex;
55
55
  const existing = await findByWorkspaceIdAndUserId(workspaceId, userId, { trx: client });
56
56
  if (existing) {
57
- if (existing.roleId !== OWNER_ROLE_ID || existing.status !== "active") {
57
+ if (existing.roleSid !== OWNER_ROLE_ID || existing.status !== "active") {
58
58
  await client("workspace_memberships")
59
59
  .where({ workspace_id: Number(workspaceId), user_id: Number(userId) })
60
60
  .update({
61
- role_id: OWNER_ROLE_ID,
61
+ role_sid: OWNER_ROLE_ID,
62
62
  status: "active",
63
63
  updated_at: nowDb()
64
64
  });
@@ -70,7 +70,7 @@ function createRepository(knex) {
70
70
  await client("workspace_memberships").insert({
71
71
  workspace_id: Number(workspaceId),
72
72
  user_id: Number(userId),
73
- role_id: OWNER_ROLE_ID,
73
+ role_sid: OWNER_ROLE_ID,
74
74
  status: "active",
75
75
  created_at: nowDb(),
76
76
  updated_at: nowDb()
@@ -87,14 +87,14 @@ function createRepository(knex) {
87
87
  async function upsertMembership(workspaceId, userId, patch = {}, options = {}) {
88
88
  const client = options?.trx || knex;
89
89
  const existing = await findByWorkspaceIdAndUserId(workspaceId, userId, { trx: client });
90
- const roleId = normalizeLowerText(patch.roleId || existing?.roleId || "member") || "member";
90
+ const roleSid = normalizeLowerText(patch.roleSid || existing?.roleSid || "member") || "member";
91
91
  const status = normalizeLowerText(patch.status || existing?.status || "active") || "active";
92
92
 
93
93
  if (!existing) {
94
94
  await client("workspace_memberships").insert({
95
95
  workspace_id: Number(workspaceId),
96
96
  user_id: Number(userId),
97
- role_id: roleId,
97
+ role_sid: roleSid,
98
98
  status,
99
99
  created_at: nowDb(),
100
100
  updated_at: nowDb()
@@ -105,7 +105,7 @@ function createRepository(knex) {
105
105
  await client("workspace_memberships")
106
106
  .where({ workspace_id: Number(workspaceId), user_id: Number(userId) })
107
107
  .update({
108
- role_id: roleId,
108
+ role_sid: roleSid,
109
109
  status,
110
110
  updated_at: nowDb()
111
111
  });
@@ -121,7 +121,7 @@ function createRepository(knex) {
121
121
  .orderBy("up.display_name", "asc")
122
122
  .select([
123
123
  "wm.user_id",
124
- "wm.role_id",
124
+ "wm.role_sid",
125
125
  "wm.status",
126
126
  "up.display_name",
127
127
  "up.email"
@@ -32,7 +32,7 @@ function mapMembershipWorkspaceRow(row) {
32
32
 
33
33
  return {
34
34
  ...mapRow(row),
35
- roleId: normalizeLowerText(row.role_id || "member"),
35
+ roleSid: normalizeLowerText(row.role_sid || "member"),
36
36
  membershipStatus: normalizeLowerText(row.membership_status || "active") || "active"
37
37
  };
38
38
  }
@@ -55,7 +55,7 @@ function createRepository(knex) {
55
55
  "w.deleted_at"
56
56
  ];
57
57
  if (includeMembership) {
58
- columns.push("wm.role_id", "wm.status as membership_status");
58
+ columns.push("wm.role_sid", "wm.status as membership_status");
59
59
  }
60
60
  return columns;
61
61
  }
@@ -1,9 +1,9 @@
1
- import { normalizeIdentity } from "../repositories/userProfilesRepository.js";
1
+ import { normalizeIdentity } from "../repositories/usersRepository.js";
2
2
 
3
- async function resolveUserProfile(userProfilesRepository, user) {
3
+ async function resolveUserProfile(usersRepository, user) {
4
4
  const identity = normalizeIdentity(user);
5
5
  if (identity) {
6
- const profile = await userProfilesRepository.findByIdentity(identity);
6
+ const profile = await usersRepository.findByIdentity(identity);
7
7
  if (profile) {
8
8
  return profile;
9
9
  }
@@ -11,7 +11,7 @@ async function resolveUserProfile(userProfilesRepository, user) {
11
11
 
12
12
  const userId = Number(user?.id);
13
13
  if (Number.isInteger(userId) && userId > 0) {
14
- const profileById = await userProfilesRepository.findById(userId);
14
+ const profileById = await usersRepository.findById(userId);
15
15
  if (profileById) {
16
16
  return profileById;
17
17
  }
@@ -1,5 +1,5 @@
1
1
  import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
2
- import { normalizeIdentity } from "../repositories/userProfilesRepository.js";
2
+ import { normalizeIdentity } from "../repositories/usersRepository.js";
3
3
 
4
4
  function buildNormalizedIdentityKey(identityLike) {
5
5
  const identity = normalizeIdentity(identityLike);
@@ -9,7 +9,7 @@ function buildNormalizedIdentityKey(identityLike) {
9
9
 
10
10
  return {
11
11
  authProvider: identity.provider,
12
- authProviderUserId: identity.providerUserId
12
+ authProviderUserSid: identity.providerUserId
13
13
  };
14
14
  }
15
15
 
@@ -25,7 +25,7 @@ function buildNormalizedIdentityProfile(profileLike) {
25
25
 
26
26
  return {
27
27
  authProvider: identity.authProvider,
28
- authProviderUserId: identity.authProviderUserId,
28
+ authProviderUserSid: identity.authProviderUserSid,
29
29
  email,
30
30
  displayName,
31
31
  username: normalizeLowerText(source.username)
@@ -41,7 +41,7 @@ function profileNeedsUpdate(existing, nextProfile) {
41
41
  existing.email !== nextProfile.email ||
42
42
  existing.displayName !== nextProfile.displayName ||
43
43
  existing.authProvider !== nextProfile.authProvider ||
44
- existing.authProviderUserId !== nextProfile.authProviderUserId
44
+ existing.authProviderUserSid !== nextProfile.authProviderUserSid
45
45
  );
46
46
  }
47
47
 
@@ -53,12 +53,12 @@ function requireSynchronizedProfile(profile) {
53
53
  throw new Error("Profile synchronization failed.");
54
54
  }
55
55
 
56
- function createService({ userProfilesRepository, workspaceProvisioningService = null, userSettingsRepository = null } = {}) {
57
- if (!userProfilesRepository || typeof userProfilesRepository.findByIdentity !== "function") {
58
- throw new Error("authProfileSyncService requires userProfilesRepository.findByIdentity().");
56
+ function createService({ usersRepository, workspaceProvisioningService = null, userSettingsRepository = null } = {}) {
57
+ if (!usersRepository || typeof usersRepository.findByIdentity !== "function") {
58
+ throw new Error("authProfileSyncService requires usersRepository.findByIdentity().");
59
59
  }
60
- if (typeof userProfilesRepository.upsert !== "function") {
61
- throw new Error("authProfileSyncService requires userProfilesRepository.upsert().");
60
+ if (typeof usersRepository.upsert !== "function") {
61
+ throw new Error("authProfileSyncService requires usersRepository.upsert().");
62
62
  }
63
63
  if (!userSettingsRepository || typeof userSettingsRepository.ensureForUserId !== "function") {
64
64
  throw new Error("authProfileSyncService requires userSettingsRepository.ensureForUserId().");
@@ -66,10 +66,10 @@ function createService({ userProfilesRepository, workspaceProvisioningService =
66
66
 
67
67
  async function findByIdentity(identityLike, options = {}) {
68
68
  const normalized = buildNormalizedIdentityKey(identityLike);
69
- return userProfilesRepository.findByIdentity(
69
+ return usersRepository.findByIdentity(
70
70
  {
71
71
  provider: normalized.authProvider,
72
- providerUserId: normalized.authProviderUserId
72
+ providerUserId: normalized.authProviderUserSid
73
73
  },
74
74
  options
75
75
  );
@@ -77,10 +77,10 @@ function createService({ userProfilesRepository, workspaceProvisioningService =
77
77
 
78
78
  async function upsertByIdentity(profileLike, options = {}) {
79
79
  const normalized = buildNormalizedIdentityProfile(profileLike);
80
- return userProfilesRepository.upsert(
80
+ return usersRepository.upsert(
81
81
  {
82
82
  authProvider: normalized.authProvider,
83
- authProviderUserId: normalized.authProviderUserId,
83
+ authProviderUserSid: normalized.authProviderUserSid,
84
84
  email: normalized.email,
85
85
  displayName: normalized.displayName,
86
86
  username: normalized.username
@@ -118,8 +118,8 @@ function createService({ userProfilesRepository, workspaceProvisioningService =
118
118
  if (options?.trx) {
119
119
  return runSync(options.trx);
120
120
  }
121
- if (typeof userProfilesRepository.withTransaction === "function") {
122
- return userProfilesRepository.withTransaction((trx) => runSync(trx));
121
+ if (typeof usersRepository.withTransaction === "function") {
122
+ return usersRepository.withTransaction((trx) => runSync(trx));
123
123
  }
124
124
  return runSync();
125
125
  }
@@ -50,8 +50,8 @@ function buildWorkspaceName(user = {}) {
50
50
  }
51
51
 
52
52
  function buildPermissionsFromMembership(membership, appConfig = {}) {
53
- const roleId = normalizeLowerText(membership?.roleId || "member");
54
- return resolveRolePermissions(roleId, appConfig);
53
+ const roleSid = normalizeLowerText(membership?.roleSid || "member");
54
+ return resolveRolePermissions(roleSid, appConfig);
55
55
  }
56
56
 
57
57
  function hashInviteToken(token) {
@@ -145,7 +145,7 @@ function createService({
145
145
 
146
146
  const list = await workspacesRepository.listForUserId(normalizedUser.id, options);
147
147
  const accessible = list
148
- .map((entry) => mapWorkspaceSummary(entry, { roleId: entry.roleId, status: entry.membershipStatus }))
148
+ .map((entry) => mapWorkspaceSummary(entry, { roleSid: entry.roleSid, status: entry.membershipStatus }))
149
149
  .filter((entry) => entry.isAccessible);
150
150
 
151
151
  return accessible;
@@ -16,7 +16,7 @@ function normalizeAuthenticatedUser(input = {}) {
16
16
  username: normalizeLowerText(source.username),
17
17
  displayName: normalizeText(source.displayName) || email || `User ${id}`,
18
18
  authProvider: normalizeLowerText(source.authProvider),
19
- authProviderUserId: normalizeText(source.authProviderUserId),
19
+ authProviderUserSid: normalizeText(source.authProviderUserSid),
20
20
  avatarStorageKey: source.avatarStorageKey ? normalizeText(source.avatarStorageKey) : null,
21
21
  avatarVersion: source.avatarVersion == null ? null : String(source.avatarVersion)
22
22
  };
@@ -30,7 +30,7 @@ const authenticatedUserValidator = Object.freeze({
30
30
  username: Type.Optional(Type.String()),
31
31
  displayName: Type.Optional(Type.String()),
32
32
  authProvider: Type.Optional(Type.String()),
33
- authProviderUserId: Type.Optional(Type.String()),
33
+ authProviderUserSid: Type.Optional(Type.String()),
34
34
  avatarStorageKey: Type.Optional(Type.Union([Type.String(), Type.Null()])),
35
35
  avatarVersion: Type.Optional(Type.Union([Type.String(), Type.Number(), Type.Null()]))
36
36
  },
@@ -17,7 +17,7 @@ function registerWorkspaceBootstrap(app) {
17
17
  ? scope.make("users.workspace.pending-invitations.service")
18
18
  : null,
19
19
  workspaceInvitationsEnabled,
20
- userProfilesRepository: scope.make("userProfilesRepository"),
20
+ usersRepository: scope.make("usersRepository"),
21
21
  userSettingsRepository: scope.make("userSettingsRepository"),
22
22
  appConfig: resolveAppConfig(scope),
23
23
  tenancyProfile: scope.make("users.tenancy.profile"),
@@ -10,6 +10,7 @@ import { createWorkspaceActionContextContributor } from "./common/contributors/w
10
10
  import { createWorkspaceRouteVisibilityResolver } from "./common/contributors/workspaceRouteVisibilityResolver.js";
11
11
  import { createWorkspaceAuthPolicyContextResolver } from "./common/contributors/workspaceAuthPolicyContextResolver.js";
12
12
  import { resolveWorkspaceInvitationsPolicy } from "./support/workspaceInvitationsPolicy.js";
13
+ import { resolveWorkspaceSurfaceIdsFromAppConfig } from "./support/workspaceActionSurfaces.js";
13
14
 
14
15
 
15
16
  function registerWorkspaceCore(app) {
@@ -29,7 +30,7 @@ function registerWorkspaceCore(app) {
29
30
 
30
31
  app.singleton("users.profile.sync.service", (scope) => {
31
32
  return createAuthProfileSyncService({
32
- userProfilesRepository: scope.make("userProfilesRepository"),
33
+ usersRepository: scope.make("usersRepository"),
33
34
  userSettingsRepository: scope.make("userSettingsRepository"),
34
35
  workspaceProvisioningService: scope.make("users.workspace.service")
35
36
  });
@@ -62,8 +63,10 @@ function registerWorkspaceCore(app) {
62
63
  });
63
64
 
64
65
  registerActionContextContributor(app, "users.core.workspace.actionContextContributor", (scope) => {
66
+ const appConfig = resolveAppConfig(scope);
65
67
  return createWorkspaceActionContextContributor({
66
- workspaceService: scope.make("users.workspace.service")
68
+ workspaceService: scope.make("users.workspace.service"),
69
+ workspaceSurfaceIds: resolveWorkspaceSurfaceIdsFromAppConfig(appConfig)
67
70
  });
68
71
  });
69
72
 
@@ -230,7 +230,7 @@ function mapUserSettingsBootstrap(settings = {}) {
230
230
  function createWorkspaceBootstrapContributor({
231
231
  workspaceService,
232
232
  workspacePendingInvitationsService,
233
- userProfilesRepository,
233
+ usersRepository,
234
234
  userSettingsRepository,
235
235
  workspaceInvitationsEnabled = false,
236
236
  appConfig = {},
@@ -255,8 +255,8 @@ function createWorkspaceBootstrapContributor({
255
255
  serviceLabel: "workspacePendingInvitationsService"
256
256
  });
257
257
  }
258
- requireServiceMethod(userProfilesRepository, "findByIdentity", contributorId, {
259
- serviceLabel: "userProfilesRepository"
258
+ requireServiceMethod(usersRepository, "findByIdentity", contributorId, {
259
+ serviceLabel: "usersRepository"
260
260
  });
261
261
  requireServiceMethod(userSettingsRepository, "ensureForUserId", contributorId, {
262
262
  serviceLabel: "userSettingsRepository"
@@ -317,9 +317,9 @@ function createWorkspaceBootstrapContributor({
317
317
 
318
318
  if (normalizedUser) {
319
319
  const latestProfile =
320
- (await userProfilesRepository.findByIdentity({
320
+ (await usersRepository.findByIdentity({
321
321
  provider: normalizedUser.authProvider,
322
- providerUserId: normalizedUser.authProviderUserId
322
+ providerUserId: normalizedUser.authProviderUserSid
323
323
  })) || normalizedUser;
324
324
 
325
325
  const workspaces = await workspaceService.listWorkspacesForUser(latestProfile, { request });
@@ -362,7 +362,7 @@ function createWorkspaceBootstrapContributor({
362
362
  pendingInvites,
363
363
  activeWorkspace: workspaceContext
364
364
  ? mapWorkspaceSummary(workspaceContext.workspace, {
365
- roleId: workspaceContext.membership?.roleId,
365
+ roleSid: workspaceContext.membership?.roleSid,
366
366
  status: workspaceContext.membership?.status
367
367
  })
368
368
  : null,
@@ -103,7 +103,7 @@ function bootWorkspaceMembers(app) {
103
103
  input: {
104
104
  workspaceSlug: request.input.params.workspaceSlug,
105
105
  memberUserId: request.input.params.memberUserId,
106
- roleId: request.input.body.roleId
106
+ roleSid: request.input.body.roleSid
107
107
  }
108
108
  });
109
109
  reply.code(200).send(response);
@@ -195,7 +195,7 @@ function bootWorkspaceMembers(app) {
195
195
  input: {
196
196
  workspaceSlug: request.input.params.workspaceSlug,
197
197
  email: request.input.body.email,
198
- roleId: request.input.body.roleId
198
+ roleSid: request.input.body.roleSid
199
199
  }
200
200
  });
201
201
  reply.code(200).send(response);
@@ -68,7 +68,7 @@ const workspaceMembersActions = Object.freeze([
68
68
  async execute(input, context, deps) {
69
69
  return deps.workspaceMembersService.updateMemberRole(resolveWorkspace(context, input), {
70
70
  memberUserId: input.memberUserId,
71
- roleId: input.roleId
71
+ roleSid: input.roleSid
72
72
  }, {
73
73
  context
74
74
  });
@@ -150,7 +150,7 @@ const workspaceMembersActions = Object.freeze([
150
150
  resolveActionUser(context, input),
151
151
  {
152
152
  email: input.email,
153
- roleId: input.roleId
153
+ roleSid: input.roleSid
154
154
  },
155
155
  {
156
156
  context
@@ -62,12 +62,12 @@ function createService({
62
62
 
63
63
  async function updateMemberRole(workspace, payload = {}, options = {}) {
64
64
  const memberUserId = payload.memberUserId;
65
- const roleId = payload.roleId;
66
- if (!assignableRoleIds.includes(roleId)) {
65
+ const roleSid = payload.roleSid;
66
+ if (!assignableRoleIds.includes(roleSid)) {
67
67
  throw new AppError(400, "Validation failed.", {
68
68
  details: {
69
69
  fieldErrors: {
70
- roleId: "Role is not assignable."
70
+ roleSid: "Role is not assignable."
71
71
  }
72
72
  }
73
73
  });
@@ -77,7 +77,7 @@ function createService({
77
77
  if (!existingMembership || existingMembership.status !== "active") {
78
78
  throw new AppError(404, "Member not found.");
79
79
  }
80
- if (Number(memberUserId) === Number(workspace.ownerUserId) || existingMembership.roleId === OWNER_ROLE_ID) {
80
+ if (Number(memberUserId) === Number(workspace.ownerUserId) || existingMembership.roleSid === OWNER_ROLE_ID) {
81
81
  throw new AppError(409, "Cannot change workspace owner role.");
82
82
  }
83
83
 
@@ -85,7 +85,7 @@ function createService({
85
85
  workspace.id,
86
86
  memberUserId,
87
87
  {
88
- roleId,
88
+ roleSid,
89
89
  status: "active"
90
90
  },
91
91
  options
@@ -101,7 +101,7 @@ function createService({
101
101
  if (!existingMembership || existingMembership.status !== "active") {
102
102
  throw new AppError(404, "Member not found.");
103
103
  }
104
- if (Number(memberUserId) === Number(workspace.ownerUserId) || existingMembership.roleId === OWNER_ROLE_ID) {
104
+ if (Number(memberUserId) === Number(workspace.ownerUserId) || existingMembership.roleSid === OWNER_ROLE_ID) {
105
105
  throw new AppError(409, "Cannot remove workspace owner.");
106
106
  }
107
107
 
@@ -109,7 +109,7 @@ function createService({
109
109
  workspace.id,
110
110
  memberUserId,
111
111
  {
112
- roleId: existingMembership.roleId,
112
+ roleSid: existingMembership.roleSid,
113
113
  status: "revoked"
114
114
  },
115
115
  options
@@ -134,12 +134,12 @@ function createService({
134
134
 
135
135
  async function createInvite(workspace, user, payload = {}, options = {}) {
136
136
  const email = payload.email;
137
- const roleId = payload.roleId;
138
- if (!assignableRoleIds.includes(roleId)) {
137
+ const roleSid = payload.roleSid;
138
+ if (!assignableRoleIds.includes(roleSid)) {
139
139
  throw new AppError(400, "Validation failed.", {
140
140
  details: {
141
141
  fieldErrors: {
142
- roleId: "Role is not assignable."
142
+ roleSid: "Role is not assignable."
143
143
  }
144
144
  }
145
145
  });
@@ -152,7 +152,7 @@ function createService({
152
152
  {
153
153
  workspaceId: workspace.id,
154
154
  email,
155
- roleId,
155
+ roleSid,
156
156
  status: "pending",
157
157
  tokenHash,
158
158
  invitedByUserId: Number(user?.id || 0) || null,
@@ -99,7 +99,7 @@ function createService({
99
99
  workspaceId,
100
100
  resolvedInvite.user.id,
101
101
  {
102
- roleId: resolvedInvite.invite.roleId,
102
+ roleSid: resolvedInvite.invite.roleSid,
103
103
  status: "active"
104
104
  },
105
105
  options