@jskit-ai/users-core 0.1.48 → 0.1.50

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
@@ -1,82 +0,0 @@
1
- import { Type } from "@fastify/type-provider-typebox";
2
- import { encodeInviteTokenHash } from "@jskit-ai/auth-core/shared/inviteTokens";
3
- import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
4
- import { createOperationMessages } from "../operationMessages.js";
5
- import { normalizeObjectInput, recordIdSchema } from "@jskit-ai/kernel/shared/validators";
6
- import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
7
-
8
- function normalizePendingInvite(invite) {
9
- const id = normalizeRecordId(invite?.id, { fallback: null });
10
- const workspaceId = normalizeRecordId(invite?.workspaceId, { fallback: null });
11
- const tokenHash = normalizeText(invite?.tokenHash);
12
-
13
- if (!id || !workspaceId || !tokenHash) {
14
- return null;
15
- }
16
-
17
- return {
18
- id,
19
- workspaceId,
20
- workspaceSlug: normalizeText(invite?.workspaceSlug),
21
- workspaceName: normalizeText(invite?.workspaceName || invite?.workspaceSlug),
22
- workspaceAvatarUrl: normalizeText(invite?.workspaceAvatarUrl),
23
- roleSid: normalizeLowerText(invite?.roleSid || "member") || "member",
24
- status: normalizeLowerText(invite?.status || "pending") || "pending",
25
- expiresAt: invite?.expiresAt || null,
26
- token: encodeInviteTokenHash(tokenHash)
27
- };
28
- }
29
-
30
- function normalizePendingInviteList(invites) {
31
- return (Array.isArray(invites) ? invites : []).map((invite) => normalizePendingInvite(invite)).filter(Boolean);
32
- }
33
-
34
- const pendingInviteRecordValidator = Object.freeze({
35
- schema: Type.Object(
36
- {
37
- id: recordIdSchema,
38
- workspaceId: recordIdSchema,
39
- workspaceSlug: Type.String({ minLength: 1 }),
40
- workspaceName: Type.String({ minLength: 1 }),
41
- workspaceAvatarUrl: Type.String(),
42
- roleSid: Type.String({ minLength: 1 }),
43
- status: Type.String({ minLength: 1 }),
44
- expiresAt: Type.Union([Type.String({ minLength: 1 }), Type.Null()]),
45
- token: Type.String({ minLength: 1 })
46
- },
47
- { additionalProperties: false }
48
- ),
49
- normalize: normalizePendingInvite
50
- });
51
-
52
- const pendingInvitationsListOutputValidator = Object.freeze({
53
- schema: Type.Object(
54
- {
55
- pendingInvites: Type.Array(pendingInviteRecordValidator.schema)
56
- },
57
- { additionalProperties: false }
58
- ),
59
- normalize(payload = {}) {
60
- const source = normalizeObjectInput(payload);
61
-
62
- return {
63
- pendingInvites: normalizePendingInviteList(source.pendingInvites)
64
- };
65
- }
66
- });
67
-
68
- const WORKSPACE_PENDING_INVITATIONS_MESSAGES = createOperationMessages();
69
-
70
- const workspacePendingInvitationsResource = Object.freeze({
71
- resource: "workspacePendingInvitations",
72
- messages: WORKSPACE_PENDING_INVITATIONS_MESSAGES,
73
- operations: Object.freeze({
74
- list: Object.freeze({
75
- method: "GET",
76
- messages: WORKSPACE_PENDING_INVITATIONS_MESSAGES,
77
- outputValidator: pendingInvitationsListOutputValidator
78
- })
79
- })
80
- });
81
-
82
- export { workspacePendingInvitationsResource };
@@ -1,176 +0,0 @@
1
- import { Type } from "typebox";
2
- import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
3
- import {
4
- normalizeObjectInput,
5
- createCursorListValidator,
6
- recordIdSchema,
7
- recordIdInputSchema
8
- } from "@jskit-ai/kernel/shared/validators";
9
- import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
10
-
11
- function normalizeWorkspaceAvatarUrl(value) {
12
- const avatarUrl = normalizeText(value);
13
- if (!avatarUrl) {
14
- return "";
15
- }
16
- if (!avatarUrl.startsWith("http://") && !avatarUrl.startsWith("https://")) {
17
- return null;
18
- }
19
- try {
20
- return new URL(avatarUrl).toString();
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- function normalizeWorkspaceInput(payload = {}) {
27
- const source = normalizeObjectInput(payload);
28
- const normalized = {};
29
-
30
- if (Object.hasOwn(source, "slug")) {
31
- normalized.slug = normalizeLowerText(source.slug);
32
- }
33
- if (Object.hasOwn(source, "name")) {
34
- normalized.name = normalizeText(source.name);
35
- }
36
- if (Object.hasOwn(source, "ownerUserId")) {
37
- normalized.ownerUserId = normalizeRecordId(source.ownerUserId, { fallback: "" });
38
- }
39
- if (Object.hasOwn(source, "avatarUrl")) {
40
- normalized.avatarUrl = normalizeWorkspaceAvatarUrl(source.avatarUrl);
41
- }
42
- if (Object.hasOwn(source, "isPersonal")) {
43
- normalized.isPersonal = source.isPersonal === true;
44
- }
45
-
46
- return normalized;
47
- }
48
-
49
- function normalizeWorkspaceOutput(payload = {}) {
50
- const source = normalizeObjectInput(payload);
51
-
52
- return {
53
- id: normalizeRecordId(source.id, { fallback: "" }),
54
- slug: normalizeLowerText(source.slug),
55
- name: normalizeText(source.name),
56
- ownerUserId: normalizeRecordId(source.ownerUserId, { fallback: "" }),
57
- avatarUrl: normalizeText(source.avatarUrl)
58
- };
59
- }
60
-
61
- function normalizeWorkspaceListItemOutput(payload = {}) {
62
- const source = normalizeObjectInput(payload);
63
-
64
- return {
65
- id: normalizeRecordId(source.id, { fallback: "" }),
66
- slug: normalizeLowerText(source.slug),
67
- name: normalizeText(source.name),
68
- avatarUrl: normalizeText(source.avatarUrl),
69
- roleSid: normalizeLowerText(source.roleSid || "member") || "member",
70
- isAccessible: source.isAccessible !== false
71
- };
72
- }
73
-
74
- const responseRecordSchema = Type.Object(
75
- {
76
- id: recordIdSchema,
77
- slug: Type.String({ minLength: 1 }),
78
- name: Type.String({ minLength: 1, maxLength: 160 }),
79
- ownerUserId: recordIdSchema,
80
- avatarUrl: Type.String()
81
- },
82
- { additionalProperties: false }
83
- );
84
-
85
- const listItemSchema = Type.Object(
86
- {
87
- id: recordIdSchema,
88
- slug: Type.String({ minLength: 1 }),
89
- name: Type.String({ minLength: 1, maxLength: 160 }),
90
- avatarUrl: Type.String(),
91
- roleSid: Type.String({ minLength: 1 }),
92
- isAccessible: Type.Boolean()
93
- },
94
- { additionalProperties: false }
95
- );
96
-
97
- const createRequestBodySchema = Type.Object(
98
- {
99
- name: Type.String({ minLength: 1, maxLength: 160 }),
100
- slug: Type.Optional(Type.String({ minLength: 1, maxLength: 120 })),
101
- ownerUserId: Type.Optional(recordIdInputSchema)
102
- },
103
- { additionalProperties: false }
104
- );
105
-
106
- const patchRequestBodySchema = Type.Object(
107
- {
108
- name: Type.Optional(Type.String({ minLength: 1, maxLength: 160 })),
109
- avatarUrl: Type.Optional(
110
- Type.String({
111
- pattern: "^(https?://.+)?$",
112
- messages: {
113
- pattern: "Workspace avatar URL must be a valid absolute URL (http:// or https://).",
114
- default: "Workspace avatar URL must be a valid absolute URL (http:// or https://)."
115
- }
116
- })
117
- )
118
- },
119
- { additionalProperties: false }
120
- );
121
-
122
- const responseRecordValidator = Object.freeze({
123
- schema: responseRecordSchema,
124
- normalize: normalizeWorkspaceOutput
125
- });
126
-
127
- const workspaceSummaryOutputValidator = Object.freeze({
128
- schema: listItemSchema,
129
- normalize: normalizeWorkspaceListItemOutput
130
- });
131
-
132
- const resource = {
133
- resource: "workspace",
134
- messages: {
135
- validation: "Fix invalid workspace values and try again.",
136
- saveSuccess: "Workspace updated.",
137
- saveError: "Unable to update workspace.",
138
- apiValidation: "Validation failed."
139
- },
140
- operations: {
141
- view: {
142
- method: "GET",
143
- outputValidator: responseRecordValidator
144
- },
145
- list: {
146
- method: "GET",
147
- outputValidator: createCursorListValidator(workspaceSummaryOutputValidator)
148
- },
149
- create: {
150
- method: "POST",
151
- bodyValidator: {
152
- schema: createRequestBodySchema,
153
- normalize: normalizeWorkspaceInput
154
- },
155
- outputValidator: responseRecordValidator
156
- },
157
- replace: {
158
- method: "PUT",
159
- bodyValidator: {
160
- schema: createRequestBodySchema,
161
- normalize: normalizeWorkspaceInput
162
- },
163
- outputValidator: responseRecordValidator
164
- },
165
- patch: {
166
- method: "PATCH",
167
- bodyValidator: {
168
- schema: patchRequestBodySchema,
169
- normalize: normalizeWorkspaceInput
170
- },
171
- outputValidator: responseRecordValidator
172
- }
173
- }
174
- };
175
-
176
- export { resource as workspaceResource };
@@ -1,59 +0,0 @@
1
- import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
2
- import { resolveGlobalArrayRegistry } from "./resolveGlobalArrayRegistry.js";
3
-
4
- const workspaceSettingsFields = resolveGlobalArrayRegistry("jskit.users-core.workspaceSettingsFields");
5
-
6
- function defineField(field = {}) {
7
- const key = normalizeText(field.key);
8
- if (!key) {
9
- throw new TypeError("workspaceSettingsFields.defineField requires field.key.");
10
- }
11
- if (workspaceSettingsFields.some((entry) => entry.key === key)) {
12
- throw new Error(`workspaceSettingsFields.defineField duplicate key: ${key}`);
13
- }
14
- if (!field.inputSchema || typeof field.inputSchema !== "object") {
15
- throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires inputSchema.`);
16
- }
17
- if (!field.outputSchema || typeof field.outputSchema !== "object") {
18
- throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires outputSchema.`);
19
- }
20
- const dbColumn = normalizeText(field.dbColumn);
21
- if (!dbColumn) {
22
- throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires dbColumn.`);
23
- }
24
- if (typeof field.normalizeInput !== "function") {
25
- throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires normalizeInput.`);
26
- }
27
- if (typeof field.normalizeOutput !== "function") {
28
- throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires normalizeOutput.`);
29
- }
30
- if (typeof field.resolveDefault !== "function") {
31
- throw new TypeError(`workspaceSettingsFields.defineField("${key}") requires resolveDefault.`);
32
- }
33
-
34
- workspaceSettingsFields.push({
35
- key,
36
- dbColumn,
37
- required: field.required !== false,
38
- inputSchema: field.inputSchema,
39
- outputSchema: field.outputSchema,
40
- normalizeInput: field.normalizeInput,
41
- normalizeOutput: field.normalizeOutput,
42
- resolveDefault: field.resolveDefault
43
- });
44
- }
45
-
46
- function resetWorkspaceSettingsFields() {
47
- workspaceSettingsFields.splice(0, workspaceSettingsFields.length);
48
- }
49
-
50
- function resolveWorkspaceSettingsFieldKeys() {
51
- return workspaceSettingsFields.map((field) => field.key);
52
- }
53
-
54
- export {
55
- defineField,
56
- resetWorkspaceSettingsFields,
57
- resolveWorkspaceSettingsFieldKeys,
58
- workspaceSettingsFields
59
- };
@@ -1,169 +0,0 @@
1
- import { Type } from "typebox";
2
- import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
3
- import {
4
- normalizeObjectInput,
5
- createCursorListValidator,
6
- normalizeSettingsFieldInput,
7
- recordIdSchema
8
- } from "@jskit-ai/kernel/shared/validators";
9
- import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
10
- import { workspaceSettingsFields } from "./workspaceSettingsFields.js";
11
- import { createWorkspaceRoleCatalog } from "../roles.js";
12
-
13
- function buildCreateBodySchema() {
14
- const properties = {};
15
- for (const field of workspaceSettingsFields) {
16
- properties[field.key] = field.required === false ? Type.Optional(field.inputSchema) : field.inputSchema;
17
- }
18
-
19
- return Type.Object(properties, {
20
- additionalProperties: false,
21
- messages: {
22
- additionalProperties: "Unexpected field.",
23
- default: "Invalid value."
24
- }
25
- });
26
- }
27
-
28
- function buildSettingsOutputSchema() {
29
- const properties = {};
30
- for (const field of workspaceSettingsFields) {
31
- properties[field.key] = field.outputSchema;
32
- }
33
- properties.invitesAvailable = Type.Boolean();
34
- properties.invitesEffective = Type.Boolean();
35
-
36
- return Type.Object(properties, { additionalProperties: false });
37
- }
38
-
39
- function buildResponseRecordSchema() {
40
- return Type.Object(
41
- {
42
- workspace: Type.Object(
43
- {
44
- id: recordIdSchema,
45
- slug: Type.String({ minLength: 1 }),
46
- ownerUserId: recordIdSchema
47
- },
48
- { additionalProperties: false }
49
- ),
50
- settings: buildSettingsOutputSchema(),
51
- roleCatalog: Type.Object(
52
- {
53
- collaborationEnabled: Type.Boolean(),
54
- defaultInviteRole: Type.String(),
55
- roles: Type.Array(Type.Object({}, { additionalProperties: true })),
56
- assignableRoleIds: Type.Array(Type.String({ minLength: 1 }))
57
- },
58
- { additionalProperties: true }
59
- )
60
- },
61
- { additionalProperties: false }
62
- );
63
- }
64
-
65
- function normalizeInput(payload = {}) {
66
- return normalizeSettingsFieldInput(payload, workspaceSettingsFields);
67
- }
68
-
69
- function normalizeOutput(payload = {}) {
70
- const source = normalizeObjectInput(payload);
71
- const workspace = normalizeObjectInput(source.workspace);
72
- const settings = normalizeObjectInput(source.settings);
73
- const normalizedSettings = {};
74
-
75
- for (const field of workspaceSettingsFields) {
76
- const rawValue = Object.hasOwn(settings, field.key)
77
- ? settings[field.key]
78
- : field.resolveDefault({
79
- workspace,
80
- settings
81
- });
82
- normalizedSettings[field.key] = field.normalizeOutput(rawValue, {
83
- workspace,
84
- settings
85
- });
86
- }
87
-
88
- const invitesEnabled = normalizedSettings.invitesEnabled !== false;
89
- const invitesAvailable = settings.invitesAvailable !== false;
90
- const invitesEffective =
91
- typeof settings.invitesEffective === "boolean" ? settings.invitesEffective : invitesEnabled;
92
- normalizedSettings.invitesEnabled = invitesEnabled;
93
- normalizedSettings.invitesAvailable = invitesAvailable;
94
- normalizedSettings.invitesEffective = invitesEffective;
95
- const roleCatalog = normalizeObjectInput(source.roleCatalog);
96
- const hasRoleCatalog =
97
- Array.isArray(roleCatalog.roles) &&
98
- roleCatalog.roles.length > 0 &&
99
- Array.isArray(roleCatalog.assignableRoleIds);
100
-
101
- return {
102
- workspace: {
103
- id: normalizeRecordId(workspace.id, { fallback: "" }),
104
- slug: normalizeText(workspace.slug),
105
- ownerUserId: normalizeRecordId(workspace.ownerUserId, { fallback: "" })
106
- },
107
- settings: normalizedSettings,
108
- roleCatalog: hasRoleCatalog ? roleCatalog : createWorkspaceRoleCatalog()
109
- };
110
- }
111
-
112
- const responseRecordValidator = Object.freeze({
113
- get schema() {
114
- return buildResponseRecordSchema();
115
- },
116
- normalize: normalizeOutput
117
- });
118
-
119
- const resource = {
120
- resource: "workspaceSettings",
121
- messages: {
122
- validation: "Fix invalid workspace settings values and try again.",
123
- saveSuccess: "Workspace settings updated.",
124
- saveError: "Unable to update workspace settings.",
125
- apiValidation: "Validation failed."
126
- },
127
- operations: {
128
- view: {
129
- method: "GET",
130
- outputValidator: responseRecordValidator
131
- },
132
- list: {
133
- method: "GET",
134
- outputValidator: createCursorListValidator(responseRecordValidator)
135
- },
136
- create: {
137
- method: "POST",
138
- bodyValidator: {
139
- get schema() {
140
- return buildCreateBodySchema();
141
- },
142
- normalize: normalizeInput
143
- },
144
- outputValidator: responseRecordValidator
145
- },
146
- replace: {
147
- method: "PUT",
148
- bodyValidator: {
149
- get schema() {
150
- return buildCreateBodySchema();
151
- },
152
- normalize: normalizeInput
153
- },
154
- outputValidator: responseRecordValidator
155
- },
156
- patch: {
157
- method: "PATCH",
158
- bodyValidator: {
159
- get schema() {
160
- return Type.Partial(buildCreateBodySchema(), { additionalProperties: false });
161
- },
162
- normalize: normalizeInput
163
- },
164
- outputValidator: responseRecordValidator
165
- }
166
- }
167
- };
168
-
169
- export { resource as workspaceSettingsResource };
@@ -1,161 +0,0 @@
1
- import {
2
- hasPermission,
3
- normalizePermissionList
4
- } from "@jskit-ai/kernel/shared/support";
5
-
6
- const OWNER_ROLE_ID = "owner";
7
- const ADMIN_ROLE_ID = "admin";
8
- const MEMBER_ROLE_ID = "member";
9
-
10
- function asRecord(value) {
11
- if (!value || typeof value !== "object" || Array.isArray(value)) {
12
- return {};
13
- }
14
- return value;
15
- }
16
-
17
- function normalizeRoleId(value) {
18
- return String(value || "")
19
- .trim()
20
- .toLowerCase();
21
- }
22
-
23
- function resolveInheritedRolePermissions(roleSid, configuredRoles = {}, seenRoleIds = new Set()) {
24
- if (seenRoleIds.has(roleSid)) {
25
- throw new TypeError(`roleCatalog role "${roleSid}" has circular inheritance.`);
26
- }
27
-
28
- const source = asRecord(configuredRoles[roleSid]);
29
- const inheritedRoleId = normalizeRoleId(source.inherits);
30
- const directPermissions = normalizePermissionList(source.permissions);
31
- if (!inheritedRoleId) {
32
- return directPermissions;
33
- }
34
-
35
- if (!Object.hasOwn(configuredRoles, inheritedRoleId)) {
36
- throw new TypeError(`roleCatalog role "${roleSid}" inherits unknown role "${inheritedRoleId}".`);
37
- }
38
-
39
- const nextSeenRoleIds = new Set(seenRoleIds);
40
- nextSeenRoleIds.add(roleSid);
41
-
42
- return normalizePermissionList([
43
- ...resolveInheritedRolePermissions(inheritedRoleId, configuredRoles, nextSeenRoleIds),
44
- ...directPermissions
45
- ]);
46
- }
47
-
48
- function createRoleDescriptor(roleSid, configuredDefinition, configuredRoles = {}) {
49
- const source = asRecord(configuredDefinition);
50
- const assignable = roleSid === OWNER_ROLE_ID ? false : source.assignable === true;
51
- const permissions = resolveInheritedRolePermissions(roleSid, configuredRoles);
52
-
53
- return Object.freeze({
54
- id: roleSid,
55
- assignable,
56
- permissions: Object.freeze([...permissions])
57
- });
58
- }
59
-
60
- function listConfiguredRoleIds(appConfig = {}) {
61
- const configuredRoles = normalizeConfiguredRoles(appConfig);
62
- return Object.freeze(Object.keys(configuredRoles));
63
- }
64
-
65
- function resolveConfiguredDefaultInviteRole(appConfig = {}) {
66
- return normalizeRoleId(appConfig?.roleCatalog?.workspace?.defaultInviteRole);
67
- }
68
-
69
- function normalizeConfiguredRoles(appConfig = {}) {
70
- const roleCatalog = asRecord(appConfig?.roleCatalog);
71
- const configuredRoles = asRecord(roleCatalog.roles);
72
- const normalizedRoles = {};
73
-
74
- for (const [roleSid, roleDefinition] of Object.entries(configuredRoles)) {
75
- const normalizedRoleId = normalizeRoleId(roleSid);
76
- if (!normalizedRoleId) {
77
- continue;
78
- }
79
- normalizedRoles[normalizedRoleId] = roleDefinition;
80
- }
81
-
82
- return normalizedRoles;
83
- }
84
-
85
- function createWorkspaceRoleCatalog(appConfig = {}) {
86
- const configuredRoles = normalizeConfiguredRoles(appConfig);
87
- const roleIds = listConfiguredRoleIds(appConfig);
88
- const roles = roleIds.map((roleSid) => createRoleDescriptor(roleSid, configuredRoles[roleSid], configuredRoles));
89
- const assignableRoleIds = roles.filter((role) => role.assignable).map((role) => role.id);
90
- const configuredDefaultInviteRole = resolveConfiguredDefaultInviteRole(appConfig);
91
- const defaultInviteRole = assignableRoleIds.includes(configuredDefaultInviteRole)
92
- ? configuredDefaultInviteRole
93
- : assignableRoleIds[0] || "";
94
-
95
- return Object.freeze({
96
- collaborationEnabled: assignableRoleIds.length > 0 && Boolean(defaultInviteRole),
97
- defaultInviteRole,
98
- roles: Object.freeze(
99
- roles.map((role) =>
100
- Object.freeze({
101
- id: role.id,
102
- assignable: role.assignable,
103
- permissions: Object.freeze([...role.permissions])
104
- })
105
- )
106
- ),
107
- assignableRoleIds: Object.freeze([...assignableRoleIds])
108
- });
109
- }
110
-
111
- function cloneWorkspaceRoleCatalog(roleCatalog = null) {
112
- const source = asRecord(roleCatalog);
113
-
114
- return {
115
- collaborationEnabled: source.collaborationEnabled === true,
116
- defaultInviteRole: String(source.defaultInviteRole || ""),
117
- roles: Array.isArray(source.roles)
118
- ? source.roles.map((role) => ({
119
- id: normalizeRoleId(role?.id),
120
- assignable: role?.assignable === true,
121
- permissions: Array.isArray(role?.permissions) ? [...role.permissions] : []
122
- }))
123
- : [],
124
- assignableRoleIds: Array.isArray(source.assignableRoleIds) ? [...source.assignableRoleIds] : []
125
- };
126
- }
127
-
128
- function listRoleDescriptors(appConfig = {}) {
129
- const roleCatalog = createWorkspaceRoleCatalog(appConfig);
130
- return roleCatalog.roles.map((role) => ({
131
- id: role.id,
132
- assignable: role.assignable,
133
- permissions: [...role.permissions]
134
- }));
135
- }
136
-
137
- function resolveRolePermissions(roleSid, appConfig = {}) {
138
- const normalizedRoleId = normalizeRoleId(roleSid);
139
- if (!normalizedRoleId) {
140
- return [];
141
- }
142
-
143
- const roleCatalog = createWorkspaceRoleCatalog(appConfig);
144
- const role = roleCatalog.roles.find((entry) => entry.id === normalizedRoleId);
145
- if (!role) {
146
- return [];
147
- }
148
-
149
- return [...role.permissions];
150
- }
151
-
152
- export {
153
- OWNER_ROLE_ID,
154
- ADMIN_ROLE_ID,
155
- MEMBER_ROLE_ID,
156
- resolveRolePermissions,
157
- listRoleDescriptors,
158
- createWorkspaceRoleCatalog,
159
- cloneWorkspaceRoleCatalog,
160
- hasPermission
161
- };
@@ -1,43 +0,0 @@
1
- import { normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
2
- import { splitPathQueryAndHash } from "@jskit-ai/kernel/shared/support";
3
-
4
- const USERS_PUBLIC_API_BASE_PATH = "/api";
5
- const USERS_WORKSPACE_API_BASE_PATH = "/api/w/:workspaceSlug";
6
-
7
- function normalizeApiRelativePath(relativePath = "/") {
8
- const { pathname, queryString, hash } = splitPathQueryAndHash(relativePath);
9
- const normalizedPath = normalizePathname(pathname || "/") || "/";
10
- const normalizedQueryString = String(queryString || "").trim().replace(/^\?+/, "");
11
- const normalizedHash = String(hash || "").trim();
12
- const querySuffix = normalizedQueryString ? `?${normalizedQueryString}` : "";
13
- return `${normalizedPath}${querySuffix}${normalizedHash}`;
14
- }
15
-
16
- function normalizeSurfaceWorkspaceRequirement(value = false) {
17
- return value === true;
18
- }
19
-
20
- function resolveApiBasePath({ surfaceRequiresWorkspace = false, relativePath = "/" } = {}) {
21
- const basePath = normalizeSurfaceWorkspaceRequirement(surfaceRequiresWorkspace)
22
- ? USERS_WORKSPACE_API_BASE_PATH
23
- : USERS_PUBLIC_API_BASE_PATH;
24
- const normalizedRelativePath = normalizeApiRelativePath(relativePath);
25
-
26
- if (normalizedRelativePath === "/") {
27
- return basePath;
28
- }
29
-
30
- if (normalizedRelativePath.startsWith("/?") || normalizedRelativePath.startsWith("/#")) {
31
- return `${basePath}${normalizedRelativePath.slice(1)}`;
32
- }
33
-
34
- return `${basePath}${normalizedRelativePath}`;
35
- }
36
-
37
- export {
38
- USERS_PUBLIC_API_BASE_PATH,
39
- USERS_WORKSPACE_API_BASE_PATH,
40
- normalizeApiRelativePath,
41
- normalizeSurfaceWorkspaceRequirement,
42
- resolveApiBasePath
43
- };