@jskit-ai/users-core 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.descriptor.mjs +464 -0
- package/package.json +35 -0
- package/src/server/UsersCoreServiceProvider.js +74 -0
- package/src/server/accountNotifications/accountNotificationsActions.js +39 -0
- package/src/server/accountNotifications/accountNotificationsService.js +41 -0
- package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +41 -0
- package/src/server/accountNotifications/registerAccountNotifications.js +39 -0
- package/src/server/accountPreferences/accountPreferencesActions.js +39 -0
- package/src/server/accountPreferences/accountPreferencesService.js +41 -0
- package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +41 -0
- package/src/server/accountPreferences/registerAccountPreferences.js +39 -0
- package/src/server/accountProfile/accountProfileActions.js +137 -0
- package/src/server/accountProfile/accountProfileService.js +124 -0
- package/src/server/accountProfile/avatarService.js +141 -0
- package/src/server/accountProfile/avatarStorageService.js +132 -0
- package/src/server/accountProfile/bootAccountProfileRoutes.js +166 -0
- package/src/server/accountProfile/registerAccountProfile.js +62 -0
- package/src/server/accountProfile/registerAvatarMultipartSupport.js +43 -0
- package/src/server/accountSecurity/accountSecurityActions.js +144 -0
- package/src/server/accountSecurity/accountSecurityService.js +103 -0
- package/src/server/accountSecurity/bootAccountSecurityRoutes.js +183 -0
- package/src/server/accountSecurity/registerAccountSecurity.js +31 -0
- package/src/server/common/README.md +21 -0
- package/src/server/common/contributors/README.md +11 -0
- package/src/server/common/contributors/workspaceActionContextContributor.js +79 -0
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +34 -0
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +79 -0
- package/src/server/common/diTokens.js +21 -0
- package/src/server/common/formatters/README.md +11 -0
- package/src/server/common/formatters/accountAvatarFormatter.js +42 -0
- package/src/server/common/formatters/accountSecurityStatusFormatter.js +71 -0
- package/src/server/common/formatters/accountSettingsResponseFormatter.js +62 -0
- package/src/server/common/formatters/workspaceFormatter.js +46 -0
- package/src/server/common/registerCommonRepositories.js +45 -0
- package/src/server/common/registerSharedApi.js +9 -0
- package/src/server/common/repositories/README.md +24 -0
- package/src/server/common/repositories/repositoryUtils.js +50 -0
- package/src/server/common/repositories/userProfilesRepository.js +251 -0
- package/src/server/common/repositories/userSettingsRepository.js +179 -0
- package/src/server/common/repositories/workspaceInvitesRepository.js +172 -0
- package/src/server/common/repositories/workspaceMembershipsRepository.js +157 -0
- package/src/server/common/repositories/workspacesRepository.js +183 -0
- package/src/server/common/routes/README.md +11 -0
- package/src/server/common/services/README.md +12 -0
- package/src/server/common/services/accountContextService.js +31 -0
- package/src/server/common/services/authProfileSyncService.js +128 -0
- package/src/server/common/services/workspaceContextService.js +270 -0
- package/src/server/common/support/deepFreeze.js +17 -0
- package/src/server/common/support/realtimeServiceEvents.js +94 -0
- package/src/server/common/support/resolveActionUser.js +11 -0
- package/src/server/common/support/workspaceRoutePaths.js +17 -0
- package/src/server/common/validators/README.md +11 -0
- package/src/server/common/validators/authenticatedUserValidator.js +42 -0
- package/src/server/common/validators/routeParamsValidator.js +62 -0
- package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +64 -0
- package/src/server/consoleSettings/consoleService.js +36 -0
- package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
- package/src/server/consoleSettings/consoleSettingsRepository.js +111 -0
- package/src/server/consoleSettings/consoleSettingsService.js +40 -0
- package/src/server/consoleSettings/registerConsoleSettings.js +57 -0
- package/src/server/registerWorkspaceBootstrap.js +36 -0
- package/src/server/registerWorkspaceCore.js +95 -0
- package/src/server/support/resolveWorkspace.js +16 -0
- package/src/server/support/workspaceActionSurfaces.js +135 -0
- package/src/server/support/workspaceInvitationsPolicy.js +45 -0
- package/src/server/support/workspaceRouteInput.js +22 -0
- package/src/server/workspaceBootstrapContributor.js +401 -0
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +73 -0
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +19 -0
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +65 -0
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +238 -0
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +112 -0
- package/src/server/workspaceMembers/workspaceMembersActions.js +186 -0
- package/src/server/workspaceMembers/workspaceMembersService.js +210 -0
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +63 -0
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +128 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +74 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +137 -0
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +77 -0
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +67 -0
- package/src/server/workspaceSettings/workspaceSettingsActions.js +72 -0
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +135 -0
- package/src/server/workspaceSettings/workspaceSettingsService.js +65 -0
- package/src/shared/events/usersEvents.js +19 -0
- package/src/shared/index.js +91 -0
- package/src/shared/operationMessages.js +16 -0
- package/src/shared/resources/consoleSettingsFields.js +55 -0
- package/src/shared/resources/consoleSettingsResource.js +139 -0
- package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
- package/src/shared/resources/userProfileResource.js +148 -0
- package/src/shared/resources/userSettingsFields.js +71 -0
- package/src/shared/resources/userSettingsResource.js +416 -0
- package/src/shared/resources/workspaceMembersResource.js +352 -0
- package/src/shared/resources/workspacePendingInvitationsResource.js +87 -0
- package/src/shared/resources/workspaceResource.js +149 -0
- package/src/shared/resources/workspaceSettingsFields.js +60 -0
- package/src/shared/resources/workspaceSettingsResource.js +178 -0
- package/src/shared/roles.js +136 -0
- package/src/shared/settings.js +31 -0
- package/src/shared/support/usersApiPaths.js +34 -0
- package/src/shared/support/usersVisibility.js +45 -0
- package/src/shared/support/workspacePathModel.js +145 -0
- package/src/shared/tenancyMode.js +35 -0
- package/src/shared/tenancyProfile.js +73 -0
- package/templates/config/workspaceRoles.js +30 -0
- package/templates/migrations/users_core_console_owner.cjs +39 -0
- package/templates/migrations/users_core_initial.cjs +118 -0
- package/templates/migrations/users_core_profile_username.cjs +98 -0
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
- package/templates/packages/main/src/shared/resources/userSettingsFields.js +138 -0
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +105 -0
- package/test/authProfileSyncService.test.js +119 -0
- package/test/avatarService.test.js +114 -0
- package/test/avatarStorageService.test.js +61 -0
- package/test/consoleService.test.js +57 -0
- package/test/consoleSettingsService.test.js +86 -0
- package/test/exportsContract.test.js +38 -0
- package/test/registerAvatarMultipartSupport.test.js +64 -0
- package/test/registerServiceRealtimeEvents.test.js +160 -0
- package/test/registerWorkspaceDirectory.test.js +26 -0
- package/test/registerWorkspaceSettings.test.js +44 -0
- package/test/resourcesCanonical.test.js +90 -0
- package/test/roles.test.js +74 -0
- package/test/settingsFieldRegistriesSingleton.test.js +24 -0
- package/test/tenancyProfile.test.js +67 -0
- package/test/userSettingsResource.test.js +31 -0
- package/test/usersApiPaths.test.js +31 -0
- package/test/usersRouteRequestInputValidator.test.js +556 -0
- package/test/usersRouteResources.test.js +113 -0
- package/test/usersRouteValidators.test.js +49 -0
- package/test/usersVisibility.test.js +22 -0
- package/test/workspaceActionContextContributor.test.js +251 -0
- package/test/workspaceActionSurfaces.test.js +105 -0
- package/test/workspaceAuthPolicyContextResolver.test.js +119 -0
- package/test/workspaceBootstrapContributor.test.js +466 -0
- package/test/workspaceInvitationsPolicy.test.js +71 -0
- package/test/workspaceInvitesRepository.test.js +111 -0
- package/test/workspaceMembersService.test.js +400 -0
- package/test/workspacePathModel.test.js +93 -0
- package/test/workspacePendingInvitationsResource.test.js +38 -0
- package/test/workspacePendingInvitationsService.test.js +151 -0
- package/test/workspaceRouteVisibilityResolver.test.js +83 -0
- package/test/workspaceService.test.js +480 -0
- package/test/workspaceSettingsActions.test.js +42 -0
- package/test/workspaceSettingsRepository.test.js +156 -0
- package/test/workspaceSettingsResource.test.js +156 -0
- package/test/workspaceSettingsService.test.js +120 -0
- package/test-support/registerDefaultSettingsFields.js +3 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
3
|
+
import {
|
|
4
|
+
normalizeObjectInput,
|
|
5
|
+
createCursorListValidator
|
|
6
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
7
|
+
import { workspaceSettingsFields } from "./workspaceSettingsFields.js";
|
|
8
|
+
import { createWorkspaceRoleCatalog } from "../roles.js";
|
|
9
|
+
|
|
10
|
+
function buildCreateBodySchema() {
|
|
11
|
+
const properties = {};
|
|
12
|
+
for (const field of workspaceSettingsFields) {
|
|
13
|
+
properties[field.key] = field.required === false ? Type.Optional(field.inputSchema) : field.inputSchema;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return Type.Object(properties, {
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
messages: {
|
|
19
|
+
additionalProperties: "Unexpected field.",
|
|
20
|
+
default: "Invalid value."
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildSettingsOutputSchema() {
|
|
26
|
+
const properties = {};
|
|
27
|
+
for (const field of workspaceSettingsFields) {
|
|
28
|
+
properties[field.key] = field.outputSchema;
|
|
29
|
+
}
|
|
30
|
+
properties.invitesAvailable = Type.Boolean();
|
|
31
|
+
properties.invitesEffective = Type.Boolean();
|
|
32
|
+
|
|
33
|
+
return Type.Object(properties, { additionalProperties: false });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildResponseRecordSchema() {
|
|
37
|
+
return Type.Object(
|
|
38
|
+
{
|
|
39
|
+
workspace: Type.Object(
|
|
40
|
+
{
|
|
41
|
+
id: Type.Integer({ minimum: 1 }),
|
|
42
|
+
slug: Type.String({ minLength: 1 }),
|
|
43
|
+
ownerUserId: Type.Integer({ minimum: 1 })
|
|
44
|
+
},
|
|
45
|
+
{ additionalProperties: false }
|
|
46
|
+
),
|
|
47
|
+
settings: buildSettingsOutputSchema(),
|
|
48
|
+
roleCatalog: Type.Object(
|
|
49
|
+
{
|
|
50
|
+
collaborationEnabled: Type.Boolean(),
|
|
51
|
+
defaultInviteRole: Type.String(),
|
|
52
|
+
roles: Type.Array(Type.Object({}, { additionalProperties: true })),
|
|
53
|
+
assignableRoleIds: Type.Array(Type.String({ minLength: 1 }))
|
|
54
|
+
},
|
|
55
|
+
{ additionalProperties: true }
|
|
56
|
+
)
|
|
57
|
+
},
|
|
58
|
+
{ additionalProperties: false }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeInput(payload = {}) {
|
|
63
|
+
const source = normalizeObjectInput(payload);
|
|
64
|
+
const normalized = {};
|
|
65
|
+
|
|
66
|
+
for (const field of workspaceSettingsFields) {
|
|
67
|
+
if (!Object.hasOwn(source, field.key)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
normalized[field.key] = field.normalizeInput(source[field.key], {
|
|
71
|
+
payload: source
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return normalized;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeOutput(payload = {}) {
|
|
79
|
+
const source = normalizeObjectInput(payload);
|
|
80
|
+
const workspace = normalizeObjectInput(source.workspace);
|
|
81
|
+
const settings = normalizeObjectInput(source.settings);
|
|
82
|
+
const normalizedSettings = {};
|
|
83
|
+
|
|
84
|
+
for (const field of workspaceSettingsFields) {
|
|
85
|
+
const rawValue = Object.hasOwn(settings, field.key)
|
|
86
|
+
? settings[field.key]
|
|
87
|
+
: field.resolveDefault({
|
|
88
|
+
workspace,
|
|
89
|
+
settings
|
|
90
|
+
});
|
|
91
|
+
normalizedSettings[field.key] = field.normalizeOutput(rawValue, {
|
|
92
|
+
workspace,
|
|
93
|
+
settings
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const invitesEnabled = normalizedSettings.invitesEnabled !== false;
|
|
98
|
+
const invitesAvailable = settings.invitesAvailable !== false;
|
|
99
|
+
const invitesEffective =
|
|
100
|
+
typeof settings.invitesEffective === "boolean" ? settings.invitesEffective : invitesEnabled;
|
|
101
|
+
normalizedSettings.invitesEnabled = invitesEnabled;
|
|
102
|
+
normalizedSettings.invitesAvailable = invitesAvailable;
|
|
103
|
+
normalizedSettings.invitesEffective = invitesEffective;
|
|
104
|
+
const roleCatalog = normalizeObjectInput(source.roleCatalog);
|
|
105
|
+
const hasRoleCatalog =
|
|
106
|
+
Array.isArray(roleCatalog.roles) &&
|
|
107
|
+
roleCatalog.roles.length > 0 &&
|
|
108
|
+
Array.isArray(roleCatalog.assignableRoleIds);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
workspace: {
|
|
112
|
+
id: Number(workspace.id),
|
|
113
|
+
slug: normalizeText(workspace.slug),
|
|
114
|
+
ownerUserId: Number(workspace.ownerUserId)
|
|
115
|
+
},
|
|
116
|
+
settings: normalizedSettings,
|
|
117
|
+
roleCatalog: hasRoleCatalog ? roleCatalog : createWorkspaceRoleCatalog()
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const responseRecordValidator = Object.freeze({
|
|
122
|
+
get schema() {
|
|
123
|
+
return buildResponseRecordSchema();
|
|
124
|
+
},
|
|
125
|
+
normalize: normalizeOutput
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const resource = {
|
|
129
|
+
resource: "workspaceSettings",
|
|
130
|
+
messages: {
|
|
131
|
+
validation: "Fix invalid workspace settings values and try again.",
|
|
132
|
+
saveSuccess: "Workspace settings updated.",
|
|
133
|
+
saveError: "Unable to update workspace settings.",
|
|
134
|
+
apiValidation: "Validation failed."
|
|
135
|
+
},
|
|
136
|
+
operations: {
|
|
137
|
+
view: {
|
|
138
|
+
method: "GET",
|
|
139
|
+
outputValidator: responseRecordValidator
|
|
140
|
+
},
|
|
141
|
+
list: {
|
|
142
|
+
method: "GET",
|
|
143
|
+
outputValidator: createCursorListValidator(responseRecordValidator)
|
|
144
|
+
},
|
|
145
|
+
create: {
|
|
146
|
+
method: "POST",
|
|
147
|
+
bodyValidator: {
|
|
148
|
+
get schema() {
|
|
149
|
+
return buildCreateBodySchema();
|
|
150
|
+
},
|
|
151
|
+
normalize: normalizeInput
|
|
152
|
+
},
|
|
153
|
+
outputValidator: responseRecordValidator
|
|
154
|
+
},
|
|
155
|
+
replace: {
|
|
156
|
+
method: "PUT",
|
|
157
|
+
bodyValidator: {
|
|
158
|
+
get schema() {
|
|
159
|
+
return buildCreateBodySchema();
|
|
160
|
+
},
|
|
161
|
+
normalize: normalizeInput
|
|
162
|
+
},
|
|
163
|
+
outputValidator: responseRecordValidator
|
|
164
|
+
},
|
|
165
|
+
patch: {
|
|
166
|
+
method: "PATCH",
|
|
167
|
+
bodyValidator: {
|
|
168
|
+
get schema() {
|
|
169
|
+
return Type.Partial(buildCreateBodySchema(), { additionalProperties: false });
|
|
170
|
+
},
|
|
171
|
+
normalize: normalizeInput
|
|
172
|
+
},
|
|
173
|
+
outputValidator: responseRecordValidator
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export { resource as workspaceSettingsResource };
|
|
@@ -0,0 +1,136 @@
|
|
|
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 createRoleDescriptor(roleId, configuredDefinition) {
|
|
24
|
+
const source = asRecord(configuredDefinition);
|
|
25
|
+
const assignable = roleId === OWNER_ROLE_ID ? false : source.assignable === true;
|
|
26
|
+
const permissions = normalizePermissionList(source.permissions);
|
|
27
|
+
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
id: roleId,
|
|
30
|
+
assignable,
|
|
31
|
+
permissions: Object.freeze([...permissions])
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function listConfiguredRoleIds(appConfig = {}) {
|
|
36
|
+
const configuredRoles = normalizeConfiguredRoles(appConfig);
|
|
37
|
+
return Object.freeze(Object.keys(configuredRoles));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveConfiguredDefaultInviteRole(appConfig = {}) {
|
|
41
|
+
return normalizeRoleId(appConfig?.workspaceRoles?.defaultInviteRole);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeConfiguredRoles(appConfig = {}) {
|
|
45
|
+
const workspaceRoles = asRecord(appConfig?.workspaceRoles);
|
|
46
|
+
const configuredRoles = asRecord(workspaceRoles.roles);
|
|
47
|
+
const normalizedRoles = {};
|
|
48
|
+
|
|
49
|
+
for (const [roleId, roleDefinition] of Object.entries(configuredRoles)) {
|
|
50
|
+
const normalizedRoleId = normalizeRoleId(roleId);
|
|
51
|
+
if (!normalizedRoleId) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
normalizedRoles[normalizedRoleId] = roleDefinition;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return normalizedRoles;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createWorkspaceRoleCatalog(appConfig = {}) {
|
|
61
|
+
const configuredRoles = normalizeConfiguredRoles(appConfig);
|
|
62
|
+
const roleIds = listConfiguredRoleIds(appConfig);
|
|
63
|
+
const roles = roleIds.map((roleId) => createRoleDescriptor(roleId, configuredRoles[roleId]));
|
|
64
|
+
const assignableRoleIds = roles.filter((role) => role.assignable).map((role) => role.id);
|
|
65
|
+
const configuredDefaultInviteRole = resolveConfiguredDefaultInviteRole(appConfig);
|
|
66
|
+
const defaultInviteRole = assignableRoleIds.includes(configuredDefaultInviteRole)
|
|
67
|
+
? configuredDefaultInviteRole
|
|
68
|
+
: assignableRoleIds[0] || "";
|
|
69
|
+
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
collaborationEnabled: assignableRoleIds.length > 0 && Boolean(defaultInviteRole),
|
|
72
|
+
defaultInviteRole,
|
|
73
|
+
roles: Object.freeze(
|
|
74
|
+
roles.map((role) =>
|
|
75
|
+
Object.freeze({
|
|
76
|
+
id: role.id,
|
|
77
|
+
assignable: role.assignable,
|
|
78
|
+
permissions: Object.freeze([...role.permissions])
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
),
|
|
82
|
+
assignableRoleIds: Object.freeze([...assignableRoleIds])
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function cloneWorkspaceRoleCatalog(roleCatalog = null) {
|
|
87
|
+
const source = asRecord(roleCatalog);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
collaborationEnabled: source.collaborationEnabled === true,
|
|
91
|
+
defaultInviteRole: String(source.defaultInviteRole || ""),
|
|
92
|
+
roles: Array.isArray(source.roles)
|
|
93
|
+
? source.roles.map((role) => ({
|
|
94
|
+
id: normalizeRoleId(role?.id),
|
|
95
|
+
assignable: role?.assignable === true,
|
|
96
|
+
permissions: Array.isArray(role?.permissions) ? [...role.permissions] : []
|
|
97
|
+
}))
|
|
98
|
+
: [],
|
|
99
|
+
assignableRoleIds: Array.isArray(source.assignableRoleIds) ? [...source.assignableRoleIds] : []
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function listRoleDescriptors(appConfig = {}) {
|
|
104
|
+
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
105
|
+
return roleCatalog.roles.map((role) => ({
|
|
106
|
+
id: role.id,
|
|
107
|
+
assignable: role.assignable,
|
|
108
|
+
permissions: [...role.permissions]
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveRolePermissions(roleId, appConfig = {}) {
|
|
113
|
+
const normalizedRoleId = normalizeRoleId(roleId);
|
|
114
|
+
if (!normalizedRoleId) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
119
|
+
const role = roleCatalog.roles.find((entry) => entry.id === normalizedRoleId);
|
|
120
|
+
if (!role) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return [...role.permissions];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
OWNER_ROLE_ID,
|
|
129
|
+
ADMIN_ROLE_ID,
|
|
130
|
+
MEMBER_ROLE_ID,
|
|
131
|
+
resolveRolePermissions,
|
|
132
|
+
listRoleDescriptors,
|
|
133
|
+
createWorkspaceRoleCatalog,
|
|
134
|
+
cloneWorkspaceRoleCatalog,
|
|
135
|
+
hasPermission
|
|
136
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const DEFAULT_WORKSPACE_COLOR = "#0F6B54";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_USER_SETTINGS = Object.freeze({
|
|
4
|
+
theme: "system",
|
|
5
|
+
locale: "en",
|
|
6
|
+
timeZone: "UTC",
|
|
7
|
+
dateFormat: "yyyy-mm-dd",
|
|
8
|
+
numberFormat: "1,234.56",
|
|
9
|
+
currencyCode: "USD",
|
|
10
|
+
avatarSize: 64,
|
|
11
|
+
productUpdates: true,
|
|
12
|
+
accountActivity: true,
|
|
13
|
+
securityAlerts: true,
|
|
14
|
+
passwordSignInEnabled: true,
|
|
15
|
+
passwordSetupRequired: false,
|
|
16
|
+
lastActiveWorkspaceId: null
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function coerceWorkspaceColor(value) {
|
|
20
|
+
const normalized = String(value || "").trim();
|
|
21
|
+
if (/^#[0-9A-Fa-f]{6}$/.test(normalized)) {
|
|
22
|
+
return normalized.toUpperCase();
|
|
23
|
+
}
|
|
24
|
+
return DEFAULT_WORKSPACE_COLOR;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
DEFAULT_WORKSPACE_COLOR,
|
|
29
|
+
DEFAULT_USER_SETTINGS,
|
|
30
|
+
coerceWorkspaceColor
|
|
31
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
|
|
2
|
+
|
|
3
|
+
const USERS_PUBLIC_API_BASE_PATH = "/api";
|
|
4
|
+
const USERS_WORKSPACE_API_BASE_PATH = "/api/w/:workspaceSlug/workspace";
|
|
5
|
+
|
|
6
|
+
function normalizeApiRelativePath(relativePath = "/") {
|
|
7
|
+
const normalizedPath = normalizePathname(relativePath);
|
|
8
|
+
return normalizedPath || "/";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeSurfaceWorkspaceRequirement(value = false) {
|
|
12
|
+
return value === true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveApiBasePath({ surfaceRequiresWorkspace = false, relativePath = "/" } = {}) {
|
|
16
|
+
const basePath = normalizeSurfaceWorkspaceRequirement(surfaceRequiresWorkspace)
|
|
17
|
+
? USERS_WORKSPACE_API_BASE_PATH
|
|
18
|
+
: USERS_PUBLIC_API_BASE_PATH;
|
|
19
|
+
const normalizedRelativePath = normalizeApiRelativePath(relativePath);
|
|
20
|
+
|
|
21
|
+
if (normalizedRelativePath === "/") {
|
|
22
|
+
return basePath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return `${basePath}${normalizedRelativePath}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
USERS_PUBLIC_API_BASE_PATH,
|
|
30
|
+
USERS_WORKSPACE_API_BASE_PATH,
|
|
31
|
+
normalizeApiRelativePath,
|
|
32
|
+
normalizeSurfaceWorkspaceRequirement,
|
|
33
|
+
resolveApiBasePath
|
|
34
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { normalizeRouteVisibilityToken } from "@jskit-ai/kernel/shared/support/visibility";
|
|
2
|
+
|
|
3
|
+
const USERS_ROUTE_VISIBILITY_PUBLIC = "public";
|
|
4
|
+
const USERS_ROUTE_VISIBILITY_USER = "user";
|
|
5
|
+
const USERS_ROUTE_VISIBILITY_WORKSPACE = "workspace";
|
|
6
|
+
const USERS_ROUTE_VISIBILITY_WORKSPACE_USER = "workspace_user";
|
|
7
|
+
|
|
8
|
+
const USERS_ROUTE_VISIBILITY_LEVELS = Object.freeze([
|
|
9
|
+
USERS_ROUTE_VISIBILITY_PUBLIC,
|
|
10
|
+
USERS_ROUTE_VISIBILITY_USER,
|
|
11
|
+
USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
12
|
+
USERS_ROUTE_VISIBILITY_WORKSPACE_USER
|
|
13
|
+
]);
|
|
14
|
+
const USERS_ROUTE_VISIBILITY_LEVEL_SET = new Set(USERS_ROUTE_VISIBILITY_LEVELS);
|
|
15
|
+
|
|
16
|
+
function normalizeScopedRouteVisibility(value, { fallback = USERS_ROUTE_VISIBILITY_PUBLIC } = {}) {
|
|
17
|
+
const normalized = normalizeRouteVisibilityToken(value, { fallback: USERS_ROUTE_VISIBILITY_PUBLIC });
|
|
18
|
+
if (USERS_ROUTE_VISIBILITY_LEVEL_SET.has(normalized)) {
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const normalizedFallback = normalizeRouteVisibilityToken(fallback, { fallback: USERS_ROUTE_VISIBILITY_PUBLIC });
|
|
23
|
+
if (USERS_ROUTE_VISIBILITY_LEVEL_SET.has(normalizedFallback)) {
|
|
24
|
+
return normalizedFallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return USERS_ROUTE_VISIBILITY_PUBLIC;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isWorkspaceVisibility(visibility = "") {
|
|
31
|
+
const normalized = normalizeScopedRouteVisibility(visibility, {
|
|
32
|
+
fallback: USERS_ROUTE_VISIBILITY_PUBLIC
|
|
33
|
+
});
|
|
34
|
+
return normalized === USERS_ROUTE_VISIBILITY_WORKSPACE || normalized === USERS_ROUTE_VISIBILITY_WORKSPACE_USER;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
USERS_ROUTE_VISIBILITY_PUBLIC,
|
|
39
|
+
USERS_ROUTE_VISIBILITY_USER,
|
|
40
|
+
USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
41
|
+
USERS_ROUTE_VISIBILITY_WORKSPACE_USER,
|
|
42
|
+
USERS_ROUTE_VISIBILITY_LEVELS,
|
|
43
|
+
normalizeScopedRouteVisibility,
|
|
44
|
+
isWorkspaceVisibility
|
|
45
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveSurfaceRouteBaseFromPagesRoot,
|
|
3
|
+
normalizeSurfaceId
|
|
4
|
+
} from "@jskit-ai/kernel/shared/surface/registry";
|
|
5
|
+
import { normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
|
|
6
|
+
|
|
7
|
+
function normalizeWorkspaceBasePath(workspaceBasePath = "/w") {
|
|
8
|
+
return normalizePathname(workspaceBasePath || "/w");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeSurfaceSegment(segmentLike = "") {
|
|
12
|
+
const normalizedPath = normalizePathname(segmentLike || "/");
|
|
13
|
+
if (normalizedPath === "/") {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
return normalizedPath.replace(/^\/+/, "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeSurfaceRouteBase(routeBaseLike = "") {
|
|
20
|
+
const rawRouteBase = String(routeBaseLike || "").trim();
|
|
21
|
+
if (!rawRouteBase || rawRouteBase === "/") {
|
|
22
|
+
return "/";
|
|
23
|
+
}
|
|
24
|
+
const withoutLeadingSlash = rawRouteBase.startsWith("/") ? rawRouteBase.slice(1) : rawRouteBase;
|
|
25
|
+
return deriveSurfaceRouteBaseFromPagesRoot(withoutLeadingSlash || "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeSurfaceSegmentFromRouteBase(routeBase, { workspaceBasePath = "/w" } = {}) {
|
|
29
|
+
const normalizedRouteBase = normalizeSurfaceRouteBase(routeBase);
|
|
30
|
+
if (normalizedRouteBase === "/") {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const normalizedWorkspaceBasePath = normalizeWorkspaceBasePath(workspaceBasePath);
|
|
35
|
+
const workspacePlaceholderRoot = `${normalizedWorkspaceBasePath}/:workspaceSlug`;
|
|
36
|
+
if (normalizedRouteBase === workspacePlaceholderRoot) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
if (normalizedRouteBase.startsWith(`${workspacePlaceholderRoot}/`)) {
|
|
40
|
+
const remainder = normalizedRouteBase.slice(`${workspacePlaceholderRoot}/`.length);
|
|
41
|
+
const firstSegment = remainder.split("/").filter(Boolean)[0] || "";
|
|
42
|
+
return firstSegment.startsWith(":") ? "" : firstSegment;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const firstSegment = normalizedRouteBase.replace(/^\/+/, "").split("/").filter(Boolean)[0] || "";
|
|
46
|
+
if (!firstSegment || firstSegment.startsWith(":")) {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
return firstSegment;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseWorkspacePathname(pathname = "", { workspaceBasePath = "/w" } = {}) {
|
|
53
|
+
const normalizedPathname = normalizePathname(pathname);
|
|
54
|
+
const normalizedWorkspaceBasePath = normalizeWorkspaceBasePath(workspaceBasePath);
|
|
55
|
+
if (!normalizedPathname.startsWith(`${normalizedWorkspaceBasePath}/`)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const trailingPath = normalizedPathname.slice(`${normalizedWorkspaceBasePath}/`.length);
|
|
60
|
+
const segments = trailingPath.split("/").filter(Boolean);
|
|
61
|
+
if (segments.length < 1) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const [workspaceSlug, ...suffixSegments] = segments;
|
|
66
|
+
return {
|
|
67
|
+
workspaceSlug: String(workspaceSlug || "").trim(),
|
|
68
|
+
suffixSegments
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveDefaultWorkspaceSurfaceId({
|
|
73
|
+
defaultSurfaceId = "",
|
|
74
|
+
workspaceSurfaceIds = [],
|
|
75
|
+
surfaceRequiresWorkspace = null
|
|
76
|
+
} = {}) {
|
|
77
|
+
const normalizedDefaultSurfaceId = normalizeSurfaceId(defaultSurfaceId);
|
|
78
|
+
if (
|
|
79
|
+
normalizedDefaultSurfaceId &&
|
|
80
|
+
typeof surfaceRequiresWorkspace === "function" &&
|
|
81
|
+
surfaceRequiresWorkspace(normalizedDefaultSurfaceId)
|
|
82
|
+
) {
|
|
83
|
+
return normalizedDefaultSurfaceId;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const workspaceSurfaceId of Array.isArray(workspaceSurfaceIds) ? workspaceSurfaceIds : []) {
|
|
87
|
+
const normalizedWorkspaceSurfaceId = normalizeSurfaceId(workspaceSurfaceId);
|
|
88
|
+
if (normalizedWorkspaceSurfaceId) {
|
|
89
|
+
return normalizedWorkspaceSurfaceId;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return normalizedDefaultSurfaceId;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveWorkspaceSurfaceIdFromSuffixSegments({
|
|
97
|
+
suffixSegments = [],
|
|
98
|
+
defaultWorkspaceSurfaceId = "",
|
|
99
|
+
workspaceSurfaces = []
|
|
100
|
+
} = {}) {
|
|
101
|
+
const normalizedDefaultWorkspaceSurfaceId = normalizeSurfaceId(defaultWorkspaceSurfaceId);
|
|
102
|
+
if (!Array.isArray(suffixSegments) || suffixSegments.length < 1) {
|
|
103
|
+
return normalizedDefaultWorkspaceSurfaceId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const suffixPath = suffixSegments.join("/");
|
|
107
|
+
const candidates = (Array.isArray(workspaceSurfaces) ? workspaceSurfaces : [])
|
|
108
|
+
.map((entry) => {
|
|
109
|
+
const surfaceId = normalizeSurfaceId(entry?.surfaceId || entry?.id);
|
|
110
|
+
if (!surfaceId || surfaceId === normalizedDefaultWorkspaceSurfaceId) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const segment =
|
|
115
|
+
normalizeSurfaceSegment(entry?.segment) ||
|
|
116
|
+
normalizeSurfaceSegmentFromRouteBase(entry?.routeBase || entry?.pagesRoot) ||
|
|
117
|
+
surfaceId;
|
|
118
|
+
if (!segment) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
surfaceId,
|
|
124
|
+
segment
|
|
125
|
+
};
|
|
126
|
+
})
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.sort((left, right) => right.segment.length - left.segment.length);
|
|
129
|
+
|
|
130
|
+
for (const candidate of candidates) {
|
|
131
|
+
if (suffixPath === candidate.segment || suffixPath.startsWith(`${candidate.segment}/`)) {
|
|
132
|
+
return candidate.surfaceId;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return normalizedDefaultWorkspaceSurfaceId;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
normalizePathname,
|
|
141
|
+
normalizeSurfaceSegmentFromRouteBase,
|
|
142
|
+
parseWorkspacePathname,
|
|
143
|
+
resolveDefaultWorkspaceSurfaceId,
|
|
144
|
+
resolveWorkspaceSurfaceIdFromSuffixSegments
|
|
145
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const TENANCY_MODE_NONE = "none";
|
|
2
|
+
const TENANCY_MODE_PERSONAL = "personal";
|
|
3
|
+
const TENANCY_MODE_WORKSPACE = "workspace";
|
|
4
|
+
|
|
5
|
+
const TENANCY_MODES = Object.freeze([
|
|
6
|
+
TENANCY_MODE_NONE,
|
|
7
|
+
TENANCY_MODE_PERSONAL,
|
|
8
|
+
TENANCY_MODE_WORKSPACE
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
function normalizeTenancyMode(value = "") {
|
|
12
|
+
const normalized = String(value || "")
|
|
13
|
+
.trim()
|
|
14
|
+
.toLowerCase();
|
|
15
|
+
if (!TENANCY_MODES.includes(normalized)) {
|
|
16
|
+
return TENANCY_MODE_NONE;
|
|
17
|
+
}
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isTenancyMode(value = "") {
|
|
22
|
+
const normalized = String(value || "")
|
|
23
|
+
.trim()
|
|
24
|
+
.toLowerCase();
|
|
25
|
+
return TENANCY_MODES.includes(normalized);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
TENANCY_MODE_NONE,
|
|
30
|
+
TENANCY_MODE_PERSONAL,
|
|
31
|
+
TENANCY_MODE_WORKSPACE,
|
|
32
|
+
TENANCY_MODES,
|
|
33
|
+
normalizeTenancyMode,
|
|
34
|
+
isTenancyMode
|
|
35
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TENANCY_MODE_NONE,
|
|
3
|
+
TENANCY_MODE_PERSONAL,
|
|
4
|
+
TENANCY_MODE_WORKSPACE,
|
|
5
|
+
normalizeTenancyMode
|
|
6
|
+
} from "./tenancyMode.js";
|
|
7
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
|
+
|
|
9
|
+
const WORKSPACE_SLUG_POLICY_NONE = "none";
|
|
10
|
+
const WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME = "immutable_username";
|
|
11
|
+
const WORKSPACE_SLUG_POLICY_USER_SELECTED = "user_selected";
|
|
12
|
+
|
|
13
|
+
function resolveWorkspacePolicyOverrides(appConfig = {}) {
|
|
14
|
+
const tenancyPolicy = isRecord(appConfig?.tenancyPolicy) ? appConfig.tenancyPolicy : {};
|
|
15
|
+
const workspacePolicy = isRecord(tenancyPolicy.workspace) ? tenancyPolicy.workspace : {};
|
|
16
|
+
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
allowSelfCreate: typeof workspacePolicy.allowSelfCreate === "boolean" ? workspacePolicy.allowSelfCreate : null
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveWorkspacePolicy(mode, overrides = {}) {
|
|
23
|
+
if (mode === TENANCY_MODE_NONE) {
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
enabled: false,
|
|
26
|
+
autoProvision: false,
|
|
27
|
+
allowSelfCreate: false,
|
|
28
|
+
slugPolicy: WORKSPACE_SLUG_POLICY_NONE
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (mode === TENANCY_MODE_PERSONAL) {
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
enabled: true,
|
|
35
|
+
autoProvision: true,
|
|
36
|
+
allowSelfCreate: false,
|
|
37
|
+
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
enabled: true,
|
|
43
|
+
autoProvision: false,
|
|
44
|
+
allowSelfCreate: overrides.allowSelfCreate === true,
|
|
45
|
+
slugPolicy: WORKSPACE_SLUG_POLICY_USER_SELECTED
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveTenancyProfile(appConfig = {}) {
|
|
50
|
+
const mode = normalizeTenancyMode(appConfig?.tenancyMode);
|
|
51
|
+
const workspacePolicyOverrides = resolveWorkspacePolicyOverrides(appConfig);
|
|
52
|
+
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
mode,
|
|
55
|
+
workspace: resolveWorkspacePolicy(mode, workspacePolicyOverrides)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isWorkspaceTenancyMode(value = "") {
|
|
60
|
+
return normalizeTenancyMode(value) === TENANCY_MODE_WORKSPACE;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
TENANCY_MODE_NONE,
|
|
65
|
+
TENANCY_MODE_PERSONAL,
|
|
66
|
+
TENANCY_MODE_WORKSPACE,
|
|
67
|
+
normalizeTenancyMode,
|
|
68
|
+
WORKSPACE_SLUG_POLICY_NONE,
|
|
69
|
+
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
|
|
70
|
+
WORKSPACE_SLUG_POLICY_USER_SELECTED,
|
|
71
|
+
resolveTenancyProfile,
|
|
72
|
+
isWorkspaceTenancyMode
|
|
73
|
+
};
|