@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.
- package/package.descriptor.mjs +7 -7
- package/package.json +7 -17
- package/src/server/common/services/authProfileSyncService.js +28 -7
- package/src/server/common/support/realtimeServiceEvents.js +1 -59
- package/src/server/profileSyncLifecycleContributorRegistry.js +56 -0
- package/src/server/registerUsersBootstrap.js +0 -1
- package/src/server/registerUsersCore.js +2 -14
- package/src/server/usersBootstrapContributor.js +2 -64
- package/src/shared/index.js +2 -99
- package/src/shared/settings.js +1 -119
- package/test/authProfileSyncService.test.js +19 -10
- package/test/registerServiceRealtimeEvents.test.js +0 -86
- package/test/registerUsersCore.test.js +6 -15
- package/test/repositoryContracts.test.js +1 -9
- package/test/resourcesCanonical.test.js +0 -16
- package/test/settingsFieldRegistriesSingleton.test.js +0 -5
- package/test/usersBootstrapContributor.test.js +2 -26
- package/test/usersRouteResources.test.js +0 -16
- package/src/server/UsersWorkspacesServiceProvider.js +0 -44
- package/src/server/common/contributors/workspaceActionContextContributor.js +0 -88
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +0 -34
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +0 -78
- package/src/server/common/formatters/workspaceFormatter.js +0 -53
- package/src/server/common/repositories/workspaceInvitesRepository.js +0 -208
- package/src/server/common/repositories/workspaceMembershipsRepository.js +0 -190
- package/src/server/common/repositories/workspacesRepository.js +0 -202
- package/src/server/common/services/workspaceContextService.js +0 -281
- package/src/server/common/support/workspaceRoutePaths.js +0 -17
- package/src/server/common/validators/routeParamsValidator.js +0 -62
- package/src/server/registerWorkspaceBootstrap.js +0 -27
- package/src/server/registerWorkspaceCore.js +0 -73
- package/src/server/registerWorkspaceRepositories.js +0 -26
- package/src/server/support/resolveWorkspace.js +0 -16
- package/src/server/support/workspaceActionSurfaces.js +0 -118
- package/src/server/support/workspaceInvitationsPolicy.js +0 -45
- package/src/server/support/workspaceRouteInput.js +0 -22
- package/src/server/workspaceBootstrapContributor.js +0 -212
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +0 -133
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +0 -133
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +0 -236
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -108
- package/src/server/workspaceMembers/workspaceMembersActions.js +0 -186
- package/src/server/workspaceMembers/workspaceMembersService.js +0 -222
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +0 -62
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -119
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +0 -74
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +0 -138
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +0 -76
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -62
- package/src/server/workspaceSettings/workspaceSettingsActions.js +0 -72
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +0 -154
- package/src/server/workspaceSettings/workspaceSettingsService.js +0 -66
- package/src/shared/resources/workspaceMembersResource.js +0 -354
- package/src/shared/resources/workspacePendingInvitationsResource.js +0 -82
- package/src/shared/resources/workspaceResource.js +0 -176
- package/src/shared/resources/workspaceSettingsFields.js +0 -59
- package/src/shared/resources/workspaceSettingsResource.js +0 -169
- package/src/shared/roles.js +0 -161
- package/src/shared/support/usersApiPaths.js +0 -43
- package/src/shared/support/usersVisibility.js +0 -42
- package/src/shared/support/workspacePathModel.js +0 -145
- package/src/shared/tenancyMode.js +0 -35
- package/src/shared/tenancyProfile.js +0 -73
- package/test/registerWorkspaceDirectory.test.js +0 -31
- package/test/registerWorkspaceSettings.test.js +0 -40
- package/test/roles.test.js +0 -159
- package/test/tenancyProfile.test.js +0 -67
- package/test/usersApiPaths.test.js +0 -49
- package/test/usersRouteValidators.test.js +0 -49
- package/test/usersVisibility.test.js +0 -27
- package/test/workspaceActionContextContributor.test.js +0 -344
- package/test/workspaceActionSurfaces.test.js +0 -85
- package/test/workspaceAuthPolicyContextResolver.test.js +0 -119
- package/test/workspaceBootstrapContributor.test.js +0 -154
- package/test/workspaceInvitationsPolicy.test.js +0 -71
- package/test/workspaceInvitesRepository.test.js +0 -111
- package/test/workspaceMembersService.test.js +0 -398
- package/test/workspacePathModel.test.js +0 -93
- package/test/workspacePendingInvitationsResource.test.js +0 -38
- package/test/workspacePendingInvitationsService.test.js +0 -151
- package/test/workspaceRouteVisibilityResolver.test.js +0 -83
- package/test/workspaceService.test.js +0 -546
- package/test/workspaceSettingsActions.test.js +0 -52
- package/test/workspaceSettingsRepository.test.js +0 -202
- package/test/workspaceSettingsResource.test.js +0 -169
- package/test/workspaceSettingsService.test.js +0 -140
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
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 checkRouteVisibility(value, { context = "checkRouteVisibility" } = {}) {
|
|
17
|
-
const normalized = normalizeText(value).toLowerCase();
|
|
18
|
-
if (USERS_ROUTE_VISIBILITY_LEVEL_SET.has(normalized)) {
|
|
19
|
-
return normalized;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
throw new TypeError(
|
|
23
|
-
`${context} must be one of: ${USERS_ROUTE_VISIBILITY_LEVELS.join(", ")}.`
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function isWorkspaceVisibility(visibility = "") {
|
|
28
|
-
const normalized = checkRouteVisibility(visibility, {
|
|
29
|
-
context: "isWorkspaceVisibility visibility"
|
|
30
|
-
});
|
|
31
|
-
return normalized === USERS_ROUTE_VISIBILITY_WORKSPACE || normalized === USERS_ROUTE_VISIBILITY_WORKSPACE_USER;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
USERS_ROUTE_VISIBILITY_PUBLIC,
|
|
36
|
-
USERS_ROUTE_VISIBILITY_USER,
|
|
37
|
-
USERS_ROUTE_VISIBILITY_WORKSPACE,
|
|
38
|
-
USERS_ROUTE_VISIBILITY_WORKSPACE_USER,
|
|
39
|
-
USERS_ROUTE_VISIBILITY_LEVELS,
|
|
40
|
-
checkRouteVisibility,
|
|
41
|
-
isWorkspaceVisibility
|
|
42
|
-
};
|
|
@@ -1,145 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const TENANCY_MODE_NONE = "none";
|
|
2
|
-
const TENANCY_MODE_PERSONAL = "personal";
|
|
3
|
-
const TENANCY_MODE_WORKSPACES = "workspaces";
|
|
4
|
-
|
|
5
|
-
const TENANCY_MODES = Object.freeze([
|
|
6
|
-
TENANCY_MODE_NONE,
|
|
7
|
-
TENANCY_MODE_PERSONAL,
|
|
8
|
-
TENANCY_MODE_WORKSPACES
|
|
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_WORKSPACES,
|
|
32
|
-
TENANCY_MODES,
|
|
33
|
-
normalizeTenancyMode,
|
|
34
|
-
isTenancyMode
|
|
35
|
-
};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TENANCY_MODE_NONE,
|
|
3
|
-
TENANCY_MODE_PERSONAL,
|
|
4
|
-
TENANCY_MODE_WORKSPACES,
|
|
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 isWorkspacesTenancyMode(value = "") {
|
|
60
|
-
return normalizeTenancyMode(value) === TENANCY_MODE_WORKSPACES;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export {
|
|
64
|
-
TENANCY_MODE_NONE,
|
|
65
|
-
TENANCY_MODE_PERSONAL,
|
|
66
|
-
TENANCY_MODE_WORKSPACES,
|
|
67
|
-
normalizeTenancyMode,
|
|
68
|
-
WORKSPACE_SLUG_POLICY_NONE,
|
|
69
|
-
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
|
|
70
|
-
WORKSPACE_SLUG_POLICY_USER_SELECTED,
|
|
71
|
-
resolveTenancyProfile,
|
|
72
|
-
isWorkspacesTenancyMode
|
|
73
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { registerWorkspaceDirectory } from "../src/server/workspaceDirectory/registerWorkspaceDirectory.js";
|
|
4
|
-
|
|
5
|
-
function createAppDouble() {
|
|
6
|
-
const actionBatches = [];
|
|
7
|
-
|
|
8
|
-
return {
|
|
9
|
-
actionBatches,
|
|
10
|
-
singleton() {},
|
|
11
|
-
actions(entries) {
|
|
12
|
-
actionBatches.push(Array.isArray(entries) ? entries : [entries]);
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function listActionIds(app) {
|
|
18
|
-
return app.actionBatches.flat().map((entry) => String(entry?.id || ""));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
test("registerWorkspaceDirectory registers workspace directory actions without resolving runtime tenancy tokens", () => {
|
|
22
|
-
const app = createAppDouble();
|
|
23
|
-
|
|
24
|
-
registerWorkspaceDirectory(app);
|
|
25
|
-
assert.deepEqual(listActionIds(app), [
|
|
26
|
-
"workspace.workspaces.create",
|
|
27
|
-
"workspace.workspaces.list",
|
|
28
|
-
"workspace.workspaces.read",
|
|
29
|
-
"workspace.workspaces.update"
|
|
30
|
-
]);
|
|
31
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { registerWorkspaceSettings } from "../src/server/workspaceSettings/registerWorkspaceSettings.js";
|
|
4
|
-
|
|
5
|
-
test("registerWorkspaceSettings registers workspace settings service realtime event metadata", () => {
|
|
6
|
-
const singletonBindings = new Map();
|
|
7
|
-
const actionCalls = [];
|
|
8
|
-
const serviceCalls = [];
|
|
9
|
-
|
|
10
|
-
const app = {
|
|
11
|
-
singleton(token, factory) {
|
|
12
|
-
singletonBindings.set(token, factory);
|
|
13
|
-
return this;
|
|
14
|
-
},
|
|
15
|
-
service(token, factory, metadata) {
|
|
16
|
-
serviceCalls.push({
|
|
17
|
-
token,
|
|
18
|
-
factory,
|
|
19
|
-
metadata
|
|
20
|
-
});
|
|
21
|
-
return this;
|
|
22
|
-
},
|
|
23
|
-
actions(definitions) {
|
|
24
|
-
actionCalls.push(definitions);
|
|
25
|
-
return this;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
registerWorkspaceSettings(app);
|
|
30
|
-
|
|
31
|
-
assert.equal(singletonBindings.has("workspaceSettingsRepository"), true);
|
|
32
|
-
assert.equal(serviceCalls.length, 1);
|
|
33
|
-
assert.equal(serviceCalls[0].token, "users.workspace.settings.service");
|
|
34
|
-
assert.equal(typeof serviceCalls[0].factory, "function");
|
|
35
|
-
assert.equal(serviceCalls[0].metadata?.events?.updateWorkspaceSettings?.[0]?.realtime?.event, "workspace.settings.changed");
|
|
36
|
-
assert.equal(serviceCalls[0].metadata?.events?.updateWorkspaceSettings?.[0]?.realtime?.audience, "event_scope");
|
|
37
|
-
assert.equal(serviceCalls[0].metadata?.events?.updateWorkspaceSettings?.[1]?.realtime?.event, "users.bootstrap.changed");
|
|
38
|
-
assert.equal(serviceCalls[0].metadata?.events?.updateWorkspaceSettings?.[1]?.realtime?.audience, "event_scope");
|
|
39
|
-
assert.equal(actionCalls.length, 1);
|
|
40
|
-
});
|
package/test/roles.test.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
createWorkspaceRoleCatalog,
|
|
5
|
-
cloneWorkspaceRoleCatalog,
|
|
6
|
-
resolveRolePermissions,
|
|
7
|
-
hasPermission
|
|
8
|
-
} from "../src/shared/roles.js";
|
|
9
|
-
|
|
10
|
-
test("createWorkspaceRoleCatalog resolves role descriptors only from appConfig.roleCatalog", () => {
|
|
11
|
-
const emptyCatalog = createWorkspaceRoleCatalog();
|
|
12
|
-
assert.deepEqual(emptyCatalog.roles, []);
|
|
13
|
-
assert.deepEqual(emptyCatalog.assignableRoleIds, []);
|
|
14
|
-
assert.equal(emptyCatalog.defaultInviteRole, "");
|
|
15
|
-
assert.equal(emptyCatalog.collaborationEnabled, false);
|
|
16
|
-
|
|
17
|
-
const appConfig = {
|
|
18
|
-
roleCatalog: {
|
|
19
|
-
workspace: {
|
|
20
|
-
defaultInviteRole: "editor"
|
|
21
|
-
},
|
|
22
|
-
roles: {
|
|
23
|
-
owner: {
|
|
24
|
-
assignable: false,
|
|
25
|
-
permissions: ["workspace.settings.update"]
|
|
26
|
-
},
|
|
27
|
-
editor: {
|
|
28
|
-
assignable: true,
|
|
29
|
-
permissions: ["crud.contacts.*"]
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
35
|
-
const editorRole = roleCatalog.roles.find((role) => role.id === "editor");
|
|
36
|
-
|
|
37
|
-
assert.equal(roleCatalog.defaultInviteRole, "editor");
|
|
38
|
-
assert.equal(roleCatalog.assignableRoleIds.includes("editor"), true);
|
|
39
|
-
assert.deepEqual(resolveRolePermissions("owner", appConfig), ["workspace.settings.update"]);
|
|
40
|
-
assert.equal(hasPermission(editorRole?.permissions, "crud.contacts.update"), true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("createWorkspaceRoleCatalog resolves inherited role permissions with parent permissions first", () => {
|
|
44
|
-
const appConfig = {
|
|
45
|
-
roleCatalog: {
|
|
46
|
-
workspace: {
|
|
47
|
-
defaultInviteRole: "member"
|
|
48
|
-
},
|
|
49
|
-
roles: {
|
|
50
|
-
member: {
|
|
51
|
-
assignable: true,
|
|
52
|
-
permissions: [
|
|
53
|
-
"workspace.settings.view",
|
|
54
|
-
"crud.contacts.list"
|
|
55
|
-
]
|
|
56
|
-
},
|
|
57
|
-
admin: {
|
|
58
|
-
assignable: true,
|
|
59
|
-
inherits: "member",
|
|
60
|
-
permissions: [
|
|
61
|
-
"workspace.settings.update",
|
|
62
|
-
"workspace.members.manage",
|
|
63
|
-
"workspace.settings.view"
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const roleCatalog = createWorkspaceRoleCatalog(appConfig);
|
|
71
|
-
const adminRole = roleCatalog.roles.find((role) => role.id === "admin");
|
|
72
|
-
|
|
73
|
-
assert.deepEqual(adminRole, {
|
|
74
|
-
id: "admin",
|
|
75
|
-
assignable: true,
|
|
76
|
-
permissions: [
|
|
77
|
-
"workspace.settings.view",
|
|
78
|
-
"crud.contacts.list",
|
|
79
|
-
"workspace.settings.update",
|
|
80
|
-
"workspace.members.manage"
|
|
81
|
-
]
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("createWorkspaceRoleCatalog rejects unknown inherited roles", () => {
|
|
86
|
-
assert.throws(
|
|
87
|
-
() =>
|
|
88
|
-
createWorkspaceRoleCatalog({
|
|
89
|
-
roleCatalog: {
|
|
90
|
-
roles: {
|
|
91
|
-
admin: {
|
|
92
|
-
assignable: true,
|
|
93
|
-
inherits: "member",
|
|
94
|
-
permissions: []
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}),
|
|
99
|
-
/inherits unknown role "member"/
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("createWorkspaceRoleCatalog rejects circular inherited roles", () => {
|
|
104
|
-
assert.throws(
|
|
105
|
-
() =>
|
|
106
|
-
createWorkspaceRoleCatalog({
|
|
107
|
-
roleCatalog: {
|
|
108
|
-
roles: {
|
|
109
|
-
member: {
|
|
110
|
-
assignable: true,
|
|
111
|
-
inherits: "admin",
|
|
112
|
-
permissions: []
|
|
113
|
-
},
|
|
114
|
-
admin: {
|
|
115
|
-
assignable: true,
|
|
116
|
-
inherits: "member",
|
|
117
|
-
permissions: []
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}),
|
|
122
|
-
/circular inheritance/
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("cloneWorkspaceRoleCatalog normalizes role ids and returns detached arrays", () => {
|
|
127
|
-
const source = {
|
|
128
|
-
collaborationEnabled: true,
|
|
129
|
-
defaultInviteRole: "member",
|
|
130
|
-
roles: [
|
|
131
|
-
{
|
|
132
|
-
id: " MEMBER ",
|
|
133
|
-
assignable: true,
|
|
134
|
-
permissions: ["workspace.members.view"]
|
|
135
|
-
}
|
|
136
|
-
],
|
|
137
|
-
assignableRoleIds: ["member"]
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const cloned = cloneWorkspaceRoleCatalog(source);
|
|
141
|
-
assert.deepEqual(cloned, {
|
|
142
|
-
collaborationEnabled: true,
|
|
143
|
-
defaultInviteRole: "member",
|
|
144
|
-
roles: [
|
|
145
|
-
{
|
|
146
|
-
id: "member",
|
|
147
|
-
assignable: true,
|
|
148
|
-
permissions: ["workspace.members.view"]
|
|
149
|
-
}
|
|
150
|
-
],
|
|
151
|
-
assignableRoleIds: ["member"]
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
cloned.roles[0].permissions.push("workspace.members.manage");
|
|
155
|
-
cloned.assignableRoleIds.push("admin");
|
|
156
|
-
|
|
157
|
-
assert.deepEqual(source.roles[0].permissions, ["workspace.members.view"]);
|
|
158
|
-
assert.deepEqual(source.assignableRoleIds, ["member"]);
|
|
159
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
TENANCY_MODE_NONE,
|
|
5
|
-
TENANCY_MODE_PERSONAL,
|
|
6
|
-
TENANCY_MODE_WORKSPACES,
|
|
7
|
-
WORKSPACE_SLUG_POLICY_NONE,
|
|
8
|
-
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME,
|
|
9
|
-
WORKSPACE_SLUG_POLICY_USER_SELECTED,
|
|
10
|
-
resolveTenancyProfile,
|
|
11
|
-
isWorkspacesTenancyMode
|
|
12
|
-
} from "../src/shared/tenancyProfile.js";
|
|
13
|
-
|
|
14
|
-
test("resolveTenancyProfile returns mode-specific workspace policy matrix", () => {
|
|
15
|
-
const noneProfile = resolveTenancyProfile({ tenancyMode: TENANCY_MODE_NONE });
|
|
16
|
-
assert.deepEqual(noneProfile, {
|
|
17
|
-
mode: TENANCY_MODE_NONE,
|
|
18
|
-
workspace: {
|
|
19
|
-
enabled: false,
|
|
20
|
-
autoProvision: false,
|
|
21
|
-
allowSelfCreate: false,
|
|
22
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_NONE
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const personalProfile = resolveTenancyProfile({ tenancyMode: TENANCY_MODE_PERSONAL });
|
|
27
|
-
assert.deepEqual(personalProfile, {
|
|
28
|
-
mode: TENANCY_MODE_PERSONAL,
|
|
29
|
-
workspace: {
|
|
30
|
-
enabled: true,
|
|
31
|
-
autoProvision: true,
|
|
32
|
-
allowSelfCreate: false,
|
|
33
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const workspaceProfile = resolveTenancyProfile({ tenancyMode: TENANCY_MODE_WORKSPACES });
|
|
38
|
-
assert.deepEqual(workspaceProfile, {
|
|
39
|
-
mode: TENANCY_MODE_WORKSPACES,
|
|
40
|
-
workspace: {
|
|
41
|
-
enabled: true,
|
|
42
|
-
autoProvision: false,
|
|
43
|
-
allowSelfCreate: false,
|
|
44
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_USER_SELECTED
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("isWorkspacesTenancyMode is true only for workspace mode", () => {
|
|
50
|
-
assert.equal(isWorkspacesTenancyMode(TENANCY_MODE_WORKSPACES), true);
|
|
51
|
-
assert.equal(isWorkspacesTenancyMode(TENANCY_MODE_PERSONAL), false);
|
|
52
|
-
assert.equal(isWorkspacesTenancyMode(TENANCY_MODE_NONE), false);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("resolveTenancyProfile allows explicit workspace self-create policy override", () => {
|
|
56
|
-
const workspaceProfile = resolveTenancyProfile({
|
|
57
|
-
tenancyMode: TENANCY_MODE_WORKSPACES,
|
|
58
|
-
tenancyPolicy: {
|
|
59
|
-
workspace: {
|
|
60
|
-
allowSelfCreate: true
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
assert.equal(workspaceProfile.mode, TENANCY_MODE_WORKSPACES);
|
|
66
|
-
assert.equal(workspaceProfile.workspace.allowSelfCreate, true);
|
|
67
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
4
|
-
normalizeSurfaceWorkspaceRequirement,
|
|
5
|
-
resolveApiBasePath
|
|
6
|
-
} from "../src/shared/support/usersApiPaths.js";
|
|
7
|
-
|
|
8
|
-
test("normalizeSurfaceWorkspaceRequirement only accepts explicit true", () => {
|
|
9
|
-
assert.equal(normalizeSurfaceWorkspaceRequirement(true), true);
|
|
10
|
-
assert.equal(normalizeSurfaceWorkspaceRequirement(false), false);
|
|
11
|
-
assert.equal(normalizeSurfaceWorkspaceRequirement("true"), false);
|
|
12
|
-
assert.equal(normalizeSurfaceWorkspaceRequirement(1), false);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test("resolveApiBasePath resolves workspace and non-workspace API base paths", () => {
|
|
16
|
-
assert.equal(
|
|
17
|
-
resolveApiBasePath({
|
|
18
|
-
surfaceRequiresWorkspace: true,
|
|
19
|
-
relativePath: "/customers"
|
|
20
|
-
}),
|
|
21
|
-
"/api/w/:workspaceSlug/customers"
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
assert.equal(
|
|
25
|
-
resolveApiBasePath({
|
|
26
|
-
surfaceRequiresWorkspace: false,
|
|
27
|
-
relativePath: "/customers"
|
|
28
|
-
}),
|
|
29
|
-
"/api/customers"
|
|
30
|
-
);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("resolveApiBasePath preserves query strings and hash fragments", () => {
|
|
34
|
-
assert.equal(
|
|
35
|
-
resolveApiBasePath({
|
|
36
|
-
surfaceRequiresWorkspace: true,
|
|
37
|
-
relativePath: "/customers?search=buddy#top"
|
|
38
|
-
}),
|
|
39
|
-
"/api/w/:workspaceSlug/customers?search=buddy#top"
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
assert.equal(
|
|
43
|
-
resolveApiBasePath({
|
|
44
|
-
surfaceRequiresWorkspace: false,
|
|
45
|
-
relativePath: "/?cursor=2"
|
|
46
|
-
}),
|
|
47
|
-
"/api?cursor=2"
|
|
48
|
-
);
|
|
49
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { Type } from "@fastify/type-provider-typebox";
|
|
4
|
-
import { compileRouteValidator } from "@jskit-ai/kernel/_testable";
|
|
5
|
-
import { routeParamsValidator } from "../src/server/common/validators/routeParamsValidator.js";
|
|
6
|
-
|
|
7
|
-
test("routeParamsValidator exposes a shared route params validator", () => {
|
|
8
|
-
assert.equal(typeof routeParamsValidator.schema, "object");
|
|
9
|
-
assert.equal(typeof routeParamsValidator.normalize, "function");
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("route validator pipeline uses the shared params validator and merges query arrays automatically", () => {
|
|
13
|
-
const paginationQueryValidator = Object.freeze({
|
|
14
|
-
schema: Type.Object(
|
|
15
|
-
{
|
|
16
|
-
cursor: Type.Optional(Type.String({ minLength: 1 })),
|
|
17
|
-
limit: Type.Optional(Type.String({ pattern: "^[0-9]+$" }))
|
|
18
|
-
},
|
|
19
|
-
{ additionalProperties: false }
|
|
20
|
-
)
|
|
21
|
-
});
|
|
22
|
-
const searchQueryValidator = Object.freeze({
|
|
23
|
-
schema: Type.Object(
|
|
24
|
-
{
|
|
25
|
-
search: Type.Optional(Type.String({ minLength: 1 }))
|
|
26
|
-
},
|
|
27
|
-
{ additionalProperties: false }
|
|
28
|
-
)
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const compiled = compileRouteValidator({
|
|
32
|
-
paramsValidator: routeParamsValidator,
|
|
33
|
-
queryValidator: [paginationQueryValidator, searchQueryValidator]
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
assert.equal(compiled.schema.params.type, "object");
|
|
37
|
-
assert.equal(compiled.schema.params.additionalProperties, false);
|
|
38
|
-
assert.equal(typeof compiled.schema.params.properties.workspaceSlug, "object");
|
|
39
|
-
assert.equal(typeof compiled.schema.params.properties.memberUserId, "object");
|
|
40
|
-
assert.equal(typeof compiled.schema.params.properties.inviteId, "object");
|
|
41
|
-
assert.equal(typeof compiled.schema.params.properties.provider, "object");
|
|
42
|
-
assert.equal(compiled.input.params({ workspaceSlug: " ACME " }).workspaceSlug, "acme");
|
|
43
|
-
|
|
44
|
-
assert.equal(compiled.schema.querystring.type, "object");
|
|
45
|
-
assert.equal(compiled.schema.querystring.additionalProperties, false);
|
|
46
|
-
assert.equal(typeof compiled.schema.querystring.properties.cursor, "object");
|
|
47
|
-
assert.equal(typeof compiled.schema.querystring.properties.limit, "object");
|
|
48
|
-
assert.equal(typeof compiled.schema.querystring.properties.search, "object");
|
|
49
|
-
});
|