@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.
Files changed (104) hide show
  1. package/package.descriptor.mjs +9 -46
  2. package/package.json +8 -19
  3. package/src/server/UsersCoreServiceProvider.js +0 -4
  4. package/src/server/common/registerCommonRepositories.js +0 -5
  5. package/src/server/common/services/authProfileSyncService.js +28 -7
  6. package/src/server/common/support/realtimeServiceEvents.js +1 -59
  7. package/src/server/profileSyncLifecycleContributorRegistry.js +56 -0
  8. package/src/server/registerUsersBootstrap.js +1 -3
  9. package/src/server/registerUsersCore.js +2 -14
  10. package/src/server/usersBootstrapContributor.js +10 -85
  11. package/src/shared/index.js +2 -99
  12. package/src/shared/settings.js +1 -119
  13. package/templates/migrations/users_core_generic_initial.cjs +0 -16
  14. package/test/authProfileSyncService.test.js +19 -10
  15. package/test/registerServiceRealtimeEvents.test.js +0 -94
  16. package/test/registerUsersCore.test.js +6 -19
  17. package/test/repositoryContracts.test.js +1 -11
  18. package/test/resourcesCanonical.test.js +1 -19
  19. package/test/settingsFieldRegistriesSingleton.test.js +0 -10
  20. package/test/usersBootstrapContributor.test.js +20 -38
  21. package/test/usersRouteRequestInputValidator.test.js +2 -43
  22. package/test/usersRouteResources.test.js +2 -20
  23. package/test-support/registerDefaultSettingsFields.js +0 -1
  24. package/src/server/UsersWorkspacesServiceProvider.js +0 -44
  25. package/src/server/common/contributors/workspaceActionContextContributor.js +0 -88
  26. package/src/server/common/contributors/workspaceAuthPolicyContextResolver.js +0 -34
  27. package/src/server/common/contributors/workspaceRouteVisibilityResolver.js +0 -78
  28. package/src/server/common/formatters/workspaceFormatter.js +0 -53
  29. package/src/server/common/repositories/workspaceInvitesRepository.js +0 -208
  30. package/src/server/common/repositories/workspaceMembershipsRepository.js +0 -190
  31. package/src/server/common/repositories/workspacesRepository.js +0 -202
  32. package/src/server/common/services/workspaceContextService.js +0 -281
  33. package/src/server/common/support/workspaceRoutePaths.js +0 -17
  34. package/src/server/common/validators/routeParamsValidator.js +0 -62
  35. package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +0 -63
  36. package/src/server/consoleSettings/consoleService.js +0 -36
  37. package/src/server/consoleSettings/consoleSettingsActions.js +0 -55
  38. package/src/server/consoleSettings/consoleSettingsRepository.js +0 -115
  39. package/src/server/consoleSettings/consoleSettingsService.js +0 -40
  40. package/src/server/consoleSettings/registerConsoleSettings.js +0 -56
  41. package/src/server/registerWorkspaceBootstrap.js +0 -27
  42. package/src/server/registerWorkspaceCore.js +0 -73
  43. package/src/server/registerWorkspaceRepositories.js +0 -26
  44. package/src/server/support/resolveWorkspace.js +0 -16
  45. package/src/server/support/workspaceActionSurfaces.js +0 -135
  46. package/src/server/support/workspaceInvitationsPolicy.js +0 -45
  47. package/src/server/support/workspaceRouteInput.js +0 -22
  48. package/src/server/workspaceBootstrapContributor.js +0 -211
  49. package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +0 -133
  50. package/src/server/workspaceDirectory/registerWorkspaceDirectory.js +0 -19
  51. package/src/server/workspaceDirectory/workspaceDirectoryActions.js +0 -133
  52. package/src/server/workspaceMembers/bootWorkspaceMembers.js +0 -236
  53. package/src/server/workspaceMembers/registerWorkspaceMembers.js +0 -108
  54. package/src/server/workspaceMembers/workspaceMembersActions.js +0 -186
  55. package/src/server/workspaceMembers/workspaceMembersService.js +0 -222
  56. package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +0 -62
  57. package/src/server/workspacePendingInvitations/registerWorkspacePendingInvitations.js +0 -119
  58. package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +0 -74
  59. package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +0 -138
  60. package/src/server/workspaceSettings/bootWorkspaceSettings.js +0 -76
  61. package/src/server/workspaceSettings/registerWorkspaceSettings.js +0 -62
  62. package/src/server/workspaceSettings/workspaceSettingsActions.js +0 -72
  63. package/src/server/workspaceSettings/workspaceSettingsRepository.js +0 -154
  64. package/src/server/workspaceSettings/workspaceSettingsService.js +0 -66
  65. package/src/shared/resources/consoleSettingsFields.js +0 -54
  66. package/src/shared/resources/consoleSettingsResource.js +0 -119
  67. package/src/shared/resources/workspaceMembersResource.js +0 -354
  68. package/src/shared/resources/workspacePendingInvitationsResource.js +0 -82
  69. package/src/shared/resources/workspaceResource.js +0 -176
  70. package/src/shared/resources/workspaceSettingsFields.js +0 -59
  71. package/src/shared/resources/workspaceSettingsResource.js +0 -169
  72. package/src/shared/roles.js +0 -161
  73. package/src/shared/support/usersApiPaths.js +0 -43
  74. package/src/shared/support/usersVisibility.js +0 -42
  75. package/src/shared/support/workspacePathModel.js +0 -145
  76. package/src/shared/tenancyMode.js +0 -35
  77. package/src/shared/tenancyProfile.js +0 -73
  78. package/templates/migrations/users_core_console_owner.cjs +0 -37
  79. package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +0 -11
  80. package/test/consoleService.test.js +0 -57
  81. package/test/consoleSettingsService.test.js +0 -86
  82. package/test/registerWorkspaceDirectory.test.js +0 -31
  83. package/test/registerWorkspaceSettings.test.js +0 -40
  84. package/test/roles.test.js +0 -159
  85. package/test/tenancyProfile.test.js +0 -67
  86. package/test/usersApiPaths.test.js +0 -49
  87. package/test/usersRouteValidators.test.js +0 -49
  88. package/test/usersVisibility.test.js +0 -27
  89. package/test/workspaceActionContextContributor.test.js +0 -344
  90. package/test/workspaceActionSurfaces.test.js +0 -105
  91. package/test/workspaceAuthPolicyContextResolver.test.js +0 -119
  92. package/test/workspaceBootstrapContributor.test.js +0 -154
  93. package/test/workspaceInvitationsPolicy.test.js +0 -71
  94. package/test/workspaceInvitesRepository.test.js +0 -111
  95. package/test/workspaceMembersService.test.js +0 -398
  96. package/test/workspacePathModel.test.js +0 -93
  97. package/test/workspacePendingInvitationsResource.test.js +0 -38
  98. package/test/workspacePendingInvitationsService.test.js +0 -151
  99. package/test/workspaceRouteVisibilityResolver.test.js +0 -83
  100. package/test/workspaceService.test.js +0 -546
  101. package/test/workspaceSettingsActions.test.js +0 -52
  102. package/test/workspaceSettingsRepository.test.js +0 -202
  103. package/test/workspaceSettingsResource.test.js +0 -169
  104. 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 };