@jskit-ai/workspaces-core 0.1.138-oldline.1 → 0.1.138
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.json +154 -10
- package/patterns/workspace-server/PATTERN.md +60 -0
- package/src/server/WorkspacesFeature.js +226 -0
- package/src/server/WorkspacesIntegrationsProvider.js +74 -0
- package/src/server/common/contributors/workspaceActionContextContributor.js +5 -9
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +16 -2
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +22 -4
- package/src/server/common/support/realtimeServiceEvents.js +102 -61
- package/src/server/support/workspaceActionSurfaces.js +1 -13
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +5 -11
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +24 -14
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +5 -12
- package/src/server/workspaceMembers/workspaceMembersActions.js +63 -23
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +4 -6
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +31 -12
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +5 -8
- package/src/server/workspaceSettings/workspaceSettingsActions.js +26 -8
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +4 -4
- package/test/exportsContract.test.js +2 -1
- package/test/featureRuntime.test.js +116 -0
- package/test/packageMetadata.test.js +59 -0
- package/test/usersRouteResources.test.js +1 -1
- package/test/workspaceActionContextContributor.test.js +7 -12
- package/test/workspaceAuthPolicyContextResolver.test.js +30 -0
- package/test/workspaceRouteVisibilityResolver.test.js +31 -0
- package/test/workspaceService.test.js +2 -2
- package/test/workspaceSettingsActions.test.js +103 -17
- package/test/workspacesRouteRequestInputValidator.test.js +14 -65
- package/package.descriptor.mjs +0 -352
- package/src/server/WorkspacesCoreServiceProvider.js +0 -64
- package/src/server/registerWorkspaceBootstrap.js +0 -27
- package/src/server/registerWorkspaceCore.js +0 -103
- package/src/server/registerWorkspaceRepositories.js +0 -30
- package/src/server/support/workspaceServerScopeSupport.js +0 -18
- package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
- package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -137
- package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -130
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -66
- package/test/packageDescriptor.test.js +0 -91
- package/test/registerServiceRealtimeEvents.test.js +0 -116
- package/test/registerWorkspaceBootstrap.test.js +0 -86
- package/test/registerWorkspaceDirectory.test.js +0 -31
- package/test/registerWorkspaceMembers.test.js +0 -140
- package/test/registerWorkspaceSettings.test.js +0 -40
- package/test/workspaceServerScopeSupport.test.js +0 -131
- /package/{templates/migrations → migrations}/workspaces_core_initial.cjs +0 -0
- /package/{templates/migrations → migrations}/workspaces_core_workspace_settings_single_name_source.cjs +0 -0
- /package/{templates/migrations → migrations}/workspaces_core_workspaces_drop_color.cjs +0 -0
- /package/{templates → patterns/workspace-server/example}/config/roles.js +0 -0
- /package/{templates → patterns/workspace-server/example}/packages/main/src/server/email/workspaceInviteEmail.js +0 -0
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { workspaceDirectoryActionSpecifications } from "../src/server/workspaceDirectory/workspaceDirectoryActions.js";
|
|
4
|
+
import { workspacePendingInvitationsActionSpecifications } from "../src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js";
|
|
5
|
+
import { workspaceMembersActionSpecifications } from "../src/server/workspaceMembers/workspaceMembersActions.js";
|
|
6
|
+
import { workspaceSettingsActionSpecifications } from "../src/server/workspaceSettings/workspaceSettingsActions.js";
|
|
7
7
|
|
|
8
|
-
test("workspace settings
|
|
8
|
+
test("workspace settings action specifications stay explicit", () => {
|
|
9
9
|
assert.deepEqual(
|
|
10
|
-
|
|
10
|
+
workspaceSettingsActionSpecifications.map((action) => action.id),
|
|
11
11
|
["workspace.settings.read", "workspace.settings.update"]
|
|
12
12
|
);
|
|
13
|
-
assert.
|
|
14
|
-
assert.
|
|
15
|
-
assert.deepEqual(
|
|
16
|
-
assert.equal(
|
|
13
|
+
assert.deepEqual(workspaceSettingsActionSpecifications[0].surfaces, ["*"]);
|
|
14
|
+
assert.deepEqual(workspaceSettingsActionSpecifications[1].surfaces, ["*"]);
|
|
15
|
+
assert.deepEqual(workspaceSettingsActionSpecifications[1].channels, ["api", "assistant_tool", "automation", "internal"]);
|
|
16
|
+
assert.equal(workspaceSettingsActionSpecifications[1].extensions?.assistant?.description, "Update workspace settings.");
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
test("workspace actions array
|
|
19
|
+
test("workspace actions array excludes workspace settings actions", () => {
|
|
20
20
|
const otherWorkspaceActionIds = [
|
|
21
|
-
...
|
|
22
|
-
...
|
|
23
|
-
...
|
|
21
|
+
...workspaceDirectoryActionSpecifications,
|
|
22
|
+
...workspacePendingInvitationsActionSpecifications,
|
|
23
|
+
...workspaceMembersActionSpecifications
|
|
24
24
|
].map((action) => action.id);
|
|
25
25
|
|
|
26
26
|
assert.equal(
|
|
@@ -34,17 +34,103 @@ test("workspace actions array no longer owns workspace settings actions", () =>
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
test("workspace directory actions stay thin and defer output validation to routes", () => {
|
|
37
|
-
const listAction =
|
|
37
|
+
const listAction = workspaceDirectoryActionSpecifications.find((action) => action.id === "workspace.workspaces.list");
|
|
38
38
|
assert.ok(listAction);
|
|
39
39
|
assert.equal(listAction.output, null);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test("workspace directory read/update actions stay thin and defer output validation to routes", () => {
|
|
43
|
-
const readAction =
|
|
44
|
-
const updateAction =
|
|
43
|
+
const readAction = workspaceDirectoryActionSpecifications.find((action) => action.id === "workspace.workspaces.read");
|
|
44
|
+
const updateAction = workspaceDirectoryActionSpecifications.find((action) => action.id === "workspace.workspaces.update");
|
|
45
45
|
|
|
46
46
|
assert.ok(readAction);
|
|
47
47
|
assert.ok(updateAction);
|
|
48
48
|
assert.equal(readAction.output, null);
|
|
49
49
|
assert.equal(updateAction.output, null);
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
test("workspace mutation actions declare their domain and bootstrap changes explicitly", async () => {
|
|
53
|
+
const context = {
|
|
54
|
+
actor: { id: 7 },
|
|
55
|
+
visibilityContext: { visibility: "workspace", scopeKind: "workspace", scopeOwnerId: 11 },
|
|
56
|
+
requestMeta: {
|
|
57
|
+
resolvedWorkspaceContext: {
|
|
58
|
+
workspace: { id: 11, slug: "acme" }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const settingsAction = workspaceSettingsActionSpecifications.find(
|
|
63
|
+
(action) => action.id === "workspace.settings.update"
|
|
64
|
+
);
|
|
65
|
+
const events = await Promise.all(settingsAction.events.map((builder) => builder({
|
|
66
|
+
input: { workspaceSlug: "acme", invitesEnabled: true },
|
|
67
|
+
result: { value: { invitesEnabled: true } },
|
|
68
|
+
context
|
|
69
|
+
})));
|
|
70
|
+
|
|
71
|
+
assert.deepEqual(events.map((event) => event.realtime.event), [
|
|
72
|
+
"workspace.settings.changed",
|
|
73
|
+
"users.bootstrap.changed"
|
|
74
|
+
]);
|
|
75
|
+
assert.deepEqual(events[0].scope, { kind: "workspace", id: "11" });
|
|
76
|
+
assert.equal(events[0].entityId, "11");
|
|
77
|
+
assert.deepEqual(events[0].realtime.payload, { workspaceSlug: "acme" });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("workspace invite actions carry recipient lookup as delivery policy, not service metadata", async () => {
|
|
81
|
+
const action = workspaceMembersActionSpecifications.find((entry) => entry.id === "workspace.invite.create");
|
|
82
|
+
const context = {
|
|
83
|
+
actor: { id: 7 },
|
|
84
|
+
visibilityContext: { visibility: "workspace", scopeKind: "workspace", scopeOwnerId: 11 },
|
|
85
|
+
requestMeta: { resolvedWorkspaceContext: { workspace: { id: 11, slug: "acme" } } }
|
|
86
|
+
};
|
|
87
|
+
const events = await Promise.all(action.events.map((builder) => builder({
|
|
88
|
+
input: { workspaceSlug: "acme", email: "reader@example.test", roleSid: "member" },
|
|
89
|
+
result: { value: { createdInviteId: 91 } },
|
|
90
|
+
context
|
|
91
|
+
})));
|
|
92
|
+
assert.equal(events[0].operation, "created");
|
|
93
|
+
assert.equal(events[1].entityId, "91");
|
|
94
|
+
assert.equal(typeof events[1].realtime.audience.userQuery, "function");
|
|
95
|
+
const rows = await events[1].realtime.audience.userQuery({
|
|
96
|
+
event: events[1],
|
|
97
|
+
knex() {
|
|
98
|
+
return {
|
|
99
|
+
join() { return this; },
|
|
100
|
+
where(field, value) {
|
|
101
|
+
assert.equal(field, "wi.id");
|
|
102
|
+
assert.equal(value, "91");
|
|
103
|
+
return this;
|
|
104
|
+
},
|
|
105
|
+
async first() { return { user_id: 55 }; }
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
assert.deepEqual(rows, [{ userId: "55" }]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("accepting an invitation declares directory/member refresh while refusal does not", async () => {
|
|
113
|
+
const action = workspacePendingInvitationsActionSpecifications.find(
|
|
114
|
+
(entry) => entry.id === "workspace.invite.redeem"
|
|
115
|
+
);
|
|
116
|
+
async function emittedNames(decision) {
|
|
117
|
+
const built = await Promise.all(action.events.map((builder) => builder({
|
|
118
|
+
input: { decision, token: "opaque" },
|
|
119
|
+
result: { value: { workspaceId: 11 } },
|
|
120
|
+
context: { actor: { id: 7 } }
|
|
121
|
+
})));
|
|
122
|
+
return built.filter(Boolean).map((event) => event.realtime.event);
|
|
123
|
+
}
|
|
124
|
+
assert.deepEqual(await emittedNames("accept"), [
|
|
125
|
+
"workspace.invitations.pending.changed",
|
|
126
|
+
"users.bootstrap.changed",
|
|
127
|
+
"workspaces.changed",
|
|
128
|
+
"workspace.members.changed",
|
|
129
|
+
"workspace.invites.changed"
|
|
130
|
+
]);
|
|
131
|
+
assert.deepEqual(await emittedNames("refuse"), [
|
|
132
|
+
"workspace.invitations.pending.changed",
|
|
133
|
+
"users.bootstrap.changed",
|
|
134
|
+
"workspace.invites.changed"
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import { UsersCoreServiceProvider } from "../../users-core/src/server/UsersCoreServiceProvider.js";
|
|
4
|
-
import { INTERNAL_JSON_REST_API } from "../../json-rest-api-core/src/server/jsonRestApiHost.js";
|
|
5
3
|
import { resolveTenancyProfile } from "../src/shared/tenancyProfile.js";
|
|
6
|
-
import {
|
|
4
|
+
import { registerWorkspaceDirectoryRoutes } from "../src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js";
|
|
5
|
+
import { registerWorkspaceMembersRoutes } from "../src/server/workspaceMembers/bootWorkspaceMembers.js";
|
|
6
|
+
import { registerWorkspacePendingInvitationsRoutes } from "../src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js";
|
|
7
|
+
import { registerWorkspaceSettingsRoutes } from "../src/server/workspaceSettings/bootWorkspaceSettings.js";
|
|
7
8
|
|
|
8
9
|
function createReplyDouble() {
|
|
9
10
|
return {
|
|
@@ -30,20 +31,11 @@ function findRoute(routes, { method, path }) {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
async function registerRoutes({
|
|
33
|
-
authService = {},
|
|
34
|
-
consoleService = null,
|
|
35
34
|
workspaceEnabled = true,
|
|
36
|
-
workspaceTenancyEnabled = true,
|
|
37
35
|
workspaceInvitationsEnabled = true,
|
|
38
|
-
workspaceSelfCreateEnabled = true
|
|
36
|
+
workspaceSelfCreateEnabled = true,
|
|
37
|
+
config = {}
|
|
39
38
|
} = {}) {
|
|
40
|
-
const internalApi = {
|
|
41
|
-
resources: {},
|
|
42
|
-
async addResource(scopeName) {
|
|
43
|
-
this.resources[scopeName] = {};
|
|
44
|
-
return this.resources[scopeName];
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
39
|
const registeredRoutes = [];
|
|
48
40
|
const router = {
|
|
49
41
|
register(method, path, route, handler) {
|
|
@@ -55,53 +47,14 @@ async function registerRoutes({
|
|
|
55
47
|
});
|
|
56
48
|
}
|
|
57
49
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"users.accountProfile.service",
|
|
65
|
-
{
|
|
66
|
-
async readAvatar() {
|
|
67
|
-
return {
|
|
68
|
-
mimeType: "image/png",
|
|
69
|
-
buffer: Buffer.from([])
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
],
|
|
74
|
-
["actionExecutor", {}],
|
|
75
|
-
["workspaces.enabled", workspaceEnabled],
|
|
76
|
-
["workspaces.tenancy.enabled", workspaceTenancyEnabled],
|
|
77
|
-
["workspaces.invitations.enabled", workspaceInvitationsEnabled],
|
|
78
|
-
["workspaces.self-create.enabled", workspaceSelfCreateEnabled]
|
|
79
|
-
]);
|
|
80
|
-
|
|
81
|
-
if (consoleService) {
|
|
82
|
-
bindings.set("consoleService", consoleService);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const app = {
|
|
86
|
-
has(token) {
|
|
87
|
-
return bindings.has(token);
|
|
88
|
-
},
|
|
89
|
-
instance(token, value) {
|
|
90
|
-
bindings.set(token, value);
|
|
91
|
-
return this;
|
|
92
|
-
},
|
|
93
|
-
make(token) {
|
|
94
|
-
if (!bindings.has(token)) {
|
|
95
|
-
throw new Error(`Missing test binding for token: ${String(token)}`);
|
|
96
|
-
}
|
|
97
|
-
return bindings.get(token);
|
|
50
|
+
if (workspaceEnabled) {
|
|
51
|
+
registerWorkspaceDirectoryRoutes(router, { config, workspaceSelfCreateEnabled });
|
|
52
|
+
registerWorkspaceSettingsRoutes(router, { config });
|
|
53
|
+
registerWorkspaceMembersRoutes(router, { config, workspaceInvitationsEnabled });
|
|
54
|
+
if (workspaceInvitationsEnabled) {
|
|
55
|
+
registerWorkspacePendingInvitationsRoutes(router);
|
|
98
56
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const usersCoreProvider = new UsersCoreServiceProvider();
|
|
102
|
-
const workspacesCoreProvider = new WorkspacesCoreServiceProvider();
|
|
103
|
-
await usersCoreProvider.boot(app);
|
|
104
|
-
await workspacesCoreProvider.boot(app);
|
|
57
|
+
}
|
|
105
58
|
|
|
106
59
|
return registeredRoutes;
|
|
107
60
|
}
|
|
@@ -115,8 +68,8 @@ async function registerRoutesForMode({
|
|
|
115
68
|
tenancyPolicy
|
|
116
69
|
});
|
|
117
70
|
return registerRoutes({
|
|
71
|
+
config: { tenancyMode, tenancyPolicy },
|
|
118
72
|
workspaceEnabled: tenancyProfile.workspace.enabled === true,
|
|
119
|
-
workspaceTenancyEnabled: tenancyProfile.mode === "workspaces",
|
|
120
73
|
workspaceInvitationsEnabled:
|
|
121
74
|
tenancyProfile.workspace.enabled === true && tenancyProfile.mode !== "none",
|
|
122
75
|
workspaceSelfCreateEnabled: tenancyProfile.workspace.allowSelfCreate === true
|
|
@@ -220,7 +173,6 @@ test("workspace core/settings routes mount one canonical workspace endpoint", as
|
|
|
220
173
|
test("workspaces-core boot skips workspace routes when workspace policy is disabled", async () => {
|
|
221
174
|
const routes = await registerRoutes({
|
|
222
175
|
workspaceEnabled: false,
|
|
223
|
-
workspaceTenancyEnabled: false,
|
|
224
176
|
workspaceInvitationsEnabled: false,
|
|
225
177
|
workspaceSelfCreateEnabled: false
|
|
226
178
|
});
|
|
@@ -230,13 +182,11 @@ test("workspaces-core boot skips workspace routes when workspace policy is disab
|
|
|
230
182
|
assert.equal(findRoute(routes, { method: "GET", path: "/api/w/:workspaceSlug" }), null);
|
|
231
183
|
assert.equal(findRoute(routes, { method: "PATCH", path: "/api/w/:workspaceSlug" }), null);
|
|
232
184
|
assert.equal(findRoute(routes, { method: "GET", path: "/api/w/:workspaceSlug/settings" }), null);
|
|
233
|
-
assert.equal(findRoute(routes, { method: "GET", path: "/api/settings" })?.path, "/api/settings");
|
|
234
185
|
});
|
|
235
186
|
|
|
236
187
|
test("workspaces-core boot skips workspace create route when self-create policy is disabled", async () => {
|
|
237
188
|
const routes = await registerRoutes({
|
|
238
189
|
workspaceEnabled: true,
|
|
239
|
-
workspaceTenancyEnabled: true,
|
|
240
190
|
workspaceInvitationsEnabled: true,
|
|
241
191
|
workspaceSelfCreateEnabled: false
|
|
242
192
|
});
|
|
@@ -327,7 +277,6 @@ test("workspaces-core route registration follows tenancy mode matrix", async ()
|
|
|
327
277
|
test("workspaces-core boot skips invitation redeem/list routes when workspace invitations are disabled", async () => {
|
|
328
278
|
const routes = await registerRoutes({
|
|
329
279
|
workspaceEnabled: true,
|
|
330
|
-
workspaceTenancyEnabled: true,
|
|
331
280
|
workspaceInvitationsEnabled: false,
|
|
332
281
|
workspaceSelfCreateEnabled: false
|
|
333
282
|
});
|
package/package.descriptor.mjs
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
export default Object.freeze({
|
|
2
|
-
packageVersion: 1,
|
|
3
|
-
packageId: "@jskit-ai/workspaces-core",
|
|
4
|
-
version: "0.1.138-oldline.1",
|
|
5
|
-
kind: "runtime",
|
|
6
|
-
description: "Workspace tenancy runtime plus HTTP routes, role catalog, and workspace config scaffolding.",
|
|
7
|
-
dependsOn: [
|
|
8
|
-
"@jskit-ai/json-rest-api-core",
|
|
9
|
-
"@jskit-ai/resource-core",
|
|
10
|
-
"@jskit-ai/resource-crud-core",
|
|
11
|
-
"@jskit-ai/users-core"
|
|
12
|
-
],
|
|
13
|
-
capabilities: {
|
|
14
|
-
provides: [
|
|
15
|
-
"workspaces.core",
|
|
16
|
-
"workspaces.server-routes"
|
|
17
|
-
],
|
|
18
|
-
requires: [
|
|
19
|
-
"users.core",
|
|
20
|
-
"json-rest-api.core"
|
|
21
|
-
]
|
|
22
|
-
},
|
|
23
|
-
runtime: {
|
|
24
|
-
server: {
|
|
25
|
-
providers: [
|
|
26
|
-
{
|
|
27
|
-
entrypoint: "src/server/WorkspacesCoreServiceProvider.js",
|
|
28
|
-
export: "WorkspacesCoreServiceProvider"
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
},
|
|
32
|
-
client: {
|
|
33
|
-
providers: []
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
metadata: {
|
|
37
|
-
jskit: {
|
|
38
|
-
tableOwnership: {
|
|
39
|
-
tables: [
|
|
40
|
-
{
|
|
41
|
-
tableName: "workspaces",
|
|
42
|
-
provenance: "runtime-package",
|
|
43
|
-
ownerKind: "package-runtime"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
tableName: "workspace_memberships",
|
|
47
|
-
provenance: "runtime-package",
|
|
48
|
-
ownerKind: "package-runtime"
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
tableName: "workspace_settings",
|
|
52
|
-
provenance: "runtime-package",
|
|
53
|
-
ownerKind: "package-runtime"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
tableName: "workspace_invites",
|
|
57
|
-
provenance: "runtime-package",
|
|
58
|
-
ownerKind: "package-runtime"
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
apiSummary: {
|
|
64
|
-
surfaces: [
|
|
65
|
-
{
|
|
66
|
-
subpath: "./server",
|
|
67
|
-
summary: "Exports the workspace runtime provider and workspace route registration surface."
|
|
68
|
-
}
|
|
69
|
-
],
|
|
70
|
-
containerTokens: {
|
|
71
|
-
server: [
|
|
72
|
-
"workspaces.server.scope-support"
|
|
73
|
-
],
|
|
74
|
-
client: []
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
server: {
|
|
78
|
-
routes: [
|
|
79
|
-
{
|
|
80
|
-
method: "POST",
|
|
81
|
-
path: "/api/workspaces",
|
|
82
|
-
summary: "Create a workspace for the authenticated user."
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
method: "GET",
|
|
86
|
-
path: "/api/workspaces",
|
|
87
|
-
summary: "List workspaces visible to authenticated user."
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
method: "GET",
|
|
91
|
-
path: "/api/workspace/invitations/resolve",
|
|
92
|
-
summary: "Resolve safe public workspace invitation metadata."
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
method: "GET",
|
|
96
|
-
path: "/api/workspace/invitations/pending",
|
|
97
|
-
summary: "List pending workspace invitations for authenticated user."
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
method: "POST",
|
|
101
|
-
path: "/api/workspace/invitations/redeem",
|
|
102
|
-
summary: "Accept or refuse a workspace invitation using an invite token."
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
method: "GET",
|
|
106
|
-
path: "/api/w/:workspaceSlug/settings",
|
|
107
|
-
summary: "Get workspace settings and role catalog by workspace slug."
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
method: "PATCH",
|
|
111
|
-
path: "/api/w/:workspaceSlug/settings",
|
|
112
|
-
summary: "Update workspace settings by workspace slug."
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
method: "GET",
|
|
116
|
-
path: "/api/w/:workspaceSlug/roles",
|
|
117
|
-
summary: "Get role catalog by workspace slug."
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
method: "GET",
|
|
121
|
-
path: "/api/w/:workspaceSlug/members",
|
|
122
|
-
summary: "List members by workspace slug."
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
method: "PATCH",
|
|
126
|
-
path: "/api/w/:workspaceSlug/members/:memberUserId/role",
|
|
127
|
-
summary: "Update workspace member role by workspace slug."
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
method: "GET",
|
|
131
|
-
path: "/api/w/:workspaceSlug/invites",
|
|
132
|
-
summary: "List workspace invites by workspace slug."
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
method: "POST",
|
|
136
|
-
path: "/api/w/:workspaceSlug/invites",
|
|
137
|
-
summary: "Create workspace invite by workspace slug."
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
method: "DELETE",
|
|
141
|
-
path: "/api/w/:workspaceSlug/invites/:inviteId",
|
|
142
|
-
summary: "Revoke workspace invite by workspace slug."
|
|
143
|
-
}
|
|
144
|
-
]
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
mutations: {
|
|
148
|
-
dependencies: {
|
|
149
|
-
runtime: {
|
|
150
|
-
"@jskit-ai/json-rest-api-core": "0.1.92",
|
|
151
|
-
"@jskit-ai/resource-core": "0.1.90",
|
|
152
|
-
"@jskit-ai/resource-crud-core": "0.1.90",
|
|
153
|
-
"@jskit-ai/users-core": "0.1.161"
|
|
154
|
-
},
|
|
155
|
-
dev: {}
|
|
156
|
-
},
|
|
157
|
-
packageJson: {
|
|
158
|
-
scripts: {
|
|
159
|
-
"server:app": "SERVER_SURFACE=app node ./bin/server.js",
|
|
160
|
-
"server:admin": "SERVER_SURFACE=admin node ./bin/server.js"
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
procfile: {},
|
|
164
|
-
files: [
|
|
165
|
-
{
|
|
166
|
-
op: "install-migration",
|
|
167
|
-
from: "templates/migrations/workspaces_core_initial.cjs",
|
|
168
|
-
toDir: "migrations",
|
|
169
|
-
extension: ".cjs",
|
|
170
|
-
reason: "Install workspace tenancy schema migration.",
|
|
171
|
-
category: "migration",
|
|
172
|
-
id: "workspaces-core-initial-schema"
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
op: "install-migration",
|
|
176
|
-
from: "templates/migrations/workspaces_core_workspace_settings_single_name_source.cjs",
|
|
177
|
-
toDir: "migrations",
|
|
178
|
-
extension: ".cjs",
|
|
179
|
-
reason: "Remove workspace_settings name/avatar fields so workspace identity data comes from workspaces only.",
|
|
180
|
-
category: "migration",
|
|
181
|
-
id: "users-core-workspace-settings-single-name-source"
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
op: "install-migration",
|
|
185
|
-
from: "templates/migrations/workspaces_core_workspaces_drop_color.cjs",
|
|
186
|
-
toDir: "migrations",
|
|
187
|
-
extension: ".cjs",
|
|
188
|
-
reason: "Drop workspaces.color now that workspace theme colors live in workspace_settings.",
|
|
189
|
-
category: "migration",
|
|
190
|
-
id: "users-core-workspaces-drop-color"
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
from: "templates/config/roles.js",
|
|
194
|
-
to: "config/roles.js",
|
|
195
|
-
ownership: "app",
|
|
196
|
-
preserveOnRemove: true,
|
|
197
|
-
reason: "Install app-owned role catalog in a dedicated config file.",
|
|
198
|
-
category: "workspaces-core",
|
|
199
|
-
id: "users-core-app-owned-role-catalog-config"
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
from: "templates/packages/main/src/server/email/workspaceInviteEmail.js",
|
|
203
|
-
to: "packages/main/src/server/email/workspaceInviteEmail.js",
|
|
204
|
-
ownership: "app",
|
|
205
|
-
preserveOnRemove: true,
|
|
206
|
-
reason: "Install app-owned editable workspace invite email template.",
|
|
207
|
-
category: "workspaces-core",
|
|
208
|
-
id: "workspaces-core-main-workspace-invite-email-template"
|
|
209
|
-
}
|
|
210
|
-
],
|
|
211
|
-
source: [
|
|
212
|
-
{
|
|
213
|
-
op: "ensure-import",
|
|
214
|
-
file: "config/server.js",
|
|
215
|
-
namedImports: ["renderWorkspaceInviteEmail"],
|
|
216
|
-
from: "../packages/main/src/server/email/workspaceInviteEmail.js",
|
|
217
|
-
reason: "Load app-owned workspace invite email renderer from packages/main.",
|
|
218
|
-
category: "workspaces-core",
|
|
219
|
-
id: "workspaces-core-server-config-workspace-invite-email-import"
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
op: "ensure-assignment",
|
|
223
|
-
file: "config/server.js",
|
|
224
|
-
target: "config.workspaceInviteEmailTemplate",
|
|
225
|
-
value: "renderWorkspaceInviteEmail",
|
|
226
|
-
reason: "Bind app-owned workspace invite email renderer into server config.",
|
|
227
|
-
category: "workspaces-core",
|
|
228
|
-
id: "workspaces-core-server-config-workspace-invite-email-template"
|
|
229
|
-
}
|
|
230
|
-
],
|
|
231
|
-
text: [
|
|
232
|
-
{
|
|
233
|
-
op: "append-text",
|
|
234
|
-
file: "config/public.js",
|
|
235
|
-
position: "top",
|
|
236
|
-
skipIfContains: "import { roleCatalog } from \"./roles.js\";",
|
|
237
|
-
value: "import { roleCatalog } from \"./roles.js\";\n",
|
|
238
|
-
reason: "Load app-owned role catalog from dedicated config file.",
|
|
239
|
-
category: "workspaces-core",
|
|
240
|
-
id: "users-core-role-catalog-public-import"
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
op: "append-text",
|
|
244
|
-
file: "config/public.js",
|
|
245
|
-
position: "top",
|
|
246
|
-
skipIfContains: "import { surfaceAccessPolicies } from \"./surfaceAccessPolicies.js\";",
|
|
247
|
-
value: "import { surfaceAccessPolicies } from \"./surfaceAccessPolicies.js\";\n",
|
|
248
|
-
reason: "Load app-owned surface access policy catalog from dedicated config file.",
|
|
249
|
-
category: "workspaces-core",
|
|
250
|
-
id: "users-core-surface-access-policies-public-import"
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
op: "append-text",
|
|
254
|
-
file: "config/surfaceAccessPolicies.js",
|
|
255
|
-
position: "top",
|
|
256
|
-
skipIfContains: "export const surfaceAccessPolicies = {};",
|
|
257
|
-
value: "export const surfaceAccessPolicies = {};\n\n",
|
|
258
|
-
reason: "Initialize app-owned surface access policy config if missing.",
|
|
259
|
-
category: "workspaces-core",
|
|
260
|
-
id: "users-core-surface-access-policies-config-init"
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
op: "append-text",
|
|
264
|
-
file: "config/surfaceAccessPolicies.js",
|
|
265
|
-
position: "bottom",
|
|
266
|
-
skipIfContains: "surfaceAccessPolicies.workspace_member = {",
|
|
267
|
-
value: "\nsurfaceAccessPolicies.workspace_member = {\n requireAuth: true,\n requireWorkspaceMembership: true\n};\n",
|
|
268
|
-
reason: "Register workspace-member surface access policy for workspace surfaces.",
|
|
269
|
-
category: "workspaces-core",
|
|
270
|
-
id: "users-core-surface-access-policies-workspace-member"
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
op: "append-text",
|
|
274
|
-
file: "config/public.js",
|
|
275
|
-
position: "bottom",
|
|
276
|
-
skipIfContains: "config.surfaceDefinitions.app = {",
|
|
277
|
-
value:
|
|
278
|
-
"\nconfig.surfaceDefinitions.app = {\n id: \"app\",\n label: \"App\",\n pagesRoot: \"w/[workspaceSlug]\",\n enabled: true,\n requiresAuth: true,\n requiresWorkspace: true,\n accessPolicyId: \"workspace_member\",\n origin: \"\"\n};\n\nconfig.surfaceDefinitions.admin = {\n id: \"admin\",\n label: \"Admin\",\n pagesRoot: \"w/[workspaceSlug]/admin\",\n enabled: true,\n requiresAuth: true,\n requiresWorkspace: true,\n accessPolicyId: \"workspace_member\",\n origin: \"\"\n};\n",
|
|
279
|
-
reason: "Append workspace surface topology when tenancy enables workspace routing.",
|
|
280
|
-
category: "workspaces-core",
|
|
281
|
-
id: "users-core-surface-config-workspace",
|
|
282
|
-
when: {
|
|
283
|
-
config: "tenancyMode",
|
|
284
|
-
in: ["personal", "workspaces"]
|
|
285
|
-
}
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
op: "append-text",
|
|
289
|
-
file: "config/public.js",
|
|
290
|
-
position: "bottom",
|
|
291
|
-
skipIfContains: "config.workspaceSwitching =",
|
|
292
|
-
value:
|
|
293
|
-
"\nconfig.workspaceSwitching = true;\nconfig.workspaceInvitations = {\n enabled: true,\n allowInPersonalMode: true\n};\n",
|
|
294
|
-
reason: "Append default workspace feature toggles into app-owned config.",
|
|
295
|
-
category: "workspaces-core",
|
|
296
|
-
id: "users-core-public-config"
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
op: "append-text",
|
|
300
|
-
file: "config/public.js",
|
|
301
|
-
position: "bottom",
|
|
302
|
-
skipIfContains: "config.roleCatalog = roleCatalog;",
|
|
303
|
-
value: "\nconfig.roleCatalog = roleCatalog;\n",
|
|
304
|
-
reason: "Bind app-owned role catalog onto public config.",
|
|
305
|
-
category: "workspaces-core",
|
|
306
|
-
id: "users-core-role-catalog-public-config"
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
op: "append-text",
|
|
310
|
-
file: "config/public.js",
|
|
311
|
-
position: "bottom",
|
|
312
|
-
skipIfContains: "config.surfaceAccessPolicies = surfaceAccessPolicies;",
|
|
313
|
-
value: "\nconfig.surfaceAccessPolicies = surfaceAccessPolicies;\n",
|
|
314
|
-
reason: "Bind app-owned surface access policies onto public config.",
|
|
315
|
-
category: "workspaces-core",
|
|
316
|
-
id: "users-core-surface-access-policies-public-config"
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
op: "append-text",
|
|
320
|
-
file: "config/server.js",
|
|
321
|
-
position: "bottom",
|
|
322
|
-
skipIfContains: "config.workspaceColor =",
|
|
323
|
-
value: "\nconfig.workspaceColor = \"#1867C0\";\n",
|
|
324
|
-
reason: "Append default workspace server settings into app-owned config.",
|
|
325
|
-
category: "workspaces-core",
|
|
326
|
-
id: "users-core-server-config"
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
op: "append-text",
|
|
330
|
-
file: "config/server.js",
|
|
331
|
-
position: "bottom",
|
|
332
|
-
skipIfContains: "config.workspaceSettings =",
|
|
333
|
-
value:
|
|
334
|
-
"\nconfig.workspaceSettings = {\n defaults: {\n invitesEnabled: true\n }\n};\n",
|
|
335
|
-
reason: "Append app-owned workspace settings defaults into the server config.",
|
|
336
|
-
category: "workspaces-core",
|
|
337
|
-
id: "users-core-workspace-settings-server-config"
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
op: "append-text",
|
|
341
|
-
file: "config/server.js",
|
|
342
|
-
position: "bottom",
|
|
343
|
-
skipIfContains: "config.workspaceMembers =",
|
|
344
|
-
value:
|
|
345
|
-
"\nconfig.workspaceMembers = {\n defaults: {\n inviteExpiresInMs: 604800000\n }\n};\n",
|
|
346
|
-
reason: "Append app-owned workspace member invite policy defaults into the server config.",
|
|
347
|
-
category: "workspaces-core",
|
|
348
|
-
id: "users-core-workspace-members-server-config"
|
|
349
|
-
}
|
|
350
|
-
]
|
|
351
|
-
}
|
|
352
|
-
});
|