@jskit-ai/users-core 0.1.47 → 0.1.49
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 +9 -46
- package/package.json +8 -19
- package/src/server/UsersCoreServiceProvider.js +0 -4
- package/src/server/common/registerCommonRepositories.js +0 -5
- 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 +1 -3
- package/src/server/registerUsersCore.js +2 -14
- package/src/server/usersBootstrapContributor.js +10 -85
- package/src/shared/index.js +2 -99
- package/src/shared/settings.js +1 -119
- package/templates/migrations/users_core_generic_initial.cjs +0 -16
- package/test/authProfileSyncService.test.js +19 -10
- package/test/registerServiceRealtimeEvents.test.js +0 -94
- package/test/registerUsersCore.test.js +6 -19
- package/test/repositoryContracts.test.js +1 -11
- package/test/resourcesCanonical.test.js +1 -19
- package/test/settingsFieldRegistriesSingleton.test.js +0 -10
- package/test/usersBootstrapContributor.test.js +20 -38
- package/test/usersRouteRequestInputValidator.test.js +2 -43
- package/test/usersRouteResources.test.js +2 -20
- package/test-support/registerDefaultSettingsFields.js +0 -1
- 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/consoleSettings/bootConsoleSettingsRoutes.js +0 -63
- package/src/server/consoleSettings/consoleService.js +0 -36
- package/src/server/consoleSettings/consoleSettingsActions.js +0 -55
- package/src/server/consoleSettings/consoleSettingsRepository.js +0 -115
- package/src/server/consoleSettings/consoleSettingsService.js +0 -40
- package/src/server/consoleSettings/registerConsoleSettings.js +0 -56
- 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 -135
- package/src/server/support/workspaceInvitationsPolicy.js +0 -45
- package/src/server/support/workspaceRouteInput.js +0 -22
- package/src/server/workspaceBootstrapContributor.js +0 -211
- 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/consoleSettingsFields.js +0 -54
- package/src/shared/resources/consoleSettingsResource.js +0 -119
- 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/templates/migrations/users_core_console_owner.cjs +0 -37
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +0 -11
- package/test/consoleService.test.js +0 -57
- package/test/consoleSettingsService.test.js +0 -86
- 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 -105
- 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,281 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
3
|
-
import {
|
|
4
|
-
TENANCY_MODE_NONE,
|
|
5
|
-
resolveTenancyProfile
|
|
6
|
-
} from "../../../shared/tenancyProfile.js";
|
|
7
|
-
import {
|
|
8
|
-
resolveRolePermissions
|
|
9
|
-
} from "../../../shared/roles.js";
|
|
10
|
-
import {
|
|
11
|
-
mapWorkspaceSummary
|
|
12
|
-
} from "../formatters/workspaceFormatter.js";
|
|
13
|
-
import { normalizeLowerText, normalizeText } from "@jskit-ai/kernel/shared/actions/textNormalization";
|
|
14
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
15
|
-
import { authenticatedUserValidator } from "../validators/authenticatedUserValidator.js";
|
|
16
|
-
|
|
17
|
-
function toSlugPart(value) {
|
|
18
|
-
const normalized = normalizeLowerText(value)
|
|
19
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
20
|
-
.replace(/^-+|-+$/g, "")
|
|
21
|
-
.slice(0, 48);
|
|
22
|
-
return normalized || "workspace";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function buildWorkspaceBaseSlug(user = {}) {
|
|
26
|
-
const username = normalizeLowerText(user.username);
|
|
27
|
-
if (username) {
|
|
28
|
-
return toSlugPart(username);
|
|
29
|
-
}
|
|
30
|
-
const displayName = normalizeText(user.displayName);
|
|
31
|
-
if (displayName) {
|
|
32
|
-
return toSlugPart(displayName);
|
|
33
|
-
}
|
|
34
|
-
const email = normalizeLowerText(user.email);
|
|
35
|
-
if (email.includes("@")) {
|
|
36
|
-
return toSlugPart(email.split("@")[0]);
|
|
37
|
-
}
|
|
38
|
-
return "workspace";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function buildWorkspaceName(user = {}) {
|
|
42
|
-
const displayName = normalizeText(user.displayName);
|
|
43
|
-
if (displayName) {
|
|
44
|
-
return `${displayName}'s Workspace`;
|
|
45
|
-
}
|
|
46
|
-
const email = normalizeLowerText(user.email);
|
|
47
|
-
if (email) {
|
|
48
|
-
return `${email}'s Workspace`;
|
|
49
|
-
}
|
|
50
|
-
return "Workspace";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function buildPermissionsFromMembership(membership, appConfig = {}) {
|
|
54
|
-
const roleSid = normalizeLowerText(membership?.roleSid || "member");
|
|
55
|
-
return resolveRolePermissions(roleSid, appConfig);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function hashInviteToken(token) {
|
|
59
|
-
return createHash("sha256").update(normalizeText(token)).digest("hex");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function normalizeWorkspaceCreationInput(payload = {}) {
|
|
63
|
-
const source = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
64
|
-
return {
|
|
65
|
-
name: normalizeText(source.name),
|
|
66
|
-
requestedSlug: normalizeLowerText(source.slug)
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function createService({
|
|
71
|
-
appConfig = {},
|
|
72
|
-
workspacesRepository,
|
|
73
|
-
workspaceMembershipsRepository,
|
|
74
|
-
workspaceSettingsRepository
|
|
75
|
-
} = {}) {
|
|
76
|
-
if (
|
|
77
|
-
!workspacesRepository ||
|
|
78
|
-
!workspaceMembershipsRepository ||
|
|
79
|
-
!workspaceSettingsRepository
|
|
80
|
-
) {
|
|
81
|
-
throw new Error("workspaceService requires repositories.");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const resolvedTenancyProfile = resolveTenancyProfile(appConfig);
|
|
85
|
-
const resolvedTenancyMode = resolvedTenancyProfile.mode;
|
|
86
|
-
const workspacePolicy = resolvedTenancyProfile.workspace;
|
|
87
|
-
async function ensureUniqueWorkspaceSlug(baseSlug, options = {}) {
|
|
88
|
-
let suffix = 0;
|
|
89
|
-
while (suffix < 1000) {
|
|
90
|
-
suffix += 1;
|
|
91
|
-
const candidate = suffix === 1 ? toSlugPart(baseSlug) : `${toSlugPart(baseSlug)}-${suffix}`;
|
|
92
|
-
const existing = await workspacesRepository.findBySlug(candidate, options);
|
|
93
|
-
if (!existing) {
|
|
94
|
-
return candidate;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
throw new AppError(500, "Unable to generate unique workspace slug.");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function ensureWorkspaceSettingsForWorkspace(workspace, options = {}) {
|
|
101
|
-
return workspaceSettingsRepository.ensureForWorkspaceId(workspace?.id, {
|
|
102
|
-
...options,
|
|
103
|
-
workspace
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function ensurePersonalWorkspaceForUser(user, options = {}) {
|
|
108
|
-
const normalizedUser = authenticatedUserValidator.normalize(user);
|
|
109
|
-
if (!normalizedUser) {
|
|
110
|
-
throw new AppError(400, "Invalid authenticated user payload.");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const existing = await workspacesRepository.findPersonalByOwnerUserId(normalizedUser.id, options);
|
|
114
|
-
if (existing) {
|
|
115
|
-
await workspaceMembershipsRepository.ensureOwnerMembership(existing.id, normalizedUser.id, options);
|
|
116
|
-
await ensureWorkspaceSettingsForWorkspace(existing, options);
|
|
117
|
-
return existing;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const slug = await ensureUniqueWorkspaceSlug(buildWorkspaceBaseSlug(normalizedUser), options);
|
|
121
|
-
const inserted = await workspacesRepository.insert(
|
|
122
|
-
{
|
|
123
|
-
slug,
|
|
124
|
-
name: buildWorkspaceName(normalizedUser),
|
|
125
|
-
ownerUserId: normalizedUser.id,
|
|
126
|
-
isPersonal: true,
|
|
127
|
-
avatarUrl: ""
|
|
128
|
-
},
|
|
129
|
-
options
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
await workspaceMembershipsRepository.ensureOwnerMembership(inserted.id, normalizedUser.id, options);
|
|
133
|
-
await ensureWorkspaceSettingsForWorkspace(inserted, options);
|
|
134
|
-
return inserted;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function listWorkspacesForUser(user, options = {}) {
|
|
138
|
-
const normalizedUser = authenticatedUserValidator.normalize(user);
|
|
139
|
-
if (!normalizedUser) {
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (resolvedTenancyMode === TENANCY_MODE_NONE) {
|
|
144
|
-
return [];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const list = await workspacesRepository.listForUserId(normalizedUser.id, options);
|
|
148
|
-
const accessible = list
|
|
149
|
-
.map((entry) => mapWorkspaceSummary(entry, { roleSid: entry.roleSid, status: entry.membershipStatus }))
|
|
150
|
-
.filter((entry) => entry.isAccessible);
|
|
151
|
-
|
|
152
|
-
return accessible;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function listWorkspacesForAuthenticatedUser(user, options = {}) {
|
|
156
|
-
return listWorkspacesForUser(user, options);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function provisionWorkspaceForNewUser(user, options = {}) {
|
|
160
|
-
const normalizedUser = authenticatedUserValidator.normalize(user);
|
|
161
|
-
if (!normalizedUser) {
|
|
162
|
-
throw new AppError(400, "Invalid authenticated user payload.");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (workspacePolicy.autoProvision !== true) {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return ensurePersonalWorkspaceForUser(normalizedUser, options);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function createWorkspaceForAuthenticatedUser(user, payload = {}, options = {}) {
|
|
173
|
-
const normalizedUser = authenticatedUserValidator.normalize(user);
|
|
174
|
-
if (!normalizedUser) {
|
|
175
|
-
throw new AppError(401, "Authentication required.");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (workspacePolicy.allowSelfCreate !== true) {
|
|
179
|
-
throw new AppError(403, "Workspace creation is disabled for this tenancy mode.");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const createInput = normalizeWorkspaceCreationInput(payload);
|
|
183
|
-
if (!createInput.name) {
|
|
184
|
-
throw new AppError(400, "Workspace name is required.");
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const slugBase = createInput.requestedSlug || toSlugPart(createInput.name);
|
|
188
|
-
const slug = await ensureUniqueWorkspaceSlug(slugBase, options);
|
|
189
|
-
const inserted = await workspacesRepository.insert(
|
|
190
|
-
{
|
|
191
|
-
slug,
|
|
192
|
-
name: createInput.name,
|
|
193
|
-
ownerUserId: normalizedUser.id,
|
|
194
|
-
isPersonal: false,
|
|
195
|
-
avatarUrl: ""
|
|
196
|
-
},
|
|
197
|
-
options
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
await workspaceMembershipsRepository.ensureOwnerMembership(inserted.id, normalizedUser.id, options);
|
|
201
|
-
await ensureWorkspaceSettingsForWorkspace(inserted, options);
|
|
202
|
-
return inserted;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async function getWorkspaceForAuthenticatedUser(user, workspaceSlug, options = {}) {
|
|
206
|
-
const workspaceContext = await resolveWorkspaceContextForUserBySlug(user, workspaceSlug, options);
|
|
207
|
-
return workspaceContext.workspace;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async function updateWorkspaceForAuthenticatedUser(user, workspaceSlug, patch = {}, options = {}) {
|
|
211
|
-
const workspaceContext = await resolveWorkspaceContextForUserBySlug(user, workspaceSlug, options);
|
|
212
|
-
return workspacesRepository.updateById(workspaceContext.workspace.id, patch, options);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function resolveWorkspaceContextForUserBySlug(user, workspaceSlug, options = {}) {
|
|
216
|
-
const normalizedUser = authenticatedUserValidator.normalize(user);
|
|
217
|
-
if (!normalizedUser) {
|
|
218
|
-
throw new AppError(401, "Authentication required.");
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (resolvedTenancyMode === TENANCY_MODE_NONE) {
|
|
222
|
-
throw new AppError(403, "Workspace context is disabled.");
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const normalizedWorkspaceSlug = normalizeLowerText(workspaceSlug);
|
|
226
|
-
if (!normalizedWorkspaceSlug) {
|
|
227
|
-
throw new AppError(400, "workspaceSlug is required.");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const workspace = await workspacesRepository.findBySlug(normalizedWorkspaceSlug, options);
|
|
231
|
-
|
|
232
|
-
if (!workspace) {
|
|
233
|
-
throw new AppError(404, "Workspace not found.");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
let membership = await workspaceMembershipsRepository.findByWorkspaceIdAndUserId(
|
|
237
|
-
workspace.id,
|
|
238
|
-
normalizedUser.id,
|
|
239
|
-
options
|
|
240
|
-
);
|
|
241
|
-
const actorOwnsWorkspace =
|
|
242
|
-
normalizeRecordId(workspace.ownerUserId, { fallback: null }) ===
|
|
243
|
-
normalizeRecordId(normalizedUser.id, { fallback: null });
|
|
244
|
-
const membershipIsActive = normalizeLowerText(membership?.status) === "active";
|
|
245
|
-
|
|
246
|
-
if (!membershipIsActive && actorOwnsWorkspace) {
|
|
247
|
-
membership = await workspaceMembershipsRepository.ensureOwnerMembership(workspace.id, normalizedUser.id, options);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (!membership || normalizeLowerText(membership.status) !== "active") {
|
|
251
|
-
throw new AppError(403, "You do not have access to this workspace.");
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const workspaceSettings = await ensureWorkspaceSettingsForWorkspace(workspace, options);
|
|
255
|
-
const permissions = buildPermissionsFromMembership(membership, appConfig);
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
workspace,
|
|
259
|
-
membership,
|
|
260
|
-
permissions,
|
|
261
|
-
workspaceSettings
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return Object.freeze({
|
|
266
|
-
toSlugPart,
|
|
267
|
-
buildWorkspaceName,
|
|
268
|
-
buildWorkspaceBaseSlug,
|
|
269
|
-
hashInviteToken,
|
|
270
|
-
ensurePersonalWorkspaceForUser,
|
|
271
|
-
provisionWorkspaceForNewUser,
|
|
272
|
-
createWorkspaceForAuthenticatedUser,
|
|
273
|
-
getWorkspaceForAuthenticatedUser,
|
|
274
|
-
updateWorkspaceForAuthenticatedUser,
|
|
275
|
-
listWorkspacesForUser,
|
|
276
|
-
listWorkspacesForAuthenticatedUser,
|
|
277
|
-
resolveWorkspaceContextForUserBySlug
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export { createService };
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
USERS_WORKSPACE_API_BASE_PATH,
|
|
3
|
-
normalizeApiRelativePath
|
|
4
|
-
} from "../../../shared/support/usersApiPaths.js";
|
|
5
|
-
|
|
6
|
-
const USERS_WORKSPACE_ROUTE_BASE_PATH = USERS_WORKSPACE_API_BASE_PATH;
|
|
7
|
-
|
|
8
|
-
function resolveWorkspaceRoutePath(relativePath = "/") {
|
|
9
|
-
const normalizedRelativePath = normalizeApiRelativePath(relativePath);
|
|
10
|
-
if (normalizedRelativePath === "/") {
|
|
11
|
-
return USERS_WORKSPACE_ROUTE_BASE_PATH;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return `${USERS_WORKSPACE_ROUTE_BASE_PATH}${normalizedRelativePath}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export { USERS_WORKSPACE_ROUTE_BASE_PATH, resolveWorkspaceRoutePath };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Type } from "@fastify/type-provider-typebox";
|
|
2
|
-
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
3
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
-
|
|
5
|
-
function normalizeRouteParams(input = {}) {
|
|
6
|
-
const source = normalizeObjectInput(input);
|
|
7
|
-
const normalized = {};
|
|
8
|
-
|
|
9
|
-
if (Object.hasOwn(source, "workspaceSlug")) {
|
|
10
|
-
normalized.workspaceSlug = normalizeText(source.workspaceSlug).toLowerCase();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (Object.hasOwn(source, "memberUserId")) {
|
|
14
|
-
normalized.memberUserId = normalizeText(source.memberUserId);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (Object.hasOwn(source, "inviteId")) {
|
|
18
|
-
normalized.inviteId = normalizeText(source.inviteId);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (Object.hasOwn(source, "provider")) {
|
|
22
|
-
normalized.provider = normalizeText(source.provider);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return normalized;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function normalizeWorkspaceSlugParams(input = {}) {
|
|
29
|
-
const source = normalizeObjectInput(input);
|
|
30
|
-
const normalized = {};
|
|
31
|
-
|
|
32
|
-
if (Object.hasOwn(source, "workspaceSlug")) {
|
|
33
|
-
normalized.workspaceSlug = normalizeText(source.workspaceSlug).toLowerCase();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return normalized;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const routeParamsValidator = Object.freeze({
|
|
40
|
-
schema: Type.Object(
|
|
41
|
-
{
|
|
42
|
-
workspaceSlug: Type.Optional(Type.String({ minLength: 1 })),
|
|
43
|
-
memberUserId: Type.Optional(Type.String({ minLength: 1 })),
|
|
44
|
-
inviteId: Type.Optional(Type.String({ minLength: 1 })),
|
|
45
|
-
provider: Type.Optional(Type.String({ minLength: 1 }))
|
|
46
|
-
},
|
|
47
|
-
{ additionalProperties: false }
|
|
48
|
-
),
|
|
49
|
-
normalize: normalizeRouteParams
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const workspaceSlugParamsValidator = Object.freeze({
|
|
53
|
-
schema: Type.Object(
|
|
54
|
-
{
|
|
55
|
-
workspaceSlug: Type.Optional(Type.String({ minLength: 1 }))
|
|
56
|
-
},
|
|
57
|
-
{ additionalProperties: false }
|
|
58
|
-
),
|
|
59
|
-
normalize: normalizeWorkspaceSlugParams
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
export { routeParamsValidator, workspaceSlugParamsValidator };
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
|
|
2
|
-
import { consoleSettingsResource } from "../../shared/resources/consoleSettingsResource.js";
|
|
3
|
-
|
|
4
|
-
function bootConsoleSettingsRoutes(app) {
|
|
5
|
-
if (!app || typeof app.make !== "function") {
|
|
6
|
-
throw new Error("bootConsoleSettingsRoutes requires application make().");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const router = app.make("jskit.http.router");
|
|
10
|
-
|
|
11
|
-
router.register(
|
|
12
|
-
"GET",
|
|
13
|
-
"/api/console/settings",
|
|
14
|
-
{
|
|
15
|
-
auth: "required",
|
|
16
|
-
surface: "console",
|
|
17
|
-
meta: {
|
|
18
|
-
tags: ["console", "settings"],
|
|
19
|
-
summary: "Get console settings"
|
|
20
|
-
},
|
|
21
|
-
responseValidators: withStandardErrorResponses({
|
|
22
|
-
200: consoleSettingsResource.operations.view.outputValidator
|
|
23
|
-
})
|
|
24
|
-
},
|
|
25
|
-
async function (request, reply) {
|
|
26
|
-
const response = await request.executeAction({
|
|
27
|
-
actionId: "console.settings.read"
|
|
28
|
-
});
|
|
29
|
-
reply.code(200).send(response);
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
router.register(
|
|
34
|
-
"PATCH",
|
|
35
|
-
"/api/console/settings",
|
|
36
|
-
{
|
|
37
|
-
auth: "required",
|
|
38
|
-
surface: "console",
|
|
39
|
-
meta: {
|
|
40
|
-
tags: ["console", "settings"],
|
|
41
|
-
summary: "Update console settings"
|
|
42
|
-
},
|
|
43
|
-
bodyValidator: consoleSettingsResource.operations.replace.bodyValidator,
|
|
44
|
-
responseValidators: withStandardErrorResponses(
|
|
45
|
-
{
|
|
46
|
-
200: consoleSettingsResource.operations.view.outputValidator
|
|
47
|
-
},
|
|
48
|
-
{ includeValidation400: true }
|
|
49
|
-
)
|
|
50
|
-
},
|
|
51
|
-
async function (request, reply) {
|
|
52
|
-
const response = await request.executeAction({
|
|
53
|
-
actionId: "console.settings.update",
|
|
54
|
-
input: {
|
|
55
|
-
payload: request.input.body
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
reply.code(200).send(response);
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export { bootConsoleSettingsRoutes };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
2
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
-
|
|
4
|
-
function createService({ consoleSettingsRepository } = {}) {
|
|
5
|
-
if (!consoleSettingsRepository || typeof consoleSettingsRepository.ensureOwnerUserId !== "function") {
|
|
6
|
-
throw new Error("consoleService requires consoleSettingsRepository.ensureOwnerUserId().");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async function ensureInitialConsoleMember(userId, options = {}) {
|
|
10
|
-
const normalizedUserId = normalizeRecordId(userId, { fallback: null });
|
|
11
|
-
if (!normalizedUserId) {
|
|
12
|
-
throw new AppError(400, "Invalid console user.");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return consoleSettingsRepository.ensureOwnerUserId(normalizedUserId, options);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function requireConsoleOwner(context = {}, options = {}) {
|
|
19
|
-
const actorUserId = normalizeRecordId(context?.actor?.id, { fallback: null });
|
|
20
|
-
if (!actorUserId) {
|
|
21
|
-
throw new AppError(401, "Authentication required.");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const ownerUserId = await ensureInitialConsoleMember(actorUserId, options);
|
|
25
|
-
if (actorUserId !== ownerUserId) {
|
|
26
|
-
throw new AppError(403, "Forbidden.");
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return Object.freeze({
|
|
31
|
-
ensureInitialConsoleMember,
|
|
32
|
-
requireConsoleOwner
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export { createService };
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EMPTY_INPUT_VALIDATOR
|
|
3
|
-
} from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
|
|
4
|
-
import { consoleSettingsResource } from "../../shared/resources/consoleSettingsResource.js";
|
|
5
|
-
|
|
6
|
-
const consoleSettingsActions = Object.freeze([
|
|
7
|
-
{
|
|
8
|
-
id: "console.settings.read",
|
|
9
|
-
version: 1,
|
|
10
|
-
kind: "query",
|
|
11
|
-
channels: ["api", "automation", "internal"],
|
|
12
|
-
surfacesFrom: "console",
|
|
13
|
-
permission: {
|
|
14
|
-
require: "authenticated"
|
|
15
|
-
},
|
|
16
|
-
inputValidator: EMPTY_INPUT_VALIDATOR,
|
|
17
|
-
outputValidator: consoleSettingsResource.operations.view.outputValidator,
|
|
18
|
-
idempotency: "none",
|
|
19
|
-
audit: {
|
|
20
|
-
actionName: "console.settings.read"
|
|
21
|
-
},
|
|
22
|
-
observability: {},
|
|
23
|
-
async execute(_input, context, deps) {
|
|
24
|
-
return deps.consoleSettingsService.getSettings({
|
|
25
|
-
context
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "console.settings.update",
|
|
31
|
-
version: 1,
|
|
32
|
-
kind: "command",
|
|
33
|
-
channels: ["api", "automation", "internal"],
|
|
34
|
-
surfacesFrom: "console",
|
|
35
|
-
permission: {
|
|
36
|
-
require: "authenticated"
|
|
37
|
-
},
|
|
38
|
-
inputValidator: {
|
|
39
|
-
payload: consoleSettingsResource.operations.replace.bodyValidator
|
|
40
|
-
},
|
|
41
|
-
outputValidator: consoleSettingsResource.operations.replace.outputValidator,
|
|
42
|
-
idempotency: "optional",
|
|
43
|
-
audit: {
|
|
44
|
-
actionName: "console.settings.update"
|
|
45
|
-
},
|
|
46
|
-
observability: {},
|
|
47
|
-
async execute(input, context, deps) {
|
|
48
|
-
return deps.consoleSettingsService.updateSettings(input.payload, {
|
|
49
|
-
context
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
export { consoleSettingsActions };
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeDbRecordId,
|
|
3
|
-
normalizeRecordId,
|
|
4
|
-
nowDb,
|
|
5
|
-
toIsoString,
|
|
6
|
-
createWithTransaction
|
|
7
|
-
} from "../common/repositories/repositoryUtils.js";
|
|
8
|
-
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
9
|
-
import { consoleSettingsFields } from "../../shared/resources/consoleSettingsFields.js";
|
|
10
|
-
|
|
11
|
-
function mapSettings(row = {}) {
|
|
12
|
-
const settings = {};
|
|
13
|
-
for (const field of consoleSettingsFields) {
|
|
14
|
-
const rawValue = Object.hasOwn(row, field.dbColumn)
|
|
15
|
-
? row[field.dbColumn]
|
|
16
|
-
: field.resolveDefault({
|
|
17
|
-
settings: row
|
|
18
|
-
});
|
|
19
|
-
settings[field.key] = field.normalizeOutput(rawValue, {
|
|
20
|
-
settings: row
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
return settings;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function mapSingletonRow(row) {
|
|
27
|
-
if (!row) {
|
|
28
|
-
throw new Error("console_settings singleton row is missing.");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const ownerUserId = normalizeDbRecordId(row.owner_user_id, { fallback: null });
|
|
32
|
-
return {
|
|
33
|
-
id: normalizeDbRecordId(row.id, { fallback: "1" }),
|
|
34
|
-
ownerUserId,
|
|
35
|
-
settings: mapSettings(row),
|
|
36
|
-
createdAt: toIsoString(row.created_at),
|
|
37
|
-
updatedAt: toIsoString(row.updated_at)
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function createRepository(knex) {
|
|
42
|
-
if (typeof knex !== "function") {
|
|
43
|
-
throw new TypeError("consoleSettingsRepository requires knex.");
|
|
44
|
-
}
|
|
45
|
-
const withTransaction = createWithTransaction(knex);
|
|
46
|
-
|
|
47
|
-
async function readSingleton(client) {
|
|
48
|
-
return client("console_settings").where({ id: 1 }).first();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function getSingleton(options = {}) {
|
|
52
|
-
const client = options?.trx || knex;
|
|
53
|
-
return mapSingletonRow(await readSingleton(client));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function ensureOwnerUserId(userId, options = {}) {
|
|
57
|
-
const client = options?.trx || knex;
|
|
58
|
-
const candidateOwnerUserId = normalizeRecordId(userId, { fallback: null });
|
|
59
|
-
if (!candidateOwnerUserId) {
|
|
60
|
-
throw new TypeError("consoleSettingsRepository.ensureOwnerUserId requires a positive user id.");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const current = mapSingletonRow(await readSingleton(client));
|
|
64
|
-
if (current.ownerUserId) {
|
|
65
|
-
return current.ownerUserId;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
await client("console_settings")
|
|
69
|
-
.where({ id: 1 })
|
|
70
|
-
.whereNull("owner_user_id")
|
|
71
|
-
.update({
|
|
72
|
-
owner_user_id: candidateOwnerUserId,
|
|
73
|
-
updated_at: nowDb()
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const reloaded = mapSingletonRow(await readSingleton(client));
|
|
77
|
-
if (!reloaded.ownerUserId) {
|
|
78
|
-
throw new Error("console_settings owner_user_id could not be resolved.");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return reloaded.ownerUserId;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function updateSingleton(patch, options = {}) {
|
|
85
|
-
const client = options?.trx || knex;
|
|
86
|
-
const source = normalizeObjectInput(patch);
|
|
87
|
-
const dbPatch = {
|
|
88
|
-
updated_at: nowDb()
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
for (const field of consoleSettingsFields) {
|
|
92
|
-
if (!Object.hasOwn(source, field.key)) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
dbPatch[field.dbColumn] = field.normalizeInput(source[field.key], {
|
|
96
|
-
payload: source
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
await client("console_settings")
|
|
101
|
-
.where({ id: 1 })
|
|
102
|
-
.update(dbPatch);
|
|
103
|
-
|
|
104
|
-
return getSingleton({ trx: client });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return Object.freeze({
|
|
108
|
-
withTransaction,
|
|
109
|
-
getSingleton,
|
|
110
|
-
ensureOwnerUserId,
|
|
111
|
-
updateSingleton
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export { createRepository };
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
function buildSettingsResponse(record = {}) {
|
|
2
|
-
return {
|
|
3
|
-
settings: {
|
|
4
|
-
...(record?.settings && typeof record.settings === "object" ? record.settings : {})
|
|
5
|
-
}
|
|
6
|
-
};
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function createService({ consoleSettingsRepository, consoleService } = {}) {
|
|
10
|
-
if (!consoleSettingsRepository || typeof consoleSettingsRepository.getSingleton !== "function") {
|
|
11
|
-
throw new Error("consoleSettingsService requires consoleSettingsRepository.getSingleton().");
|
|
12
|
-
}
|
|
13
|
-
if (!consoleSettingsRepository || typeof consoleSettingsRepository.updateSingleton !== "function") {
|
|
14
|
-
throw new Error("consoleSettingsService requires consoleSettingsRepository.updateSingleton().");
|
|
15
|
-
}
|
|
16
|
-
if (!consoleService || typeof consoleService.requireConsoleOwner !== "function") {
|
|
17
|
-
throw new Error("consoleSettingsService requires consoleService.requireConsoleOwner().");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function getSettings(options = {}) {
|
|
21
|
-
await consoleService.requireConsoleOwner(options?.context, options);
|
|
22
|
-
const settings = await consoleSettingsRepository.getSingleton();
|
|
23
|
-
|
|
24
|
-
return buildSettingsResponse(settings);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function updateSettings(input = {}, options = {}) {
|
|
28
|
-
await consoleService.requireConsoleOwner(options?.context, options);
|
|
29
|
-
const settings = await consoleSettingsRepository.updateSingleton(input);
|
|
30
|
-
|
|
31
|
-
return buildSettingsResponse(settings);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return Object.freeze({
|
|
35
|
-
getSettings,
|
|
36
|
-
updateSettings
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export { createService };
|