@jskit-ai/console-core 0.1.1
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 +119 -0
- package/package.json +18 -0
- package/src/server/ConsoleCoreServiceProvider.js +22 -0
- package/src/server/consoleBootstrapContributor.js +37 -0
- package/src/server/consoleSettings/bootConsoleSettingsRoutes.js +63 -0
- package/src/server/consoleSettings/consoleService.js +36 -0
- package/src/server/consoleSettings/consoleSettingsActions.js +55 -0
- package/src/server/consoleSettings/consoleSettingsRepository.js +119 -0
- package/src/server/consoleSettings/consoleSettingsService.js +40 -0
- package/src/server/consoleSettings/registerConsoleSettings.js +56 -0
- package/src/server/registerConsoleBootstrap.js +16 -0
- package/src/server/registerConsoleCore.js +17 -0
- package/src/server/support/consoleActionSurfaces.js +62 -0
- package/src/shared/operationMessages.js +16 -0
- package/src/shared/resources/consoleSettingsFields.js +54 -0
- package/src/shared/resources/consoleSettingsResource.js +119 -0
- package/src/shared/resources/resolveGlobalArrayRegistry.js +6 -0
- package/templates/migrations/console_core_generic_initial.cjs +27 -0
- package/templates/packages/main/src/shared/resources/consoleSettingsFields.js +11 -0
- package/test/bootstrapPayloadIntegration.test.js +86 -0
- package/test/consoleActionSurfaces.test.js +24 -0
- package/test/consoleBootstrapContributor.test.js +64 -0
- package/test/consoleRouteRequestInputValidator.test.js +119 -0
- package/test/consoleRouteResources.test.js +72 -0
- package/test/consoleService.test.js +57 -0
- package/test/consoleSettingsService.test.js +86 -0
- package/test/exportsContract.test.js +26 -0
- package/test/registerConsoleCore.test.js +38 -0
- package/test/registerServiceRealtimeEvents.test.js +38 -0
- package/test/repositoryContracts.test.js +28 -0
- package/test/settingsFieldRegistriesSingleton.test.js +14 -0
- package/test-support/registerDefaultSettingsFields.js +1 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export default Object.freeze({
|
|
2
|
+
packageVersion: 1,
|
|
3
|
+
packageId: "@jskit-ai/console-core",
|
|
4
|
+
version: "0.1.1",
|
|
5
|
+
kind: "runtime",
|
|
6
|
+
description: "Console runtime: console settings schema, bootstrap flags, actions, and HTTP routes.",
|
|
7
|
+
dependsOn: [
|
|
8
|
+
"@jskit-ai/database-runtime",
|
|
9
|
+
"@jskit-ai/http-runtime",
|
|
10
|
+
"@jskit-ai/users-core"
|
|
11
|
+
],
|
|
12
|
+
capabilities: {
|
|
13
|
+
provides: [
|
|
14
|
+
"console.core",
|
|
15
|
+
"console.server-routes"
|
|
16
|
+
],
|
|
17
|
+
requires: [
|
|
18
|
+
"runtime.actions",
|
|
19
|
+
"runtime.database",
|
|
20
|
+
"users.core"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
runtime: {
|
|
24
|
+
server: {
|
|
25
|
+
providers: [
|
|
26
|
+
{
|
|
27
|
+
entrypoint: "src/server/ConsoleCoreServiceProvider.js",
|
|
28
|
+
export: "ConsoleCoreServiceProvider"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
client: {
|
|
33
|
+
providers: []
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
metadata: {
|
|
37
|
+
apiSummary: {
|
|
38
|
+
surfaces: [
|
|
39
|
+
{
|
|
40
|
+
subpath: "./server",
|
|
41
|
+
summary: "Exports ConsoleCoreServiceProvider plus console settings services, routes, and action definitions."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
subpath: "./shared",
|
|
45
|
+
summary: "Exports shared console settings resource and field registration helpers."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
subpath: "./client",
|
|
49
|
+
summary: "Exports no runtime API today (reserved client entrypoint)."
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
containerTokens: {
|
|
53
|
+
server: [],
|
|
54
|
+
client: []
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
server: {
|
|
58
|
+
routes: [
|
|
59
|
+
{
|
|
60
|
+
method: "GET",
|
|
61
|
+
path: "/api/console/settings",
|
|
62
|
+
summary: "Get console settings."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
method: "PATCH",
|
|
66
|
+
path: "/api/console/settings",
|
|
67
|
+
summary: "Update console settings."
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
mutations: {
|
|
73
|
+
dependencies: {
|
|
74
|
+
runtime: {
|
|
75
|
+
"@jskit-ai/database-runtime": "0.1.38",
|
|
76
|
+
"@jskit-ai/http-runtime": "0.1.37",
|
|
77
|
+
"@jskit-ai/kernel": "0.1.38",
|
|
78
|
+
"@jskit-ai/users-core": "0.1.48",
|
|
79
|
+
"typebox": "^1.0.81"
|
|
80
|
+
},
|
|
81
|
+
dev: {}
|
|
82
|
+
},
|
|
83
|
+
packageJson: {
|
|
84
|
+
scripts: {}
|
|
85
|
+
},
|
|
86
|
+
procfile: {},
|
|
87
|
+
files: [
|
|
88
|
+
{
|
|
89
|
+
op: "install-migration",
|
|
90
|
+
from: "templates/migrations/console_core_generic_initial.cjs",
|
|
91
|
+
toDir: "migrations",
|
|
92
|
+
extension: ".cjs",
|
|
93
|
+
reason: "Install console settings schema migration.",
|
|
94
|
+
category: "migration",
|
|
95
|
+
id: "console-core-generic-initial-schema"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
from: "templates/packages/main/src/shared/resources/consoleSettingsFields.js",
|
|
99
|
+
to: "packages/main/src/shared/resources/consoleSettingsFields.js",
|
|
100
|
+
preserveOnRemove: true,
|
|
101
|
+
reason: "Install app-owned console settings field definitions.",
|
|
102
|
+
category: "console-core",
|
|
103
|
+
id: "console-core-app-owned-console-settings-fields"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
text: [
|
|
107
|
+
{
|
|
108
|
+
op: "append-text",
|
|
109
|
+
file: "packages/main/src/shared/index.js",
|
|
110
|
+
position: "top",
|
|
111
|
+
skipIfContains: "import \"./resources/consoleSettingsFields.js\";",
|
|
112
|
+
value: "import \"./resources/consoleSettingsFields.js\";\n",
|
|
113
|
+
reason: "Load app-owned console settings field definitions inside the main shared module.",
|
|
114
|
+
category: "console-core",
|
|
115
|
+
id: "console-core-main-shared-console-settings-field-import"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jskit-ai/console-core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "node --test"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
"./shared/resources/consoleSettingsFields": "./src/shared/resources/consoleSettingsFields.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@jskit-ai/database-runtime": "0.1.38",
|
|
13
|
+
"@jskit-ai/http-runtime": "0.1.37",
|
|
14
|
+
"@jskit-ai/kernel": "0.1.38",
|
|
15
|
+
"@jskit-ai/users-core": "0.1.48",
|
|
16
|
+
"typebox": "^1.0.81"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { bootConsoleSettingsRoutes } from "./consoleSettings/bootConsoleSettingsRoutes.js";
|
|
2
|
+
import { registerConsoleCore } from "./registerConsoleCore.js";
|
|
3
|
+
import { registerConsoleBootstrap } from "./registerConsoleBootstrap.js";
|
|
4
|
+
import { registerConsoleSettings } from "./consoleSettings/registerConsoleSettings.js";
|
|
5
|
+
|
|
6
|
+
class ConsoleCoreServiceProvider {
|
|
7
|
+
static id = "console.core";
|
|
8
|
+
|
|
9
|
+
static dependsOn = ["users.core", "runtime.server", "runtime.actions", "runtime.database"];
|
|
10
|
+
|
|
11
|
+
register(app) {
|
|
12
|
+
registerConsoleCore(app);
|
|
13
|
+
registerConsoleBootstrap(app);
|
|
14
|
+
registerConsoleSettings(app);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async boot(app) {
|
|
18
|
+
bootConsoleSettingsRoutes(app);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { ConsoleCoreServiceProvider };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { normalizeObject, normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
|
|
3
|
+
function createConsoleBootstrapContributor({ consoleService } = {}) {
|
|
4
|
+
if (!consoleService || typeof consoleService.ensureInitialConsoleMember !== "function") {
|
|
5
|
+
throw new Error("createConsoleBootstrapContributor requires consoleService.ensureInitialConsoleMember().");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return Object.freeze({
|
|
9
|
+
contributorId: "console.bootstrap",
|
|
10
|
+
order: 300,
|
|
11
|
+
async contribute({ payload = {} } = {}) {
|
|
12
|
+
const normalizedPayload = normalizeObject(payload);
|
|
13
|
+
const session = normalizeObject(normalizedPayload.session);
|
|
14
|
+
const surfaceAccess = normalizeObject(normalizedPayload.surfaceAccess);
|
|
15
|
+
const authenticatedUserId =
|
|
16
|
+
session.authenticated === true ? normalizeRecordId(session.userId, { fallback: null }) : null;
|
|
17
|
+
|
|
18
|
+
let consoleOwner = false;
|
|
19
|
+
if (authenticatedUserId) {
|
|
20
|
+
const seededConsoleOwnerUserId = normalizeRecordId(
|
|
21
|
+
await consoleService.ensureInitialConsoleMember(authenticatedUserId),
|
|
22
|
+
{ fallback: null }
|
|
23
|
+
);
|
|
24
|
+
consoleOwner = Boolean(seededConsoleOwnerUserId) && seededConsoleOwnerUserId === authenticatedUserId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
surfaceAccess: {
|
|
29
|
+
...surfaceAccess,
|
|
30
|
+
consoleowner: consoleOwner
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { createConsoleBootstrapContributor };
|
|
@@ -0,0 +1,63 @@
|
|
|
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 };
|
|
@@ -0,0 +1,36 @@
|
|
|
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 };
|
|
@@ -0,0 +1,55 @@
|
|
|
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 };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeDbRecordId,
|
|
3
|
+
toIsoString,
|
|
4
|
+
createWithTransaction
|
|
5
|
+
} from "@jskit-ai/database-runtime/shared";
|
|
6
|
+
import { toInsertDateTime } from "@jskit-ai/database-runtime/shared";
|
|
7
|
+
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
8
|
+
import { normalizeRecordId as normalizeKernelRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
9
|
+
import { consoleSettingsFields } from "../../shared/resources/consoleSettingsFields.js";
|
|
10
|
+
|
|
11
|
+
function nowDb() {
|
|
12
|
+
return toInsertDateTime();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function mapSettings(row = {}) {
|
|
16
|
+
const settings = {};
|
|
17
|
+
for (const field of consoleSettingsFields) {
|
|
18
|
+
const rawValue = Object.hasOwn(row, field.dbColumn)
|
|
19
|
+
? row[field.dbColumn]
|
|
20
|
+
: field.resolveDefault({
|
|
21
|
+
settings: row
|
|
22
|
+
});
|
|
23
|
+
settings[field.key] = field.normalizeOutput(rawValue, {
|
|
24
|
+
settings: row
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return settings;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function mapSingletonRow(row) {
|
|
31
|
+
if (!row) {
|
|
32
|
+
throw new Error("console_settings singleton row is missing.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ownerUserId = normalizeDbRecordId(row.owner_user_id, { fallback: null });
|
|
36
|
+
return {
|
|
37
|
+
id: normalizeDbRecordId(row.id, { fallback: "1" }),
|
|
38
|
+
ownerUserId,
|
|
39
|
+
settings: mapSettings(row),
|
|
40
|
+
createdAt: toIsoString(row.created_at),
|
|
41
|
+
updatedAt: toIsoString(row.updated_at)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createRepository(knex) {
|
|
46
|
+
if (typeof knex !== "function") {
|
|
47
|
+
throw new TypeError("consoleSettingsRepository requires knex.");
|
|
48
|
+
}
|
|
49
|
+
const withTransaction = createWithTransaction(knex);
|
|
50
|
+
|
|
51
|
+
async function readSingleton(client) {
|
|
52
|
+
return client("console_settings").where({ id: 1 }).first();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function getSingleton(options = {}) {
|
|
56
|
+
const client = options?.trx || knex;
|
|
57
|
+
return mapSingletonRow(await readSingleton(client));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function ensureOwnerUserId(userId, options = {}) {
|
|
61
|
+
const client = options?.trx || knex;
|
|
62
|
+
const candidateOwnerUserId = normalizeKernelRecordId(userId, { fallback: null });
|
|
63
|
+
if (!candidateOwnerUserId) {
|
|
64
|
+
throw new TypeError("consoleSettingsRepository.ensureOwnerUserId requires a positive user id.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const current = mapSingletonRow(await readSingleton(client));
|
|
68
|
+
if (current.ownerUserId) {
|
|
69
|
+
return current.ownerUserId;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await client("console_settings")
|
|
73
|
+
.where({ id: 1 })
|
|
74
|
+
.whereNull("owner_user_id")
|
|
75
|
+
.update({
|
|
76
|
+
owner_user_id: candidateOwnerUserId,
|
|
77
|
+
updated_at: nowDb()
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const reloaded = mapSingletonRow(await readSingleton(client));
|
|
81
|
+
if (!reloaded.ownerUserId) {
|
|
82
|
+
throw new Error("console_settings owner_user_id could not be resolved.");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return reloaded.ownerUserId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function updateSingleton(patch, options = {}) {
|
|
89
|
+
const client = options?.trx || knex;
|
|
90
|
+
const source = normalizeObjectInput(patch);
|
|
91
|
+
const dbPatch = {
|
|
92
|
+
updated_at: nowDb()
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
for (const field of consoleSettingsFields) {
|
|
96
|
+
if (!Object.hasOwn(source, field.key)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
dbPatch[field.dbColumn] = field.normalizeInput(source[field.key], {
|
|
100
|
+
payload: source
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await client("console_settings")
|
|
105
|
+
.where({ id: 1 })
|
|
106
|
+
.update(dbPatch);
|
|
107
|
+
|
|
108
|
+
return getSingleton({ trx: client });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
withTransaction,
|
|
113
|
+
getSingleton,
|
|
114
|
+
ensureOwnerUserId,
|
|
115
|
+
updateSingleton
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { createRepository };
|
|
@@ -0,0 +1,40 @@
|
|
|
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 };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
|
|
2
|
+
import { createService as createConsoleSettingsService } from "./consoleSettingsService.js";
|
|
3
|
+
import { createService as createConsoleService } from "./consoleService.js";
|
|
4
|
+
import { consoleSettingsActions } from "./consoleSettingsActions.js";
|
|
5
|
+
|
|
6
|
+
function registerConsoleSettings(app) {
|
|
7
|
+
if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
|
|
8
|
+
throw new Error("registerConsoleSettings requires application singleton()/service()/actions().");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const hasConsoleService = typeof app.has === "function" ? app.has("consoleService") : false;
|
|
12
|
+
if (!hasConsoleService) {
|
|
13
|
+
app.singleton("consoleService", (scope) =>
|
|
14
|
+
createConsoleService({
|
|
15
|
+
consoleSettingsRepository: scope.make("consoleSettingsRepository")
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
app.service(
|
|
21
|
+
"console.settings.service",
|
|
22
|
+
(scope) =>
|
|
23
|
+
createConsoleSettingsService({
|
|
24
|
+
consoleSettingsRepository: scope.make("consoleSettingsRepository"),
|
|
25
|
+
consoleService: scope.make("consoleService")
|
|
26
|
+
}),
|
|
27
|
+
{
|
|
28
|
+
events: Object.freeze({
|
|
29
|
+
updateSettings: Object.freeze([
|
|
30
|
+
Object.freeze({
|
|
31
|
+
type: "entity.changed",
|
|
32
|
+
source: "console",
|
|
33
|
+
entity: "settings",
|
|
34
|
+
operation: "updated",
|
|
35
|
+
entityId: 1,
|
|
36
|
+
realtime: Object.freeze({
|
|
37
|
+
event: "console.settings.changed",
|
|
38
|
+
audience: "all_users"
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
])
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
app.actions(
|
|
47
|
+
withActionDefaults(consoleSettingsActions, {
|
|
48
|
+
domain: "console",
|
|
49
|
+
dependencies: {
|
|
50
|
+
consoleSettingsService: "console.settings.service"
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { registerConsoleSettings };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerBootstrapPayloadContributor } from "@jskit-ai/kernel/server/runtime";
|
|
2
|
+
import { createConsoleBootstrapContributor } from "./consoleBootstrapContributor.js";
|
|
3
|
+
|
|
4
|
+
function registerConsoleBootstrap(app) {
|
|
5
|
+
if (!app || typeof app.singleton !== "function") {
|
|
6
|
+
throw new Error("registerConsoleBootstrap requires application singleton().");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
registerBootstrapPayloadContributor(app, "console.core.bootstrap.payloadContributor", (scope) => {
|
|
10
|
+
return createConsoleBootstrapContributor({
|
|
11
|
+
consoleService: scope.make("consoleService")
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { registerConsoleBootstrap };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRepository as createConsoleSettingsRepository } from "./consoleSettings/consoleSettingsRepository.js";
|
|
2
|
+
import { registerConsoleCoreActionSurfaceSources } from "./support/consoleActionSurfaces.js";
|
|
3
|
+
|
|
4
|
+
function registerConsoleCore(app) {
|
|
5
|
+
if (!app || typeof app.singleton !== "function") {
|
|
6
|
+
throw new Error("registerConsoleCore requires application singleton().");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
registerConsoleCoreActionSurfaceSources(app);
|
|
10
|
+
|
|
11
|
+
app.singleton("consoleSettingsRepository", (scope) => {
|
|
12
|
+
const knex = scope.make("jskit.database.knex");
|
|
13
|
+
return createConsoleSettingsRepository(knex);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { registerConsoleCore };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
2
|
+
import { isRecord, normalizeLowerText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
|
|
4
|
+
const CONSOLE_OWNER_ACCESS_POLICY_ID = "console_owner";
|
|
5
|
+
|
|
6
|
+
function normalizeSurfaceIds(surfaceIds = []) {
|
|
7
|
+
const source = Array.isArray(surfaceIds) ? surfaceIds : [];
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
const normalized = [];
|
|
10
|
+
|
|
11
|
+
for (const candidate of source) {
|
|
12
|
+
const surfaceId = normalizeSurfaceId(candidate);
|
|
13
|
+
if (!surfaceId || seen.has(surfaceId)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
seen.add(surfaceId);
|
|
17
|
+
normalized.push(surfaceId);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveConsoleSurfaceIdsFromAppConfig(appConfig = {}) {
|
|
24
|
+
const source = isRecord(appConfig?.surfaceDefinitions) ? appConfig.surfaceDefinitions : {};
|
|
25
|
+
const resolved = [];
|
|
26
|
+
|
|
27
|
+
for (const [key, value] of Object.entries(source)) {
|
|
28
|
+
const definition = isRecord(value) ? value : {};
|
|
29
|
+
const surfaceId = normalizeSurfaceId(definition.id || key);
|
|
30
|
+
if (!surfaceId) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (definition.enabled === false) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (definition.requiresWorkspace === true) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (normalizeLowerText(definition.accessPolicyId) !== CONSOLE_OWNER_ACCESS_POLICY_ID) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
resolved.push(surfaceId);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return normalizeSurfaceIds(resolved);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registerConsoleCoreActionSurfaceSources(app) {
|
|
49
|
+
if (!app || typeof app.actionSurfaceSource !== "function") {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
app.actionSurfaceSource("console", ({ scope }) => {
|
|
54
|
+
const appConfig = scope?.has?.("appConfig") ? scope.make("appConfig") : {};
|
|
55
|
+
return resolveConsoleSurfaceIdsFromAppConfig(appConfig);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
resolveConsoleSurfaceIdsFromAppConfig,
|
|
61
|
+
registerConsoleCoreActionSurfaceSources
|
|
62
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function createOperationMessages({
|
|
2
|
+
validationMessage = "Validation failed.",
|
|
3
|
+
apiValidationMessage = validationMessage
|
|
4
|
+
} = {}) {
|
|
5
|
+
const validation = String(validationMessage || "Validation failed.");
|
|
6
|
+
const apiValidation = String(apiValidationMessage || validation || "Validation failed.");
|
|
7
|
+
|
|
8
|
+
return Object.freeze({
|
|
9
|
+
validation,
|
|
10
|
+
apiValidation
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
createOperationMessages
|
|
16
|
+
};
|