@jskit-ai/workspaces-core 0.2.4 → 0.2.6
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/locales/en.json +2 -2
- package/package.json +38 -11
- package/patterns/workspace-server/PATTERN.md +42 -17
- package/patterns/workspace-server/example/packages/main/src/server/WorkspaceInvitationsFeature.js +27 -0
- package/patterns/workspace-server/example/packages/main/src/server/email/workspaceInviteEmail.js +11 -10
- package/patterns/workspace-server/example/packages/main/src/server/email/workspaceInviteMailer.js +27 -0
- package/src/server/WorkspacesFeature.js +74 -183
- package/src/server/WorkspacesIntegrationsProvider.js +26 -10
- package/src/server/common/contributors/workspaceActionContextContributor.js +60 -70
- package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +12 -27
- package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +48 -63
- package/src/server/common/formatters/workspaceFormatter.js +12 -10
- package/src/server/common/repositories/workspaceInvitesRepository.js +74 -75
- package/src/server/common/repositories/workspaceMembershipsRepository.js +23 -45
- package/src/server/common/repositories/workspacesRepository.js +17 -25
- package/src/server/common/services/{workspaceContextService.js → workspaceDirectoryService.js} +52 -72
- package/src/server/common/support/realtimeServiceEvents.js +20 -12
- package/src/server/common/support/workspaceRecordId.js +21 -0
- package/src/server/common/validators/routeParamsValidator.js +3 -2
- package/src/server/i18n.js +5 -2
- package/src/server/support/resolveWorkspace.js +10 -7
- package/src/server/support/workspaceActionSurfaces.js +5 -41
- package/src/server/support/workspaceCrud.js +25 -0
- package/src/server/support/workspaceInvitationsPolicy.js +16 -27
- package/src/server/support/workspaceRouteInput.js +19 -2
- package/src/server/{common/support → support}/workspaceRoutePaths.js +8 -2
- package/src/server/types.js +47 -0
- package/src/server/workspaceBootstrapContributor.js +105 -142
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +105 -137
- package/src/server/workspaceDirectory/{bootWorkspaceDirectoryRoutes.js → workspaceDirectoryRoutes.js} +7 -19
- package/src/server/workspaceMembers/defaultWorkspaceInviteEmail.js +15 -29
- package/src/server/workspaceMembers/unconfiguredWorkspaceInviteMailer.js +25 -0
- package/src/server/workspaceMembers/workspaceInviteUrls.js +24 -23
- package/src/server/workspaceMembers/workspaceMembersActions.js +189 -219
- package/src/server/workspaceMembers/{bootWorkspaceMembers.js → workspaceMembersRoutes.js} +14 -26
- package/src/server/workspaceMembers/workspaceMembersService.js +110 -97
- package/src/server/workspacePendingInvitations/noInviteAcceptanceParticipant.js +8 -0
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +79 -112
- package/src/server/workspacePendingInvitations/{bootWorkspacePendingInvitations.js → workspacePendingInvitationsRoutes.js} +11 -8
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +54 -57
- package/src/server/workspaceSettings/workspaceSettingsActions.js +53 -73
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +20 -20
- package/src/server/workspaceSettings/{bootWorkspaceSettings.js → workspaceSettingsRoutes.js} +9 -21
- package/src/server/workspaceSettings/workspaceSettingsService.js +24 -26
- package/src/server/workspacesRuntime.js +185 -0
- package/src/shared/operationMessages.js +6 -15
- package/src/shared/resources/workspaceMembersResource.js +2 -2
- package/src/shared/resources/workspacePendingInvitationsResource.js +2 -2
- package/src/shared/resources/workspaceResource.js +4 -1
- package/src/shared/roles.js +15 -12
- package/src/shared/tenancyProfile.js +2 -2
- package/test/exportsContract.test.js +5 -0
- package/test/featureRuntime.test.js +5 -6
- package/test/packageMetadata.test.js +12 -0
- package/test/repositories.integration.test.js +1 -1
- package/test/requestedWorkspaceScope.test.js +24 -0
- package/test/roleLabels.test.js +29 -7
- package/test/support/invitationTransactions.js +35 -19
- package/test/workspaceActionSurfaces.test.js +0 -55
- package/test/workspaceAuthPolicyContextResolver.test.js +1 -1
- package/test/workspaceBootstrapContributor.test.js +7 -6
- package/test/workspaceCrud.test.js +23 -0
- package/test/workspaceInvitesRepository.test.js +9 -8
- package/test/workspaceMembersService.test.js +131 -28
- package/test/workspacePendingInvitationsResource.test.js +2 -2
- package/test/workspacePendingInvitationsService.test.js +69 -22
- package/test/workspaceService.test.js +58 -47
- package/test/workspaceSettingsActions.test.js +14 -7
- package/test/workspaceSettingsRepository.test.js +11 -0
- package/test/workspaceSettingsService.test.js +11 -9
- package/test/workspacesRepository.test.js +16 -16
- package/test/workspacesRouteRequestInputValidator.test.js +4 -4
- package/src/server/common/repositories/repositoryUtils.js +0 -43
- package/src/server/common/support/deepFreeze.js +0 -1
- package/src/server/common/support/resolveActionUser.js +0 -9
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
emptyInputValidator,
|
|
3
|
-
resolveRequest
|
|
4
|
-
} from "@jskit-ai/kernel/server/actions/actionContributorHelpers";
|
|
5
|
-
import { returnJsonApiData } from "@jskit-ai/http-runtime/shared";
|
|
1
|
+
import { emptyInputValidator } from "@jskit-ai/kernel/server/actions/actionContributorHelpers";
|
|
6
2
|
import { composeSchemaDefinitions } from "@jskit-ai/kernel/shared/validators";
|
|
7
3
|
import { workspaceResource } from "../../shared/resources/workspaceResource.js";
|
|
8
4
|
import { workspaceSlugParamsValidator } from "../common/validators/routeParamsValidator.js";
|
|
9
|
-
import { resolveActionUser } from "
|
|
5
|
+
import { resolveActionUser } from "@jskit-ai/users-core/server/resolveActionUser";
|
|
10
6
|
|
|
11
7
|
function workspaceAssistantSummary({ id, slug, name, ownerUserId, avatarUrl }) {
|
|
12
8
|
return { id, slug, name, ownerUserId, avatarUrl };
|
|
@@ -20,140 +16,112 @@ const workspaceUpdateInputValidator = composeSchemaDefinitions([
|
|
|
20
16
|
context: "workspaceDirectoryActions.workspaceUpdateInputValidator"
|
|
21
17
|
});
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
request: resolveRequest(context),
|
|
46
|
-
context
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
id: "workspace.workspaces.list",
|
|
52
|
-
version: 1,
|
|
53
|
-
kind: "query",
|
|
54
|
-
channels: ["api", "automation", "internal"],
|
|
55
|
-
surfaces: ["*"],
|
|
56
|
-
permission: {
|
|
57
|
-
require: "authenticated"
|
|
58
|
-
},
|
|
59
|
-
input: emptyInputValidator,
|
|
60
|
-
output: null,
|
|
61
|
-
extensions: {
|
|
62
|
-
assistant: {
|
|
63
|
-
description: "List workspaces accessible to the signed-in user.",
|
|
64
|
-
output: workspaceResource.operations.list.output,
|
|
65
|
-
transformResult: (result) => ({
|
|
66
|
-
items: result.value.items.map(({ id, slug, name, avatarUrl, roleSid, isAccessible }) =>
|
|
67
|
-
({ id, slug, name, avatarUrl, roleSid, isAccessible })),
|
|
68
|
-
nextCursor: result.value.nextCursor
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
idempotency: "none",
|
|
73
|
-
async run(workspaceService, input, context) {
|
|
74
|
-
return returnJsonApiData({
|
|
75
|
-
items: await workspaceService.listWorkspacesForAuthenticatedUser(resolveActionUser(context, input), {
|
|
76
|
-
request: resolveRequest(context),
|
|
77
|
-
context
|
|
78
|
-
}),
|
|
79
|
-
nextCursor: null
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
id: "workspace.workspaces.read",
|
|
85
|
-
version: 1,
|
|
86
|
-
kind: "query",
|
|
87
|
-
channels: ["api", "automation", "internal"],
|
|
88
|
-
surfaces: ["*"],
|
|
89
|
-
permission: {
|
|
90
|
-
require: "any",
|
|
91
|
-
permissions: ["workspace.settings.view", "workspace.settings.update"]
|
|
92
|
-
},
|
|
93
|
-
input: workspaceSlugParamsValidator,
|
|
94
|
-
output: null,
|
|
95
|
-
extensions: {
|
|
96
|
-
assistant: {
|
|
97
|
-
description: "Read the active workspace profile.",
|
|
98
|
-
output: workspaceResource.operations.view.output,
|
|
99
|
-
transformResult: (result) => workspaceAssistantSummary(result.value)
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
idempotency: "none",
|
|
103
|
-
async run(workspaceService, input, context) {
|
|
104
|
-
return returnJsonApiData(await workspaceService.getWorkspaceForAuthenticatedUser(
|
|
19
|
+
/** @param {{ workspaceService: import("../types.js").WorkspaceDirectoryService }} dependencies */
|
|
20
|
+
function createWorkspaceDirectoryActions({ workspaceService }) {
|
|
21
|
+
return Object.freeze([
|
|
22
|
+
Object.freeze({
|
|
23
|
+
id: "workspace.workspaces.create",
|
|
24
|
+
version: 1,
|
|
25
|
+
kind: "command",
|
|
26
|
+
channels: ["api", "assistant_tool", "automation", "internal"],
|
|
27
|
+
surfaces: ["*"],
|
|
28
|
+
permission: {
|
|
29
|
+
require: "authenticated"
|
|
30
|
+
},
|
|
31
|
+
input: workspaceResource.operations.create.body,
|
|
32
|
+
output: null,
|
|
33
|
+
extensions: {
|
|
34
|
+
assistant: {
|
|
35
|
+
description: "Create a workspace for the signed-in user.",
|
|
36
|
+
output: workspaceResource.operations.create.output,
|
|
37
|
+
transformResult: (result) => workspaceAssistantSummary(result.value)
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
execute: (input, context) => workspaceService.createWorkspaceForAuthenticatedUser(
|
|
105
41
|
resolveActionUser(context, input),
|
|
106
|
-
input
|
|
107
|
-
{
|
|
108
|
-
|
|
109
|
-
|
|
42
|
+
input,
|
|
43
|
+
{ context }
|
|
44
|
+
)
|
|
45
|
+
}),
|
|
46
|
+
Object.freeze({
|
|
47
|
+
id: "workspace.workspaces.list",
|
|
48
|
+
version: 1,
|
|
49
|
+
kind: "query",
|
|
50
|
+
channels: ["api", "automation", "internal"],
|
|
51
|
+
surfaces: ["*"],
|
|
52
|
+
permission: {
|
|
53
|
+
require: "authenticated"
|
|
54
|
+
},
|
|
55
|
+
input: emptyInputValidator,
|
|
56
|
+
output: null,
|
|
57
|
+
extensions: {
|
|
58
|
+
assistant: {
|
|
59
|
+
description: "List workspaces accessible to the signed-in user.",
|
|
60
|
+
output: workspaceResource.operations.list.output,
|
|
61
|
+
transformResult: (result) => ({
|
|
62
|
+
items: result.value.items.map(({ id, slug, name, avatarUrl, roleSid, isAccessible }) =>
|
|
63
|
+
({ id, slug, name, avatarUrl, roleSid, isAccessible })),
|
|
64
|
+
nextCursor: result.value.nextCursor
|
|
65
|
+
})
|
|
110
66
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
id: "workspace.workspaces.update",
|
|
116
|
-
version: 1,
|
|
117
|
-
kind: "command",
|
|
118
|
-
channels: ["api", "assistant_tool", "automation", "internal"],
|
|
119
|
-
surfaces: ["*"],
|
|
120
|
-
permission: {
|
|
121
|
-
require: "all",
|
|
122
|
-
permissions: ["workspace.settings.update"]
|
|
123
|
-
},
|
|
124
|
-
input: workspaceUpdateInputValidator,
|
|
125
|
-
output: null,
|
|
126
|
-
extensions: {
|
|
127
|
-
assistant: {
|
|
128
|
-
description: "Update the active workspace name or avatar URL.",
|
|
129
|
-
output: workspaceResource.operations.patch.output,
|
|
130
|
-
transformResult: (result) => workspaceAssistantSummary(result.value)
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
idempotency: "optional",
|
|
134
|
-
async run(workspaceService, input, context) {
|
|
135
|
-
const { workspaceSlug, ...patch } = input;
|
|
136
|
-
return returnJsonApiData(await workspaceService.updateWorkspaceForAuthenticatedUser(
|
|
67
|
+
},
|
|
68
|
+
execute: (input, context) => workspaceService.listWorkspacesForAuthenticatedUser(
|
|
137
69
|
resolveActionUser(context, input),
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
70
|
+
{ context }
|
|
71
|
+
)
|
|
72
|
+
}),
|
|
73
|
+
Object.freeze({
|
|
74
|
+
id: "workspace.workspaces.read",
|
|
75
|
+
version: 1,
|
|
76
|
+
kind: "query",
|
|
77
|
+
channels: ["api", "automation", "internal"],
|
|
78
|
+
surfaces: ["*"],
|
|
79
|
+
permission: {
|
|
80
|
+
require: "any",
|
|
81
|
+
permissions: ["workspace.settings.view", "workspace.settings.update"]
|
|
82
|
+
},
|
|
83
|
+
input: workspaceSlugParamsValidator,
|
|
84
|
+
output: null,
|
|
85
|
+
extensions: {
|
|
86
|
+
assistant: {
|
|
87
|
+
description: "Read the active workspace profile.",
|
|
88
|
+
output: workspaceResource.operations.view.output,
|
|
89
|
+
transformResult: (result) => workspaceAssistantSummary(result.value)
|
|
143
90
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
91
|
+
},
|
|
92
|
+
execute: (input, context) => workspaceService.getWorkspaceForAuthenticatedUser(
|
|
93
|
+
resolveActionUser(context, input),
|
|
94
|
+
input.workspaceSlug,
|
|
95
|
+
{ context }
|
|
96
|
+
)
|
|
97
|
+
}),
|
|
98
|
+
Object.freeze({
|
|
99
|
+
id: "workspace.workspaces.update",
|
|
100
|
+
version: 1,
|
|
101
|
+
kind: "command",
|
|
102
|
+
channels: ["api", "assistant_tool", "automation", "internal"],
|
|
103
|
+
surfaces: ["*"],
|
|
104
|
+
permission: {
|
|
105
|
+
require: "all",
|
|
106
|
+
permissions: ["workspace.settings.update"]
|
|
107
|
+
},
|
|
108
|
+
input: workspaceUpdateInputValidator,
|
|
109
|
+
output: null,
|
|
110
|
+
extensions: {
|
|
111
|
+
assistant: {
|
|
112
|
+
description: "Update the active workspace name or avatar URL.",
|
|
113
|
+
output: workspaceResource.operations.patch.output,
|
|
114
|
+
transformResult: (result) => workspaceAssistantSummary(result.value)
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
execute: (input, context) => workspaceService.updateWorkspaceForAuthenticatedUser(
|
|
118
|
+
resolveActionUser(context, input),
|
|
119
|
+
input.workspaceSlug,
|
|
120
|
+
input,
|
|
121
|
+
{ context }
|
|
122
|
+
)
|
|
123
|
+
})
|
|
124
|
+
]);
|
|
157
125
|
}
|
|
158
126
|
|
|
159
|
-
export {
|
|
127
|
+
export { createWorkspaceDirectoryActions };
|
|
@@ -7,28 +7,16 @@ import {
|
|
|
7
7
|
WORKSPACES_COLLECTION_TRANSPORT,
|
|
8
8
|
WORKSPACES_TRANSPORT
|
|
9
9
|
} from "../../shared/jsonApiTransports.js";
|
|
10
|
-
import {
|
|
10
|
+
import { resolveWorkspaceRecordId } from "../common/support/workspaceRecordId.js";
|
|
11
|
+
import { resolveWorkspaceRoutePath } from "../support/workspaceRoutePaths.js";
|
|
11
12
|
import { workspaceSlugParamsValidator } from "../common/validators/routeParamsValidator.js";
|
|
12
13
|
import { resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig } from "../support/workspaceActionSurfaces.js";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const workspaceSlug = context?.request?.params?.workspaceSlug;
|
|
21
|
-
if (workspaceSlug != null && String(workspaceSlug).trim()) {
|
|
22
|
-
return workspaceSlug;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
throw new Error("Workspace JSON:API response requires workspace id.");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function registerWorkspaceDirectoryRoutes(router, { config = {}, workspaceSelfCreateEnabled = false } = {}) {
|
|
29
|
-
if (!router || typeof router.register !== "function") {
|
|
30
|
-
throw new TypeError("registerWorkspaceDirectoryRoutes requires router.register().");
|
|
31
|
-
}
|
|
15
|
+
/**
|
|
16
|
+
* @param {import("@jskit-ai/kernel/server/types").RuntimeRouter} router
|
|
17
|
+
* @param {{ config: import("@jskit-ai/kernel/server/types").RuntimeConfig, workspaceSelfCreateEnabled: boolean }} options
|
|
18
|
+
*/
|
|
19
|
+
function registerWorkspaceDirectoryRoutes(router, { config, workspaceSelfCreateEnabled }) {
|
|
32
20
|
const workspaceRouteSurfaceId = resolveDefaultWorkspaceRouteSurfaceIdFromAppConfig(config);
|
|
33
21
|
|
|
34
22
|
if (workspaceSelfCreateEnabled) {
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const englishI18n = createWorkspacesServerI18n();
|
|
4
|
-
|
|
5
|
-
function english(key, params) {
|
|
6
|
-
return englishI18n.t("en", key, params);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function escapeHtml(value = "") {
|
|
10
|
-
return String(value ?? "")
|
|
1
|
+
function escapeHtml(value) {
|
|
2
|
+
return String(value)
|
|
11
3
|
.replaceAll("&", "&")
|
|
12
4
|
.replaceAll("<", "<")
|
|
13
5
|
.replaceAll(">", ">")
|
|
@@ -15,35 +7,29 @@ function escapeHtml(value = "") {
|
|
|
15
7
|
.replaceAll("'", "'");
|
|
16
8
|
}
|
|
17
9
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
roleLabel = "",
|
|
26
|
-
expiresAt = "",
|
|
27
|
-
t = english
|
|
28
|
-
} = {}) {
|
|
10
|
+
/**
|
|
11
|
+
* The `workspaces.invite-email-template` default. t renders a message in the
|
|
12
|
+
* recipient's language. The markup stays here; each translated fragment,
|
|
13
|
+
* parameters included, is escaped before it enters the HTML.
|
|
14
|
+
* @type {import("../types.js").WorkspaceInviteEmailTemplate}
|
|
15
|
+
*/
|
|
16
|
+
function renderDefaultWorkspaceInviteEmail({ inviteUrl, workspace, inviter, roleSid, roleLabel, expiresAt, t }) {
|
|
29
17
|
const workspaceName =
|
|
30
|
-
String(workspace
|
|
18
|
+
String(workspace.name || workspace.slug || "").trim() || t("jskit.workspaces.inviteEmail.workspaceFallback");
|
|
31
19
|
const inviterName = String(inviter?.displayName || inviter?.email || "").trim();
|
|
32
|
-
const roleName =
|
|
33
|
-
const expiryText =
|
|
20
|
+
const roleName = roleLabel || roleSid;
|
|
21
|
+
const expiryText = t("jskit.workspaces.inviteEmail.expiry", { expiresAt });
|
|
34
22
|
const intro = inviterName
|
|
35
23
|
? t("jskit.workspaces.inviteEmail.introFromInviter", { inviter: inviterName, workspace: workspaceName, role: roleName })
|
|
36
24
|
: t("jskit.workspaces.inviteEmail.intro", { workspace: workspaceName, role: roleName });
|
|
37
25
|
const subject = t("jskit.workspaces.inviteEmail.subject", { workspace: workspaceName });
|
|
38
|
-
const text = [intro, expiryText, t("jskit.workspaces.inviteEmail.openLink"), inviteUrl]
|
|
39
|
-
.filter(Boolean)
|
|
40
|
-
.join("\n\n");
|
|
26
|
+
const text = [intro, expiryText, t("jskit.workspaces.inviteEmail.openLink"), inviteUrl].join("\n\n");
|
|
41
27
|
const html = [
|
|
42
28
|
`<p>${escapeHtml(intro)}</p>`,
|
|
43
|
-
|
|
29
|
+
`<p>${escapeHtml(expiryText)}</p>`,
|
|
44
30
|
`<p><a href="${escapeHtml(inviteUrl)}">${escapeHtml(t("jskit.workspaces.inviteEmail.accept"))}</a></p>`,
|
|
45
31
|
`<p>${escapeHtml(inviteUrl)}</p>`
|
|
46
|
-
].
|
|
32
|
+
].join("\n");
|
|
47
33
|
|
|
48
34
|
return {
|
|
49
35
|
subject,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The delivery outcome of an invitation when no feature provides a mailer. */
|
|
2
|
+
function buildMailerUnconfiguredDelivery() {
|
|
3
|
+
return {
|
|
4
|
+
status: "mailer_unconfigured",
|
|
5
|
+
message: "No workspace invite mailer is configured.",
|
|
6
|
+
messageCode: "workspaces.invite.mailerUnconfigured",
|
|
7
|
+
messageParams: {},
|
|
8
|
+
providerMessageId: ""
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The `workspaces.invite-mailer` default when no feature provides one: invitations
|
|
14
|
+
* are still created, and their delivery reports that no mailer is configured.
|
|
15
|
+
* The members service recognizes it and answers that outcome without
|
|
16
|
+
* rendering the email.
|
|
17
|
+
* @type {import("../types.js").WorkspaceInviteMailer}
|
|
18
|
+
*/
|
|
19
|
+
const unconfiguredWorkspaceInviteMailer = Object.freeze({
|
|
20
|
+
async sendWorkspaceInvite() {
|
|
21
|
+
return buildMailerUnconfiguredDelivery();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export { buildMailerUnconfiguredDelivery, unconfiguredWorkspaceInviteMailer };
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
const DEFAULT_INVITE_PATH = "/invite/:token";
|
|
2
|
+
|
|
3
|
+
// The configured invite path as an absolute path template.
|
|
4
|
+
function normalizeInvitePathTemplate(value) {
|
|
2
5
|
const raw = String(value || "").trim();
|
|
3
6
|
if (!raw) {
|
|
4
|
-
return
|
|
7
|
+
return DEFAULT_INVITE_PATH;
|
|
5
8
|
}
|
|
6
9
|
return raw.startsWith("/") ? raw : `/${raw}`;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
// The invite path for a token: the template's :token or [token] replaced, else the token appended.
|
|
13
|
+
function buildInvitePath(token, template) {
|
|
10
14
|
const encodedToken = encodeURIComponent(String(token || "").trim());
|
|
11
|
-
const template = normalizeInvitePathTemplate(pathTemplate);
|
|
12
15
|
if (template.includes(":token")) {
|
|
13
16
|
return template.replaceAll(":token", encodedToken);
|
|
14
17
|
}
|
|
@@ -18,7 +21,8 @@ function buildInvitePath(token, pathTemplate = "/invite/:token") {
|
|
|
18
21
|
return `${template.replace(/\/$/, "")}/${encodedToken}`;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
// An http(s) base URL without its trailing slash, or "" for anything else.
|
|
25
|
+
function normalizeBaseUrl(value) {
|
|
22
26
|
const raw = String(value || "").trim();
|
|
23
27
|
if (!raw) {
|
|
24
28
|
return "";
|
|
@@ -34,23 +38,23 @@ function normalizeBaseUrl(value = "") {
|
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The invitation URL builder: `workspaceInvitations.invitePath` (default
|
|
43
|
+
* /invite/:token) under `workspaceInvitations.inviteBaseUrl`, else the app's
|
|
44
|
+
* `appPublicUrl`, else APP_PUBLIC_URL; a relative path when none is set.
|
|
45
|
+
* @param {object} sources
|
|
46
|
+
* @param {import("@jskit-ai/kernel/server/types").RuntimeConfig} sources.appConfig
|
|
47
|
+
* @param {import("@jskit-ai/kernel/server/types").RuntimeEnv} sources.env
|
|
48
|
+
*/
|
|
49
|
+
function createWorkspaceInviteUrlBuilder({ appConfig, env }) {
|
|
50
|
+
const inviteConfig = appConfig.workspaceInvitations && typeof appConfig.workspaceInvitations === "object"
|
|
42
51
|
? appConfig.workspaceInvitations
|
|
43
52
|
: {};
|
|
44
|
-
const baseUrl = normalizeBaseUrl(
|
|
45
|
-
|
|
46
|
-
inviteConfig.baseUrl ||
|
|
47
|
-
appConfig?.appPublicUrl ||
|
|
48
|
-
env.APP_PUBLIC_URL ||
|
|
49
|
-
""
|
|
50
|
-
);
|
|
51
|
-
const pathTemplate = normalizeInvitePathTemplate(inviteConfig.invitePath || inviteConfig.path || "/invite/:token");
|
|
53
|
+
const baseUrl = normalizeBaseUrl(inviteConfig.inviteBaseUrl || appConfig.appPublicUrl || env.APP_PUBLIC_URL);
|
|
54
|
+
const pathTemplate = normalizeInvitePathTemplate(inviteConfig.invitePath);
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
/** @param {{ token: string, invite?: object, workspace?: object }} invite */
|
|
57
|
+
return function buildWorkspaceInviteUrl({ token }) {
|
|
54
58
|
const invitePath = buildInvitePath(token, pathTemplate);
|
|
55
59
|
if (!baseUrl) {
|
|
56
60
|
return invitePath;
|
|
@@ -59,7 +63,4 @@ function createWorkspaceInviteUrlBuilder({
|
|
|
59
63
|
};
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
export {
|
|
63
|
-
buildInvitePath,
|
|
64
|
-
createWorkspaceInviteUrlBuilder
|
|
65
|
-
};
|
|
66
|
+
export { createWorkspaceInviteUrlBuilder };
|