@axiom-lattice/gateway 2.1.28 → 2.1.30
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/.turbo/turbo-build.log +12 -8
- package/AGENTS.md +50 -0
- package/CHANGELOG.md +20 -0
- package/dist/chunk-FSASG3SB.mjs +94 -0
- package/dist/chunk-FSASG3SB.mjs.map +1 -0
- package/dist/config-F3FCBSPH.mjs +9 -0
- package/dist/config-F3FCBSPH.mjs.map +1 -0
- package/dist/index.js +1996 -209
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1878 -186
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/controllers/auth.ts +443 -0
- package/src/controllers/database-configs.ts +432 -0
- package/src/controllers/metrics-configs.ts +989 -0
- package/src/controllers/run.ts +6 -0
- package/src/controllers/tenants.ts +121 -0
- package/src/controllers/users.ts +135 -0
- package/src/controllers/workspace.ts +598 -0
- package/src/index.ts +2 -10
- package/src/routes/index.ts +21 -0
- package/src/services/agent_service.ts +71 -5
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configService
|
|
3
|
+
} from "./chunk-FSASG3SB.mjs";
|
|
4
|
+
|
|
1
5
|
// src/index.ts
|
|
2
6
|
import fastify from "fastify";
|
|
3
7
|
import cors from "@fastify/cors";
|
|
@@ -20,6 +24,31 @@ import {
|
|
|
20
24
|
getChunkBuffer,
|
|
21
25
|
hasChunkBuffer
|
|
22
26
|
} from "@axiom-lattice/core";
|
|
27
|
+
async function fetchDatabaseConfigs(baseURL, apiKey, tenantId) {
|
|
28
|
+
try {
|
|
29
|
+
const headers = {};
|
|
30
|
+
if (apiKey) {
|
|
31
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
32
|
+
}
|
|
33
|
+
if (tenantId) {
|
|
34
|
+
headers["x-tenant-id"] = tenantId;
|
|
35
|
+
}
|
|
36
|
+
const response = await fetch(`${baseURL}/api/database-configs`, { headers });
|
|
37
|
+
if (response.ok) {
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
if (data.success && data.data && Array.isArray(data.data.records)) {
|
|
40
|
+
return data.data.records.map((record) => ({
|
|
41
|
+
key: record.key,
|
|
42
|
+
name: record.name,
|
|
43
|
+
description: record.description
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("Failed to fetch database configs:", error);
|
|
49
|
+
}
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
23
52
|
function getOrCreateChunkBuffer() {
|
|
24
53
|
if (!hasChunkBuffer("default")) {
|
|
25
54
|
const buffer = new InMemoryChunkBuffer({
|
|
@@ -37,6 +66,8 @@ async function agent_invoke({
|
|
|
37
66
|
thread_id,
|
|
38
67
|
assistant_id,
|
|
39
68
|
tenant_id,
|
|
69
|
+
workspace_id,
|
|
70
|
+
project_id,
|
|
40
71
|
command,
|
|
41
72
|
run_id
|
|
42
73
|
}) {
|
|
@@ -48,10 +79,19 @@ async function agent_invoke({
|
|
|
48
79
|
if (!runnable_agent) {
|
|
49
80
|
throw new Error(`Agent ${assistant_id} not found`);
|
|
50
81
|
}
|
|
82
|
+
const { configService: configService2 } = await import("./config-F3FCBSPH.mjs");
|
|
83
|
+
const gatewayConfig = configService2.getConfig();
|
|
84
|
+
const databaseConfigs = await fetchDatabaseConfigs(
|
|
85
|
+
gatewayConfig.baseURL || "http://localhost:4001",
|
|
86
|
+
void 0,
|
|
87
|
+
tenant_id
|
|
88
|
+
);
|
|
89
|
+
global.__DATABASE_CONFIGS__ = databaseConfigs;
|
|
51
90
|
const runConfig = {
|
|
52
91
|
...agentLattice?.config?.runConfig || {},
|
|
53
92
|
assistant_id,
|
|
54
|
-
|
|
93
|
+
workspaceId: workspace_id,
|
|
94
|
+
projectId: project_id
|
|
55
95
|
};
|
|
56
96
|
const result = await runnable_agent.invoke(
|
|
57
97
|
command ? new Command(command) : { ...rest, messages, "x-tenant-id": tenant_id },
|
|
@@ -60,11 +100,12 @@ async function agent_invoke({
|
|
|
60
100
|
thread_id,
|
|
61
101
|
run_id: run_id || v4(),
|
|
62
102
|
"x-tenant-id": tenant_id,
|
|
103
|
+
"x-workspace-id": workspace_id,
|
|
104
|
+
"x-project-id": project_id,
|
|
63
105
|
"x-request-id": run_id,
|
|
64
106
|
"x-thread-id": thread_id,
|
|
65
107
|
"x-assistant-id": assistant_id,
|
|
66
108
|
runConfig
|
|
67
|
-
// Inject runConfig for tools to access
|
|
68
109
|
},
|
|
69
110
|
recursionLimit: 200
|
|
70
111
|
}
|
|
@@ -83,6 +124,8 @@ async function agent_stream({
|
|
|
83
124
|
thread_id,
|
|
84
125
|
command,
|
|
85
126
|
tenant_id,
|
|
127
|
+
workspace_id,
|
|
128
|
+
project_id,
|
|
86
129
|
assistant_id,
|
|
87
130
|
run_id
|
|
88
131
|
}) {
|
|
@@ -95,10 +138,19 @@ async function agent_stream({
|
|
|
95
138
|
messages = [humanMessage];
|
|
96
139
|
}
|
|
97
140
|
const chunkBuffer = getOrCreateChunkBuffer();
|
|
141
|
+
const { configService: configService2 } = await import("./config-F3FCBSPH.mjs");
|
|
142
|
+
const gatewayConfig = configService2.getConfig();
|
|
143
|
+
const databaseConfigs = await fetchDatabaseConfigs(
|
|
144
|
+
gatewayConfig.baseURL || "http://localhost:4001",
|
|
145
|
+
void 0,
|
|
146
|
+
tenant_id
|
|
147
|
+
);
|
|
148
|
+
global.__DATABASE_CONFIGS__ = databaseConfigs;
|
|
98
149
|
const runConfig = {
|
|
99
150
|
...agentLattice?.config?.runConfig || {},
|
|
100
151
|
assistant_id,
|
|
101
|
-
|
|
152
|
+
workspaceId: workspace_id,
|
|
153
|
+
projectId: project_id
|
|
102
154
|
};
|
|
103
155
|
try {
|
|
104
156
|
if (!runnable_agent) {
|
|
@@ -115,6 +167,8 @@ async function agent_stream({
|
|
|
115
167
|
thread_id,
|
|
116
168
|
run_id: run_id || v4(),
|
|
117
169
|
"x-tenant-id": tenant_id,
|
|
170
|
+
"x-workspace-id": workspace_id,
|
|
171
|
+
"x-project-id": project_id,
|
|
118
172
|
"x-request-id": run_id,
|
|
119
173
|
"x-thread-id": thread_id,
|
|
120
174
|
"x-assistant-id": assistant_id,
|
|
@@ -432,6 +486,8 @@ var createRun = async (request, reply) => {
|
|
|
432
486
|
...input
|
|
433
487
|
} = request.body;
|
|
434
488
|
const tenant_id = request.headers["x-tenant-id"];
|
|
489
|
+
const workspace_id = request.headers["x-workspace-id"];
|
|
490
|
+
const project_id = request.headers["x-project-id"];
|
|
435
491
|
const x_request_id = request.headers["x-request-id"] || v42();
|
|
436
492
|
if (!assistant_id) {
|
|
437
493
|
reply.status(400).send({
|
|
@@ -447,6 +503,8 @@ var createRun = async (request, reply) => {
|
|
|
447
503
|
thread_id,
|
|
448
504
|
command,
|
|
449
505
|
tenant_id,
|
|
506
|
+
workspace_id,
|
|
507
|
+
project_id,
|
|
450
508
|
run_id: x_request_id
|
|
451
509
|
});
|
|
452
510
|
reply.hijack();
|
|
@@ -474,6 +532,8 @@ var createRun = async (request, reply) => {
|
|
|
474
532
|
command,
|
|
475
533
|
thread_id,
|
|
476
534
|
tenant_id,
|
|
535
|
+
workspace_id,
|
|
536
|
+
project_id,
|
|
477
537
|
run_id: x_request_id
|
|
478
538
|
});
|
|
479
539
|
reply.status(200).send(result);
|
|
@@ -1020,78 +1080,6 @@ async function resumeScheduledTask(request, reply) {
|
|
|
1020
1080
|
}
|
|
1021
1081
|
}
|
|
1022
1082
|
|
|
1023
|
-
// src/config.ts
|
|
1024
|
-
var ConfigService = class {
|
|
1025
|
-
constructor() {
|
|
1026
|
-
this.config = this.loadFromEnv();
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Load configuration from environment variables
|
|
1030
|
-
*/
|
|
1031
|
-
loadFromEnv() {
|
|
1032
|
-
return {
|
|
1033
|
-
port: process.env.PORT ? Number(process.env.PORT) : void 0,
|
|
1034
|
-
queueServiceType: process.env.QUEUE_SERVICE_TYPE,
|
|
1035
|
-
redisUrl: process.env.REDIS_URL,
|
|
1036
|
-
redisPassword: process.env.REDIS_PASSWORD,
|
|
1037
|
-
queueName: process.env.QUEUE_NAME
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Update configuration from JSON object
|
|
1042
|
-
* This will update both the internal config and process.env
|
|
1043
|
-
*/
|
|
1044
|
-
updateConfig(jsonConfig) {
|
|
1045
|
-
for (const [key, value] of Object.entries(jsonConfig)) {
|
|
1046
|
-
if (value !== null && value !== void 0) {
|
|
1047
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
1048
|
-
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
1049
|
-
const envKey = `${key.toUpperCase()}_${nestedKey.toUpperCase()}`;
|
|
1050
|
-
process.env[envKey] = String(nestedValue);
|
|
1051
|
-
}
|
|
1052
|
-
} else {
|
|
1053
|
-
process.env[key.toUpperCase()] = String(value);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
this.config = this.loadFromEnv();
|
|
1058
|
-
this.config = this.deepMerge(this.config, jsonConfig);
|
|
1059
|
-
}
|
|
1060
|
-
/**
|
|
1061
|
-
* Deep merge two objects
|
|
1062
|
-
*/
|
|
1063
|
-
deepMerge(target, source) {
|
|
1064
|
-
const output = { ...target };
|
|
1065
|
-
if (this.isObject(target) && this.isObject(source)) {
|
|
1066
|
-
Object.keys(source).forEach((key) => {
|
|
1067
|
-
if (this.isObject(source[key])) {
|
|
1068
|
-
if (!(key in target)) {
|
|
1069
|
-
Object.assign(output, { [key]: source[key] });
|
|
1070
|
-
} else {
|
|
1071
|
-
output[key] = this.deepMerge(target[key], source[key]);
|
|
1072
|
-
}
|
|
1073
|
-
} else {
|
|
1074
|
-
Object.assign(output, { [key]: source[key] });
|
|
1075
|
-
}
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
return output;
|
|
1079
|
-
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Check if value is a plain object
|
|
1082
|
-
*/
|
|
1083
|
-
isObject(item) {
|
|
1084
|
-
return item && typeof item === "object" && !Array.isArray(item);
|
|
1085
|
-
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Get current configuration
|
|
1088
|
-
*/
|
|
1089
|
-
getConfig() {
|
|
1090
|
-
return { ...this.config };
|
|
1091
|
-
}
|
|
1092
|
-
};
|
|
1093
|
-
var configService = new ConfigService();
|
|
1094
|
-
|
|
1095
1083
|
// src/services/queue_service.ts
|
|
1096
1084
|
import {
|
|
1097
1085
|
queueLatticeManager,
|
|
@@ -2185,8 +2173,8 @@ var sandboxService = new SandboxService();
|
|
|
2185
2173
|
|
|
2186
2174
|
// src/controllers/sandbox.ts
|
|
2187
2175
|
import { getSandBoxManager as getSandBoxManager2 } from "@axiom-lattice/core";
|
|
2188
|
-
function getFilenameFromPath(
|
|
2189
|
-
const segments =
|
|
2176
|
+
function getFilenameFromPath(path2) {
|
|
2177
|
+
const segments = path2.replace(/\/+$/, "").split("/");
|
|
2190
2178
|
return segments[segments.length - 1] || "download";
|
|
2191
2179
|
}
|
|
2192
2180
|
var EXT_TO_MIME = {
|
|
@@ -2237,10 +2225,10 @@ function registerSandboxProxyRoutes(app2) {
|
|
|
2237
2225
|
const pathValue = pathEntry && typeof pathEntry === "object" && "value" in pathEntry ? String(pathEntry.value) : typeof pathEntry === "string" ? pathEntry : void 0;
|
|
2238
2226
|
const formData = new FormData();
|
|
2239
2227
|
formData.append("file", new Blob([buffer]), data.filename ?? "file");
|
|
2240
|
-
const
|
|
2228
|
+
const path2 = `/home/gem/uploads/${pathValue ? pathValue : ""}${data.filename}`;
|
|
2241
2229
|
const uploadResult = await sandbox.file.uploadFile({
|
|
2242
2230
|
file: buffer,
|
|
2243
|
-
path
|
|
2231
|
+
path: path2
|
|
2244
2232
|
});
|
|
2245
2233
|
if (!uploadResult.ok) {
|
|
2246
2234
|
return reply.status(502).send({ error: `Upload error: ${uploadResult.error}` });
|
|
@@ -2323,125 +2311,1837 @@ function registerSandboxProxyRoutes(app2) {
|
|
|
2323
2311
|
);
|
|
2324
2312
|
}
|
|
2325
2313
|
|
|
2326
|
-
// src/
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
"
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
)
|
|
2314
|
+
// src/controllers/workspace.ts
|
|
2315
|
+
import * as fs from "fs/promises";
|
|
2316
|
+
import * as path from "path";
|
|
2317
|
+
import { Readable as Readable2 } from "stream";
|
|
2318
|
+
import { getStoreLattice as getStoreLattice5 } from "@axiom-lattice/core";
|
|
2319
|
+
import { SandboxFilesystem, FilesystemBackend } from "@axiom-lattice/core";
|
|
2320
|
+
import { getSandBoxManager as getSandBoxManager3 } from "@axiom-lattice/core";
|
|
2321
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2322
|
+
var WorkspaceController = class {
|
|
2323
|
+
constructor() {
|
|
2324
|
+
this.workspaceStore = getStoreLattice5("default", "workspace").store;
|
|
2325
|
+
this.projectStore = getStoreLattice5("default", "project").store;
|
|
2326
|
+
}
|
|
2327
|
+
getTenantId(request) {
|
|
2328
|
+
return request.headers["x-tenant-id"] || "default";
|
|
2329
|
+
}
|
|
2330
|
+
// ==================== Workspace CRUD ====================
|
|
2331
|
+
async listWorkspaces(request, reply) {
|
|
2332
|
+
const tenantId = this.getTenantId(request);
|
|
2333
|
+
const workspaces = await this.workspaceStore.getAllWorkspaces(tenantId);
|
|
2334
|
+
return { success: true, data: workspaces };
|
|
2335
|
+
}
|
|
2336
|
+
async createWorkspace(request, reply) {
|
|
2337
|
+
const tenantId = this.getTenantId(request);
|
|
2338
|
+
const data = request.body;
|
|
2339
|
+
const id = uuidv4();
|
|
2340
|
+
const workspace = await this.workspaceStore.createWorkspace(
|
|
2341
|
+
tenantId,
|
|
2342
|
+
id,
|
|
2343
|
+
data
|
|
2344
|
+
);
|
|
2345
|
+
return reply.status(201).send({ success: true, data: workspace });
|
|
2346
|
+
}
|
|
2347
|
+
async getWorkspace(request, reply) {
|
|
2348
|
+
const tenantId = this.getTenantId(request);
|
|
2349
|
+
const { workspaceId } = request.params;
|
|
2350
|
+
const workspace = await this.workspaceStore.getWorkspaceById(
|
|
2351
|
+
tenantId,
|
|
2352
|
+
workspaceId
|
|
2353
|
+
);
|
|
2354
|
+
if (!workspace) {
|
|
2355
|
+
return reply.status(404).send({ success: false, error: "Workspace not found" });
|
|
2356
|
+
}
|
|
2357
|
+
return { success: true, data: workspace };
|
|
2358
|
+
}
|
|
2359
|
+
async updateWorkspace(request, reply) {
|
|
2360
|
+
const tenantId = this.getTenantId(request);
|
|
2361
|
+
const { workspaceId } = request.params;
|
|
2362
|
+
const updates = request.body;
|
|
2363
|
+
const workspace = await this.workspaceStore.updateWorkspace(
|
|
2364
|
+
tenantId,
|
|
2365
|
+
workspaceId,
|
|
2366
|
+
updates
|
|
2367
|
+
);
|
|
2368
|
+
if (!workspace) {
|
|
2369
|
+
return reply.status(404).send({ success: false, error: "Workspace not found" });
|
|
2370
|
+
}
|
|
2371
|
+
return { success: true, data: workspace };
|
|
2372
|
+
}
|
|
2373
|
+
async deleteWorkspace(request, reply) {
|
|
2374
|
+
const tenantId = this.getTenantId(request);
|
|
2375
|
+
const { workspaceId } = request.params;
|
|
2376
|
+
const deleted = await this.workspaceStore.deleteWorkspace(
|
|
2377
|
+
tenantId,
|
|
2378
|
+
workspaceId
|
|
2379
|
+
);
|
|
2380
|
+
if (!deleted) {
|
|
2381
|
+
return reply.status(404).send({ success: false, error: "Workspace not found" });
|
|
2382
|
+
}
|
|
2383
|
+
return { success: true };
|
|
2384
|
+
}
|
|
2385
|
+
// ==================== Project CRUD ====================
|
|
2386
|
+
async listProjects(request, reply) {
|
|
2387
|
+
const tenantId = this.getTenantId(request);
|
|
2388
|
+
const { workspaceId } = request.params;
|
|
2389
|
+
const projects = await this.projectStore.getProjectsByWorkspace(
|
|
2390
|
+
tenantId,
|
|
2391
|
+
workspaceId
|
|
2392
|
+
);
|
|
2393
|
+
return { success: true, data: projects };
|
|
2394
|
+
}
|
|
2395
|
+
async createProject(request, reply) {
|
|
2396
|
+
const tenantId = this.getTenantId(request);
|
|
2397
|
+
const { workspaceId } = request.params;
|
|
2398
|
+
const data = request.body;
|
|
2399
|
+
const id = uuidv4();
|
|
2400
|
+
const project = await this.projectStore.createProject(
|
|
2401
|
+
tenantId,
|
|
2402
|
+
workspaceId,
|
|
2403
|
+
id,
|
|
2404
|
+
data
|
|
2405
|
+
);
|
|
2406
|
+
return reply.status(201).send({ success: true, data: project });
|
|
2407
|
+
}
|
|
2408
|
+
async getProject(request, reply) {
|
|
2409
|
+
const tenantId = this.getTenantId(request);
|
|
2410
|
+
const { projectId } = request.params;
|
|
2411
|
+
const project = await this.projectStore.getProjectById(tenantId, projectId);
|
|
2412
|
+
if (!project) {
|
|
2413
|
+
return reply.status(404).send({ success: false, error: "Project not found" });
|
|
2414
|
+
}
|
|
2415
|
+
return { success: true, data: project };
|
|
2416
|
+
}
|
|
2417
|
+
async updateProject(request, reply) {
|
|
2418
|
+
const tenantId = this.getTenantId(request);
|
|
2419
|
+
const { projectId } = request.params;
|
|
2420
|
+
const updates = request.body;
|
|
2421
|
+
const project = await this.projectStore.updateProject(
|
|
2422
|
+
tenantId,
|
|
2423
|
+
projectId,
|
|
2424
|
+
updates
|
|
2425
|
+
);
|
|
2426
|
+
if (!project) {
|
|
2427
|
+
return reply.status(404).send({ success: false, error: "Project not found" });
|
|
2428
|
+
}
|
|
2429
|
+
return { success: true, data: project };
|
|
2430
|
+
}
|
|
2431
|
+
async deleteProject(request, reply) {
|
|
2432
|
+
const tenantId = this.getTenantId(request);
|
|
2433
|
+
const { projectId } = request.params;
|
|
2434
|
+
const deleted = await this.projectStore.deleteProject(tenantId, projectId);
|
|
2435
|
+
if (!deleted) {
|
|
2436
|
+
return reply.status(404).send({ success: false, error: "Project not found" });
|
|
2437
|
+
}
|
|
2438
|
+
return { success: true };
|
|
2439
|
+
}
|
|
2440
|
+
// ==================== File Operations ====================
|
|
2441
|
+
async getBackend(workspaceId, projectId) {
|
|
2442
|
+
const tenantId = "default";
|
|
2443
|
+
const workspace = await this.workspaceStore.getWorkspaceById(
|
|
2444
|
+
tenantId,
|
|
2445
|
+
workspaceId
|
|
2446
|
+
);
|
|
2447
|
+
if (!workspace) {
|
|
2448
|
+
throw new Error("Workspace not found");
|
|
2449
|
+
}
|
|
2450
|
+
if (workspace.storageType === "sandbox") {
|
|
2451
|
+
const sandboxManager = getSandBoxManager3("default");
|
|
2452
|
+
const sandboxName = "global";
|
|
2453
|
+
const sandbox = await sandboxManager.createSandbox(sandboxName);
|
|
2454
|
+
return { backend: new SandboxFilesystem({
|
|
2455
|
+
sandboxInstance: sandbox,
|
|
2456
|
+
workingDirectory: `/workspaces/${workspaceId}/${projectId}`
|
|
2457
|
+
}), workspace };
|
|
2458
|
+
} else {
|
|
2459
|
+
return { backend: new FilesystemBackend({
|
|
2460
|
+
rootDir: `/lattice_store/workspaces/${workspaceId}/${projectId}`,
|
|
2461
|
+
virtualMode: true
|
|
2462
|
+
}), workspace };
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
getFilenameFromPath(filePath) {
|
|
2466
|
+
const segments = filePath.split("/");
|
|
2467
|
+
return segments[segments.length - 1] || "download";
|
|
2468
|
+
}
|
|
2469
|
+
getMimeType(filename) {
|
|
2470
|
+
const ext = filename.split(".").pop()?.toLowerCase() || "";
|
|
2471
|
+
const mimeTypes = {
|
|
2472
|
+
pdf: "application/pdf",
|
|
2473
|
+
csv: "text/csv",
|
|
2474
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
2475
|
+
xls: "application/vnd.ms-excel",
|
|
2476
|
+
txt: "text/plain",
|
|
2477
|
+
json: "application/json",
|
|
2478
|
+
png: "image/png",
|
|
2479
|
+
jpg: "image/jpeg",
|
|
2480
|
+
jpeg: "image/jpeg",
|
|
2481
|
+
gif: "image/gif",
|
|
2482
|
+
svg: "image/svg+xml",
|
|
2483
|
+
html: "text/html",
|
|
2484
|
+
htm: "text/html",
|
|
2485
|
+
mp3: "audio/mpeg",
|
|
2486
|
+
mp4: "video/mp4",
|
|
2487
|
+
webm: "video/webm"
|
|
2488
|
+
};
|
|
2489
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
2490
|
+
}
|
|
2491
|
+
async downloadFile(request, reply) {
|
|
2492
|
+
const { workspaceId, projectId } = request.params;
|
|
2493
|
+
const filePath = request.query.path;
|
|
2494
|
+
if (!filePath) {
|
|
2495
|
+
return reply.status(400).send({ success: false, error: "Path is required" });
|
|
2496
|
+
}
|
|
2497
|
+
try {
|
|
2498
|
+
const { workspace } = await this.getBackend(workspaceId, projectId);
|
|
2499
|
+
const resolvedPath = filePath.startsWith("/") ? filePath : `/${filePath}`;
|
|
2500
|
+
if (workspace.storageType === "sandbox") {
|
|
2501
|
+
const sandboxManager = getSandBoxManager3("default");
|
|
2502
|
+
const sandbox = await sandboxManager.createSandbox("global");
|
|
2503
|
+
const realPath = path.join("/home/gem/workspaces", workspaceId, projectId, resolvedPath);
|
|
2504
|
+
const filename2 = this.getFilenameFromPath(resolvedPath);
|
|
2505
|
+
const inferredContentType = this.getMimeType(filename2);
|
|
2506
|
+
const downloadResult = await sandbox.file.downloadFile({
|
|
2507
|
+
path: realPath
|
|
2508
|
+
});
|
|
2509
|
+
if (!downloadResult.ok) {
|
|
2510
|
+
return reply.status(502).send({
|
|
2511
|
+
success: false,
|
|
2512
|
+
error: `Download error: ${JSON.stringify(downloadResult.error)}`
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
const body = downloadResult.body;
|
|
2516
|
+
if (typeof body?.stream === "function") {
|
|
2517
|
+
const webStream = body.stream();
|
|
2518
|
+
const nodeStream = Readable2.fromWeb(webStream);
|
|
2519
|
+
const contentType2 = body.contentType ?? inferredContentType;
|
|
2520
|
+
const contentDisposition2 = body.contentDisposition ?? `inline; filename*=UTF-8''${encodeURIComponent(filename2)}`;
|
|
2521
|
+
return reply.status(200).type(contentType2).header("Content-Disposition", contentDisposition2).send(nodeStream);
|
|
2522
|
+
}
|
|
2523
|
+
const bodyUnknown = downloadResult.body;
|
|
2524
|
+
let buf;
|
|
2525
|
+
let contentType = inferredContentType;
|
|
2526
|
+
let contentDisposition = `inline; filename*=UTF-8''${encodeURIComponent(filename2)}`;
|
|
2527
|
+
if (bodyUnknown instanceof ArrayBuffer) {
|
|
2528
|
+
buf = Buffer.from(bodyUnknown);
|
|
2529
|
+
} else if (bodyUnknown instanceof Buffer) {
|
|
2530
|
+
buf = bodyUnknown;
|
|
2531
|
+
} else if (bodyUnknown && typeof bodyUnknown.arrayBuffer === "function") {
|
|
2532
|
+
const res = bodyUnknown;
|
|
2533
|
+
buf = Buffer.from(await res.arrayBuffer());
|
|
2534
|
+
if (res.headers?.get("content-type")) contentType = res.headers.get("content-type");
|
|
2535
|
+
if (res.headers?.get("content-disposition")) contentDisposition = res.headers.get("content-disposition");
|
|
2536
|
+
} else if (bodyUnknown && typeof bodyUnknown.blob === "function") {
|
|
2537
|
+
const blob = await bodyUnknown.blob();
|
|
2538
|
+
buf = Buffer.from(await blob.arrayBuffer());
|
|
2539
|
+
} else {
|
|
2540
|
+
return reply.status(502).send({ success: false, error: "Unexpected download response format" });
|
|
2541
|
+
}
|
|
2542
|
+
return reply.status(200).type(contentType).header("Content-Disposition", contentDisposition).send(buf);
|
|
2543
|
+
}
|
|
2544
|
+
const { backend } = await this.getBackend(workspaceId, projectId);
|
|
2545
|
+
const content = await backend.read(resolvedPath, 0, Infinity);
|
|
2546
|
+
const filename = this.getFilenameFromPath(resolvedPath);
|
|
2547
|
+
const mimeType = this.getMimeType(filename);
|
|
2548
|
+
const buffer = Buffer.from(content, "utf-8");
|
|
2549
|
+
return reply.status(200).type(mimeType).header("Content-Disposition", `inline; filename*=UTF-8''${encodeURIComponent(filename)}`).send(buffer);
|
|
2550
|
+
} catch (error) {
|
|
2551
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2552
|
+
return reply.status(502).send({ success: false, error: `Download proxy error: ${message}` });
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
async listPath(request) {
|
|
2556
|
+
const { workspaceId, projectId } = request.params;
|
|
2557
|
+
const path2 = request.query.path || "/";
|
|
2558
|
+
const { backend } = await this.getBackend(workspaceId, projectId);
|
|
2559
|
+
const files = await backend.lsInfo(path2);
|
|
2560
|
+
return { success: true, data: files };
|
|
2561
|
+
}
|
|
2562
|
+
async readFile(request) {
|
|
2563
|
+
const { workspaceId, projectId } = request.params;
|
|
2564
|
+
const { path: path2, offset = 0, limit = 1e3 } = request.query;
|
|
2565
|
+
const { backend } = await this.getBackend(workspaceId, projectId);
|
|
2566
|
+
const content = await backend.read(path2, Number(offset), Number(limit));
|
|
2567
|
+
return { success: true, data: { content, offset, limit } };
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Upload a file to the workspace project storage.
|
|
2571
|
+
*
|
|
2572
|
+
* Route: POST /api/workspaces/:workspaceId/projects/:projectId/uploadfile
|
|
2573
|
+
* Accepts multipart/form-data with:
|
|
2574
|
+
* - `file`: the file to upload (required)
|
|
2575
|
+
* - `path`: optional directory path relative to project root (e.g., "docs", "src/utils")
|
|
2576
|
+
* For sandbox storage, delegates to the sandbox upload API.
|
|
2577
|
+
* For filesystem storage, writes directly under /lattice_store/workspaces/{workspaceId}/{projectId}.
|
|
2578
|
+
*/
|
|
2579
|
+
async uploadFile(request, reply) {
|
|
2580
|
+
const tenantId = this.getTenantId(request);
|
|
2581
|
+
const { workspaceId, projectId } = request.params;
|
|
2582
|
+
const workspace = await this.workspaceStore.getWorkspaceById(
|
|
2583
|
+
tenantId,
|
|
2584
|
+
workspaceId
|
|
2585
|
+
);
|
|
2586
|
+
if (!workspace) {
|
|
2587
|
+
return reply.status(404).send({ success: false, error: "Workspace not found" });
|
|
2588
|
+
}
|
|
2589
|
+
try {
|
|
2590
|
+
const data = await request.file();
|
|
2591
|
+
if (!data) {
|
|
2592
|
+
return reply.status(400).send({ success: false, error: "No file in request" });
|
|
2593
|
+
}
|
|
2594
|
+
const buffer = await data.toBuffer();
|
|
2595
|
+
const filename = data.filename || "file";
|
|
2596
|
+
const pathEntry = data.fields?.path;
|
|
2597
|
+
const pathValue = pathEntry && typeof pathEntry === "object" && "value" in pathEntry ? String(pathEntry.value) : typeof pathEntry === "string" ? pathEntry : void 0;
|
|
2598
|
+
if (pathValue && !/^[a-zA-Z0-9_./-]+$/.test(pathValue)) {
|
|
2599
|
+
return reply.status(400).send({ success: false, error: "Invalid path parameter" });
|
|
2600
|
+
}
|
|
2601
|
+
if (workspace.storageType === "sandbox") {
|
|
2602
|
+
const sandboxManager = getSandBoxManager3("default");
|
|
2603
|
+
const sandboxName = "global";
|
|
2604
|
+
const sandbox = await sandboxManager.createSandbox(sandboxName);
|
|
2605
|
+
const baseDir = path.join("/home/gem/workspaces", workspaceId, projectId);
|
|
2606
|
+
const realPath = pathValue ? path.join(baseDir, pathValue, filename) : path.join(baseDir, filename);
|
|
2607
|
+
const uploadResult = await sandbox.file.uploadFile({
|
|
2608
|
+
file: buffer,
|
|
2609
|
+
path: realPath
|
|
2610
|
+
}, { timeoutInSeconds: 300 });
|
|
2611
|
+
if (!uploadResult.ok) {
|
|
2612
|
+
return reply.status(502).send({
|
|
2613
|
+
success: false,
|
|
2614
|
+
error: `Upload error: ${JSON.stringify(uploadResult.error)}`
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
const relativePath = uploadResult.body?.data?.file_path?.replace(path.join("/home/gem/workspaces", workspaceId, projectId), "") || (pathValue ? `/${pathValue}/${filename}` : `/${filename}`);
|
|
2618
|
+
const result2 = {
|
|
2619
|
+
path: relativePath,
|
|
2620
|
+
name: filename,
|
|
2621
|
+
is_dir: false,
|
|
2622
|
+
size: buffer.length,
|
|
2623
|
+
modified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2624
|
+
};
|
|
2625
|
+
return reply.status(200).send({ success: true, data: result2 });
|
|
2626
|
+
}
|
|
2627
|
+
const rootDir = `/lattice_store/workspaces/${workspaceId}/${projectId}`;
|
|
2628
|
+
const targetDir = pathValue ? path.join(rootDir, pathValue) : rootDir;
|
|
2629
|
+
const targetPath = path.join(targetDir, filename);
|
|
2630
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
2631
|
+
await fs.writeFile(targetPath, buffer);
|
|
2632
|
+
const stat2 = await fs.stat(targetPath);
|
|
2633
|
+
const result = {
|
|
2634
|
+
path: pathValue ? `/${pathValue}/${filename}` : `/${filename}`,
|
|
2635
|
+
name: filename,
|
|
2636
|
+
is_dir: false,
|
|
2637
|
+
size: stat2.size,
|
|
2638
|
+
modified_at: stat2.mtime.toISOString()
|
|
2639
|
+
};
|
|
2640
|
+
return reply.status(200).send({ success: true, data: result });
|
|
2641
|
+
} catch (error) {
|
|
2642
|
+
return reply.status(500).send({
|
|
2643
|
+
success: false,
|
|
2644
|
+
error: `Upload handler error: ${error?.message || String(error)}`
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
function registerWorkspaceRoutes(app2) {
|
|
2650
|
+
const controller = new WorkspaceController();
|
|
2651
|
+
app2.get("/api/workspaces", controller.listWorkspaces.bind(controller));
|
|
2652
|
+
app2.post("/api/workspaces", controller.createWorkspace.bind(controller));
|
|
2340
2653
|
app2.get(
|
|
2341
|
-
"/api/
|
|
2342
|
-
|
|
2343
|
-
getMemoryItem
|
|
2344
|
-
);
|
|
2345
|
-
app2.put(
|
|
2346
|
-
"/api/assistants/:assistantId/memory/:key",
|
|
2347
|
-
{ schema: setMemoryItemSchema },
|
|
2348
|
-
setMemoryItem
|
|
2654
|
+
"/api/workspaces/:workspaceId",
|
|
2655
|
+
controller.getWorkspace.bind(controller)
|
|
2349
2656
|
);
|
|
2350
|
-
app2.
|
|
2351
|
-
"/api/
|
|
2352
|
-
|
|
2353
|
-
deleteMemoryItem
|
|
2657
|
+
app2.patch(
|
|
2658
|
+
"/api/workspaces/:workspaceId",
|
|
2659
|
+
controller.updateWorkspace.bind(controller)
|
|
2354
2660
|
);
|
|
2355
2661
|
app2.delete(
|
|
2356
|
-
"/api/
|
|
2357
|
-
|
|
2358
|
-
clearMemory
|
|
2662
|
+
"/api/workspaces/:workspaceId",
|
|
2663
|
+
controller.deleteWorkspace.bind(controller)
|
|
2359
2664
|
);
|
|
2360
|
-
app2.get("/api/assistants", getAssistantList);
|
|
2361
|
-
app2.get("/api/assistants/:id", getAssistant);
|
|
2362
|
-
app2.post("/api/assistants", createAssistant);
|
|
2363
|
-
app2.put("/api/assistants/:id", updateAssistant);
|
|
2364
|
-
app2.delete("/api/assistants/:id", deleteAssistant);
|
|
2365
2665
|
app2.get(
|
|
2366
|
-
"/api/
|
|
2367
|
-
|
|
2368
|
-
getAgentGraph
|
|
2666
|
+
"/api/workspaces/:workspaceId/projects",
|
|
2667
|
+
controller.listProjects.bind(controller)
|
|
2369
2668
|
);
|
|
2370
2669
|
app2.post(
|
|
2371
|
-
"/api/
|
|
2372
|
-
|
|
2373
|
-
triggerAgentTask
|
|
2670
|
+
"/api/workspaces/:workspaceId/projects",
|
|
2671
|
+
controller.createProject.bind(controller)
|
|
2374
2672
|
);
|
|
2375
|
-
app2.get("/api/assistants/:assistantId/threads", getThreadList);
|
|
2376
2673
|
app2.get(
|
|
2377
|
-
"/api/
|
|
2378
|
-
|
|
2674
|
+
"/api/workspaces/:workspaceId/projects/:projectId",
|
|
2675
|
+
controller.getProject.bind(controller)
|
|
2379
2676
|
);
|
|
2380
|
-
app2.
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
updateThread
|
|
2677
|
+
app2.patch(
|
|
2678
|
+
"/api/workspaces/:workspaceId/projects/:projectId",
|
|
2679
|
+
controller.updateProject.bind(controller)
|
|
2384
2680
|
);
|
|
2385
2681
|
app2.delete(
|
|
2386
|
-
"/api/
|
|
2387
|
-
|
|
2388
|
-
);
|
|
2389
|
-
app2.get(
|
|
2390
|
-
"/api/config",
|
|
2391
|
-
{ schema: getConfigSchema },
|
|
2392
|
-
getConfig
|
|
2393
|
-
);
|
|
2394
|
-
app2.put(
|
|
2395
|
-
"/api/config",
|
|
2396
|
-
{ schema: updateConfigSchema },
|
|
2397
|
-
updateConfig
|
|
2398
|
-
);
|
|
2399
|
-
app2.get("/api/models", getModels);
|
|
2400
|
-
app2.put("/api/models", updateModels);
|
|
2401
|
-
app2.get("/api/tools", getToolConfigs);
|
|
2402
|
-
app2.get(
|
|
2403
|
-
"/health",
|
|
2404
|
-
{ schema: getHealthSchema },
|
|
2405
|
-
getHealth
|
|
2682
|
+
"/api/workspaces/:workspaceId/projects/:projectId",
|
|
2683
|
+
controller.deleteProject.bind(controller)
|
|
2406
2684
|
);
|
|
2407
2685
|
app2.get(
|
|
2408
|
-
"/api/
|
|
2409
|
-
|
|
2686
|
+
"/api/workspaces/:workspaceId/projects/:projectId/listpath",
|
|
2687
|
+
controller.listPath.bind(controller)
|
|
2410
2688
|
);
|
|
2411
|
-
app2.get("/api/schedules/:taskId", getScheduledTask);
|
|
2412
|
-
app2.post("/api/schedules/:taskId/cancel", cancelScheduledTask);
|
|
2413
|
-
app2.post("/api/schedules/:taskId/pause", pauseScheduledTask);
|
|
2414
|
-
app2.post("/api/schedules/:taskId/resume", resumeScheduledTask);
|
|
2415
|
-
app2.get("/api/skills", getSkillList);
|
|
2416
2689
|
app2.get(
|
|
2417
|
-
"/api/
|
|
2418
|
-
|
|
2690
|
+
"/api/workspaces/:workspaceId/projects/:projectId/readfile",
|
|
2691
|
+
controller.readFile.bind(controller)
|
|
2419
2692
|
);
|
|
2420
2693
|
app2.post(
|
|
2421
|
-
"/api/
|
|
2422
|
-
|
|
2423
|
-
);
|
|
2424
|
-
app2.put(
|
|
2425
|
-
"/api/skills/:id",
|
|
2426
|
-
updateSkill
|
|
2427
|
-
);
|
|
2428
|
-
app2.delete(
|
|
2429
|
-
"/api/skills/:id",
|
|
2430
|
-
deleteSkill
|
|
2431
|
-
);
|
|
2432
|
-
app2.get(
|
|
2433
|
-
"/api/skills/search/metadata",
|
|
2434
|
-
searchSkillsByMetadata
|
|
2435
|
-
);
|
|
2436
|
-
app2.get(
|
|
2437
|
-
"/api/skills/filter/compatibility",
|
|
2438
|
-
filterSkillsByCompatibility
|
|
2694
|
+
"/api/workspaces/:workspaceId/projects/:projectId/uploadfile",
|
|
2695
|
+
controller.uploadFile.bind(controller)
|
|
2439
2696
|
);
|
|
2440
2697
|
app2.get(
|
|
2441
|
-
"/api/
|
|
2442
|
-
|
|
2698
|
+
"/api/workspaces/:workspaceId/projects/:projectId/downloadfile",
|
|
2699
|
+
controller.downloadFile.bind(controller)
|
|
2443
2700
|
);
|
|
2444
|
-
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// src/controllers/database-configs.ts
|
|
2704
|
+
import {
|
|
2705
|
+
getStoreLattice as getStoreLattice6,
|
|
2706
|
+
sqlDatabaseManager
|
|
2707
|
+
} from "@axiom-lattice/core";
|
|
2708
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2709
|
+
function getTenantId(request) {
|
|
2710
|
+
return request.headers["x-tenant-id"] || "default";
|
|
2711
|
+
}
|
|
2712
|
+
async function getDatabaseConfigList(request, reply) {
|
|
2713
|
+
const tenantId = getTenantId(request);
|
|
2714
|
+
try {
|
|
2715
|
+
const storeLattice = getStoreLattice6("default", "database");
|
|
2716
|
+
const store = storeLattice.store;
|
|
2717
|
+
const configs = await store.getAllConfigs(tenantId);
|
|
2718
|
+
console.log("Backend: getAllConfigs returned:", configs);
|
|
2719
|
+
if (configs.length > 0) {
|
|
2720
|
+
console.log("Backend: First config key:", configs[0].key);
|
|
2721
|
+
}
|
|
2722
|
+
return {
|
|
2723
|
+
success: true,
|
|
2724
|
+
message: "Database configurations retrieved successfully",
|
|
2725
|
+
data: {
|
|
2726
|
+
records: configs,
|
|
2727
|
+
total: configs.length
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
console.error("Failed to get database configs:", error);
|
|
2732
|
+
return {
|
|
2733
|
+
success: false,
|
|
2734
|
+
message: "Failed to retrieve database configurations",
|
|
2735
|
+
data: {
|
|
2736
|
+
records: [],
|
|
2737
|
+
total: 0
|
|
2738
|
+
}
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
async function getDatabaseConfig(request, reply) {
|
|
2743
|
+
const tenantId = getTenantId(request);
|
|
2744
|
+
const { key } = request.params;
|
|
2745
|
+
try {
|
|
2746
|
+
const storeLattice = getStoreLattice6("default", "database");
|
|
2747
|
+
const store = storeLattice.store;
|
|
2748
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
2749
|
+
if (!config) {
|
|
2750
|
+
return {
|
|
2751
|
+
success: false,
|
|
2752
|
+
message: "Database configuration not found"
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
return {
|
|
2756
|
+
success: true,
|
|
2757
|
+
message: "Database configuration retrieved successfully",
|
|
2758
|
+
data: config
|
|
2759
|
+
};
|
|
2760
|
+
} catch (error) {
|
|
2761
|
+
console.error("Failed to get database config:", error);
|
|
2762
|
+
return {
|
|
2763
|
+
success: false,
|
|
2764
|
+
message: "Failed to retrieve database configuration"
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
async function createDatabaseConfig(request, reply) {
|
|
2769
|
+
const tenantId = getTenantId(request);
|
|
2770
|
+
const body = request.body;
|
|
2771
|
+
try {
|
|
2772
|
+
const storeLattice = getStoreLattice6("default", "database");
|
|
2773
|
+
const store = storeLattice.store;
|
|
2774
|
+
const existing = await store.getConfigByKey(tenantId, body.key);
|
|
2775
|
+
if (existing) {
|
|
2776
|
+
reply.code(409);
|
|
2777
|
+
return {
|
|
2778
|
+
success: false,
|
|
2779
|
+
message: "Database configuration with this key already exists"
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
const id = body.id || randomUUID3();
|
|
2783
|
+
const config = await store.createConfig(tenantId, id, body);
|
|
2784
|
+
try {
|
|
2785
|
+
sqlDatabaseManager.registerDatabase(config.key, config.config);
|
|
2786
|
+
} catch (error) {
|
|
2787
|
+
console.warn("Failed to auto-register database:", error);
|
|
2788
|
+
}
|
|
2789
|
+
reply.code(201);
|
|
2790
|
+
return {
|
|
2791
|
+
success: true,
|
|
2792
|
+
message: "Database configuration created successfully",
|
|
2793
|
+
data: config
|
|
2794
|
+
};
|
|
2795
|
+
} catch (error) {
|
|
2796
|
+
console.error("Failed to create database config:", error);
|
|
2797
|
+
return {
|
|
2798
|
+
success: false,
|
|
2799
|
+
message: "Failed to create database configuration"
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
async function updateDatabaseConfig(request, reply) {
|
|
2804
|
+
const tenantId = getTenantId(request);
|
|
2805
|
+
const { key } = request.params;
|
|
2806
|
+
const updates = request.body;
|
|
2807
|
+
try {
|
|
2808
|
+
const storeLattice = getStoreLattice6("default", "database");
|
|
2809
|
+
const store = storeLattice.store;
|
|
2810
|
+
const existing = await store.getConfigByKey(tenantId, key);
|
|
2811
|
+
if (!existing) {
|
|
2812
|
+
reply.code(404);
|
|
2813
|
+
return {
|
|
2814
|
+
success: false,
|
|
2815
|
+
message: "Database configuration not found"
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
const updated = await store.updateConfig(tenantId, existing.id, updates);
|
|
2819
|
+
if (!updated) {
|
|
2820
|
+
return {
|
|
2821
|
+
success: false,
|
|
2822
|
+
message: "Failed to update database configuration"
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
if (updates.config) {
|
|
2826
|
+
try {
|
|
2827
|
+
sqlDatabaseManager.registerDatabase(updated.key, updated.config);
|
|
2828
|
+
} catch (error) {
|
|
2829
|
+
console.warn("Failed to re-register database:", error);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return {
|
|
2833
|
+
success: true,
|
|
2834
|
+
message: "Database configuration updated successfully",
|
|
2835
|
+
data: updated
|
|
2836
|
+
};
|
|
2837
|
+
} catch (error) {
|
|
2838
|
+
console.error("Failed to update database config:", error);
|
|
2839
|
+
return {
|
|
2840
|
+
success: false,
|
|
2841
|
+
message: "Failed to update database configuration"
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
async function deleteDatabaseConfig(request, reply) {
|
|
2846
|
+
const tenantId = getTenantId(request);
|
|
2847
|
+
const { keyOrId } = request.params;
|
|
2848
|
+
try {
|
|
2849
|
+
const storeLattice = getStoreLattice6("default", "database");
|
|
2850
|
+
const store = storeLattice.store;
|
|
2851
|
+
console.log("Delete request - keyOrId:", keyOrId);
|
|
2852
|
+
let config = await store.getConfigByKey(tenantId, keyOrId);
|
|
2853
|
+
let configKey = keyOrId;
|
|
2854
|
+
if (!config) {
|
|
2855
|
+
config = await store.getConfigById(tenantId, keyOrId);
|
|
2856
|
+
if (config) {
|
|
2857
|
+
configKey = config.key;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
if (!config) {
|
|
2861
|
+
reply.code(404);
|
|
2862
|
+
return {
|
|
2863
|
+
success: false,
|
|
2864
|
+
message: "Database configuration not found"
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
console.log("Found config to delete:", { id: config.id, key: config.key });
|
|
2868
|
+
const deleted = await store.deleteConfig(tenantId, config.id);
|
|
2869
|
+
if (!deleted) {
|
|
2870
|
+
return {
|
|
2871
|
+
success: false,
|
|
2872
|
+
message: "Failed to delete database configuration"
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
try {
|
|
2876
|
+
if (sqlDatabaseManager.hasDatabase(configKey)) {
|
|
2877
|
+
await sqlDatabaseManager.removeDatabase(configKey);
|
|
2878
|
+
}
|
|
2879
|
+
} catch (error) {
|
|
2880
|
+
console.warn("Failed to remove from SqlDatabaseManager:", error);
|
|
2881
|
+
}
|
|
2882
|
+
return {
|
|
2883
|
+
success: true,
|
|
2884
|
+
message: "Database configuration deleted successfully"
|
|
2885
|
+
};
|
|
2886
|
+
} catch (error) {
|
|
2887
|
+
console.error("Failed to delete database config:", error);
|
|
2888
|
+
return {
|
|
2889
|
+
success: false,
|
|
2890
|
+
message: "Failed to delete database configuration"
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
async function testDatabaseConnection(request, reply) {
|
|
2895
|
+
const tenantId = getTenantId(request);
|
|
2896
|
+
const { key } = request.params;
|
|
2897
|
+
try {
|
|
2898
|
+
const storeLattice = getStoreLattice6("default", "database");
|
|
2899
|
+
const store = storeLattice.store;
|
|
2900
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
2901
|
+
if (!config) {
|
|
2902
|
+
reply.code(404);
|
|
2903
|
+
return {
|
|
2904
|
+
success: false,
|
|
2905
|
+
message: "Database configuration not found"
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
const testKey = `__test_${key}_${Date.now()}`;
|
|
2909
|
+
sqlDatabaseManager.registerDatabase(testKey, config.config);
|
|
2910
|
+
const startTime = Date.now();
|
|
2911
|
+
const db = sqlDatabaseManager.getDatabase(testKey);
|
|
2912
|
+
try {
|
|
2913
|
+
await db.connect();
|
|
2914
|
+
await db.listTables();
|
|
2915
|
+
const latency = Date.now() - startTime;
|
|
2916
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2917
|
+
await db.disconnect();
|
|
2918
|
+
await sqlDatabaseManager.removeDatabase(testKey);
|
|
2919
|
+
return {
|
|
2920
|
+
success: true,
|
|
2921
|
+
message: "Connection test successful",
|
|
2922
|
+
data: {
|
|
2923
|
+
connected: true,
|
|
2924
|
+
latency
|
|
2925
|
+
}
|
|
2926
|
+
};
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
try {
|
|
2929
|
+
await db.disconnect();
|
|
2930
|
+
await sqlDatabaseManager.removeDatabase(testKey);
|
|
2931
|
+
} catch {
|
|
2932
|
+
}
|
|
2933
|
+
return {
|
|
2934
|
+
success: true,
|
|
2935
|
+
message: "Connection test failed",
|
|
2936
|
+
data: {
|
|
2937
|
+
connected: false,
|
|
2938
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
} catch (error) {
|
|
2943
|
+
console.error("Failed to test database connection:", error);
|
|
2944
|
+
return {
|
|
2945
|
+
success: false,
|
|
2946
|
+
message: "Failed to test database connection",
|
|
2947
|
+
data: {
|
|
2948
|
+
connected: false,
|
|
2949
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2950
|
+
}
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
function registerDatabaseConfigRoutes(app2) {
|
|
2955
|
+
app2.get(
|
|
2956
|
+
"/api/database-configs",
|
|
2957
|
+
getDatabaseConfigList
|
|
2958
|
+
);
|
|
2959
|
+
app2.get(
|
|
2960
|
+
"/api/database-configs/:key",
|
|
2961
|
+
getDatabaseConfig
|
|
2962
|
+
);
|
|
2963
|
+
app2.post(
|
|
2964
|
+
"/api/database-configs",
|
|
2965
|
+
createDatabaseConfig
|
|
2966
|
+
);
|
|
2967
|
+
app2.put(
|
|
2968
|
+
"/api/database-configs/:key",
|
|
2969
|
+
updateDatabaseConfig
|
|
2970
|
+
);
|
|
2971
|
+
app2.delete(
|
|
2972
|
+
"/api/database-configs/:keyOrId",
|
|
2973
|
+
deleteDatabaseConfig
|
|
2974
|
+
);
|
|
2975
|
+
app2.post(
|
|
2976
|
+
"/api/database-configs/:key/test",
|
|
2977
|
+
testDatabaseConnection
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
// src/controllers/metrics-configs.ts
|
|
2982
|
+
import {
|
|
2983
|
+
getStoreLattice as getStoreLattice7,
|
|
2984
|
+
metricsServerManager,
|
|
2985
|
+
SemanticMetricsClient
|
|
2986
|
+
} from "@axiom-lattice/core";
|
|
2987
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2988
|
+
function getTenantId2(request) {
|
|
2989
|
+
return request.headers["x-tenant-id"] || "default";
|
|
2990
|
+
}
|
|
2991
|
+
async function getMetricsServerConfigList(request, reply) {
|
|
2992
|
+
const tenantId = getTenantId2(request);
|
|
2993
|
+
try {
|
|
2994
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
2995
|
+
const store = storeLattice.store;
|
|
2996
|
+
const configs = await store.getAllConfigs(tenantId);
|
|
2997
|
+
return {
|
|
2998
|
+
success: true,
|
|
2999
|
+
message: "Metrics server configurations retrieved successfully",
|
|
3000
|
+
data: {
|
|
3001
|
+
records: configs,
|
|
3002
|
+
total: configs.length
|
|
3003
|
+
}
|
|
3004
|
+
};
|
|
3005
|
+
} catch (error) {
|
|
3006
|
+
console.error("Failed to get metrics server configs:", error);
|
|
3007
|
+
return {
|
|
3008
|
+
success: false,
|
|
3009
|
+
message: "Failed to retrieve metrics server configurations",
|
|
3010
|
+
data: {
|
|
3011
|
+
records: [],
|
|
3012
|
+
total: 0
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
async function getMetricsServerConfig(request, reply) {
|
|
3018
|
+
const tenantId = getTenantId2(request);
|
|
3019
|
+
const { key } = request.params;
|
|
3020
|
+
try {
|
|
3021
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3022
|
+
const store = storeLattice.store;
|
|
3023
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
3024
|
+
if (!config) {
|
|
3025
|
+
return {
|
|
3026
|
+
success: false,
|
|
3027
|
+
message: "Metrics server configuration not found"
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
return {
|
|
3031
|
+
success: true,
|
|
3032
|
+
message: "Metrics server configuration retrieved successfully",
|
|
3033
|
+
data: config
|
|
3034
|
+
};
|
|
3035
|
+
} catch (error) {
|
|
3036
|
+
console.error("Failed to get metrics server config:", error);
|
|
3037
|
+
return {
|
|
3038
|
+
success: false,
|
|
3039
|
+
message: "Failed to retrieve metrics server configuration"
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
async function createMetricsServerConfig(request, reply) {
|
|
3044
|
+
const tenantId = getTenantId2(request);
|
|
3045
|
+
const body = request.body;
|
|
3046
|
+
try {
|
|
3047
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3048
|
+
const store = storeLattice.store;
|
|
3049
|
+
const existing = await store.getConfigByKey(tenantId, body.key);
|
|
3050
|
+
if (existing) {
|
|
3051
|
+
reply.code(409);
|
|
3052
|
+
return {
|
|
3053
|
+
success: false,
|
|
3054
|
+
message: "Metrics server configuration with this key already exists"
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
if (body.config.type === "semantic" && !body.selectedDataSources) {
|
|
3058
|
+
reply.code(400);
|
|
3059
|
+
return {
|
|
3060
|
+
success: false,
|
|
3061
|
+
message: "selectedDataSources is required for semantic metrics servers"
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
const id = body.id || randomUUID4();
|
|
3065
|
+
const configData = {
|
|
3066
|
+
key: body.key,
|
|
3067
|
+
name: body.name,
|
|
3068
|
+
description: body.description,
|
|
3069
|
+
config: body.config.type === "semantic" ? {
|
|
3070
|
+
...body.config,
|
|
3071
|
+
selectedDataSources: body.selectedDataSources || []
|
|
3072
|
+
} : body.config
|
|
3073
|
+
};
|
|
3074
|
+
const config = await store.createConfig(tenantId, id, configData);
|
|
3075
|
+
try {
|
|
3076
|
+
metricsServerManager.registerServer(config.key, config.config);
|
|
3077
|
+
} catch (error) {
|
|
3078
|
+
console.warn("Failed to auto-register metrics server:", error);
|
|
3079
|
+
}
|
|
3080
|
+
reply.code(201);
|
|
3081
|
+
return {
|
|
3082
|
+
success: true,
|
|
3083
|
+
message: "Metrics server configuration created successfully",
|
|
3084
|
+
data: config
|
|
3085
|
+
};
|
|
3086
|
+
} catch (error) {
|
|
3087
|
+
console.error("Failed to create metrics server config:", error);
|
|
3088
|
+
return {
|
|
3089
|
+
success: false,
|
|
3090
|
+
message: "Failed to create metrics server configuration"
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
async function updateMetricsServerConfig(request, reply) {
|
|
3095
|
+
const tenantId = getTenantId2(request);
|
|
3096
|
+
const { key } = request.params;
|
|
3097
|
+
const updates = request.body;
|
|
3098
|
+
try {
|
|
3099
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3100
|
+
const store = storeLattice.store;
|
|
3101
|
+
const existing = await store.getConfigByKey(tenantId, key);
|
|
3102
|
+
if (!existing) {
|
|
3103
|
+
reply.code(404);
|
|
3104
|
+
return {
|
|
3105
|
+
success: false,
|
|
3106
|
+
message: "Metrics server configuration not found"
|
|
3107
|
+
};
|
|
3108
|
+
}
|
|
3109
|
+
const isSemantic = updates.config?.type === "semantic" || existing.config.type === "semantic" && !updates.config?.type;
|
|
3110
|
+
if (isSemantic && updates.selectedDataSources !== void 0) {
|
|
3111
|
+
updates.config = {
|
|
3112
|
+
...existing.config,
|
|
3113
|
+
...updates.config,
|
|
3114
|
+
type: "semantic",
|
|
3115
|
+
selectedDataSources: updates.selectedDataSources
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
const updated = await store.updateConfig(tenantId, existing.id, updates);
|
|
3119
|
+
if (!updated) {
|
|
3120
|
+
return {
|
|
3121
|
+
success: false,
|
|
3122
|
+
message: "Failed to update metrics server configuration"
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
if (updates.config) {
|
|
3126
|
+
try {
|
|
3127
|
+
metricsServerManager.registerServer(updated.key, updated.config);
|
|
3128
|
+
} catch (error) {
|
|
3129
|
+
console.warn("Failed to re-register metrics server:", error);
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
return {
|
|
3133
|
+
success: true,
|
|
3134
|
+
message: "Metrics server configuration updated successfully",
|
|
3135
|
+
data: updated
|
|
3136
|
+
};
|
|
3137
|
+
} catch (error) {
|
|
3138
|
+
console.error("Failed to update metrics server config:", error);
|
|
3139
|
+
return {
|
|
3140
|
+
success: false,
|
|
3141
|
+
message: "Failed to update metrics server configuration"
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
async function deleteMetricsServerConfig(request, reply) {
|
|
3146
|
+
const tenantId = getTenantId2(request);
|
|
3147
|
+
const { keyOrId } = request.params;
|
|
3148
|
+
try {
|
|
3149
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3150
|
+
const store = storeLattice.store;
|
|
3151
|
+
let config = await store.getConfigByKey(tenantId, keyOrId);
|
|
3152
|
+
let configKey = keyOrId;
|
|
3153
|
+
if (!config) {
|
|
3154
|
+
config = await store.getConfigById(tenantId, keyOrId);
|
|
3155
|
+
if (config) {
|
|
3156
|
+
configKey = config.key;
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
if (!config) {
|
|
3160
|
+
reply.code(404);
|
|
3161
|
+
return {
|
|
3162
|
+
success: false,
|
|
3163
|
+
message: "Metrics server configuration not found"
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
const deleted = await store.deleteConfig(tenantId, config.id);
|
|
3167
|
+
if (!deleted) {
|
|
3168
|
+
return {
|
|
3169
|
+
success: false,
|
|
3170
|
+
message: "Failed to delete metrics server configuration"
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
try {
|
|
3174
|
+
if (metricsServerManager.hasServer(configKey)) {
|
|
3175
|
+
metricsServerManager.removeServer(configKey);
|
|
3176
|
+
}
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
console.warn("Failed to remove from MetricsServerManager:", error);
|
|
3179
|
+
}
|
|
3180
|
+
return {
|
|
3181
|
+
success: true,
|
|
3182
|
+
message: "Metrics server configuration deleted successfully"
|
|
3183
|
+
};
|
|
3184
|
+
} catch (error) {
|
|
3185
|
+
console.error("Failed to delete metrics server config:", error);
|
|
3186
|
+
return {
|
|
3187
|
+
success: false,
|
|
3188
|
+
message: "Failed to delete metrics server configuration"
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
async function testMetricsServerConnection(request, reply) {
|
|
3193
|
+
const tenantId = getTenantId2(request);
|
|
3194
|
+
const { key } = request.params;
|
|
3195
|
+
try {
|
|
3196
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3197
|
+
const store = storeLattice.store;
|
|
3198
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
3199
|
+
if (!config) {
|
|
3200
|
+
reply.code(404);
|
|
3201
|
+
return {
|
|
3202
|
+
success: false,
|
|
3203
|
+
message: "Metrics server configuration not found"
|
|
3204
|
+
};
|
|
3205
|
+
}
|
|
3206
|
+
const testKey = `__test_${key}_${Date.now()}`;
|
|
3207
|
+
metricsServerManager.registerServer(testKey, config.config);
|
|
3208
|
+
try {
|
|
3209
|
+
const client = metricsServerManager.getClient(testKey);
|
|
3210
|
+
const result = await client.testConnection();
|
|
3211
|
+
metricsServerManager.removeServer(testKey);
|
|
3212
|
+
return {
|
|
3213
|
+
success: true,
|
|
3214
|
+
message: result.connected ? "Connection test successful" : "Connection test failed",
|
|
3215
|
+
data: result
|
|
3216
|
+
};
|
|
3217
|
+
} catch (error) {
|
|
3218
|
+
try {
|
|
3219
|
+
metricsServerManager.removeServer(testKey);
|
|
3220
|
+
} catch {
|
|
3221
|
+
}
|
|
3222
|
+
return {
|
|
3223
|
+
success: true,
|
|
3224
|
+
message: "Connection test failed",
|
|
3225
|
+
data: {
|
|
3226
|
+
connected: false,
|
|
3227
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3228
|
+
}
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
} catch (error) {
|
|
3232
|
+
console.error("Failed to test metrics server connection:", error);
|
|
3233
|
+
return {
|
|
3234
|
+
success: false,
|
|
3235
|
+
message: "Failed to test metrics server connection",
|
|
3236
|
+
data: {
|
|
3237
|
+
connected: false,
|
|
3238
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3239
|
+
}
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
async function listAvailableMetrics(request, reply) {
|
|
3244
|
+
const tenantId = getTenantId2(request);
|
|
3245
|
+
const { key } = request.params;
|
|
3246
|
+
try {
|
|
3247
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3248
|
+
const store = storeLattice.store;
|
|
3249
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
3250
|
+
if (!config) {
|
|
3251
|
+
reply.code(404);
|
|
3252
|
+
return {
|
|
3253
|
+
success: false,
|
|
3254
|
+
message: "Metrics server configuration not found"
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
if (!metricsServerManager.hasServer(key)) {
|
|
3258
|
+
metricsServerManager.registerServer(key, config.config);
|
|
3259
|
+
}
|
|
3260
|
+
const client = metricsServerManager.getClient(key);
|
|
3261
|
+
const metrics = await client.listMetrics();
|
|
3262
|
+
return {
|
|
3263
|
+
success: true,
|
|
3264
|
+
message: "Metrics retrieved successfully",
|
|
3265
|
+
data: {
|
|
3266
|
+
metrics: metrics.map((m) => ({
|
|
3267
|
+
name: m.name,
|
|
3268
|
+
type: m.type,
|
|
3269
|
+
description: m.description
|
|
3270
|
+
}))
|
|
3271
|
+
}
|
|
3272
|
+
};
|
|
3273
|
+
} catch (error) {
|
|
3274
|
+
console.error("Failed to list metrics:", error);
|
|
3275
|
+
return {
|
|
3276
|
+
success: false,
|
|
3277
|
+
message: "Failed to retrieve metrics"
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
async function queryMetricsData(request, reply) {
|
|
3282
|
+
const tenantId = getTenantId2(request);
|
|
3283
|
+
const { key } = request.params;
|
|
3284
|
+
const { metricName, startTime, endTime, step, labels } = request.body;
|
|
3285
|
+
try {
|
|
3286
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3287
|
+
const store = storeLattice.store;
|
|
3288
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
3289
|
+
if (!config) {
|
|
3290
|
+
reply.code(404);
|
|
3291
|
+
return {
|
|
3292
|
+
success: false,
|
|
3293
|
+
message: "Metrics server configuration not found"
|
|
3294
|
+
};
|
|
3295
|
+
}
|
|
3296
|
+
if (!metricName) {
|
|
3297
|
+
reply.code(400);
|
|
3298
|
+
return {
|
|
3299
|
+
success: false,
|
|
3300
|
+
message: "metricName is required"
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3303
|
+
if (!metricsServerManager.hasServer(key)) {
|
|
3304
|
+
metricsServerManager.registerServer(key, config.config);
|
|
3305
|
+
}
|
|
3306
|
+
const client = metricsServerManager.getClient(key);
|
|
3307
|
+
const result = await client.queryMetricData(metricName, {
|
|
3308
|
+
startTime,
|
|
3309
|
+
endTime,
|
|
3310
|
+
step,
|
|
3311
|
+
labels
|
|
3312
|
+
});
|
|
3313
|
+
return {
|
|
3314
|
+
success: true,
|
|
3315
|
+
message: "Metric data retrieved successfully",
|
|
3316
|
+
data: {
|
|
3317
|
+
metricName: result.metricName,
|
|
3318
|
+
dataPoints: result.dataPoints
|
|
3319
|
+
}
|
|
3320
|
+
};
|
|
3321
|
+
} catch (error) {
|
|
3322
|
+
console.error("Failed to query metric data:", error);
|
|
3323
|
+
return {
|
|
3324
|
+
success: false,
|
|
3325
|
+
message: "Failed to query metric data"
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
async function getDataSources(request, reply) {
|
|
3330
|
+
const tenantId = getTenantId2(request);
|
|
3331
|
+
const { key } = request.params;
|
|
3332
|
+
try {
|
|
3333
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3334
|
+
const store = storeLattice.store;
|
|
3335
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
3336
|
+
if (!config) {
|
|
3337
|
+
reply.code(404);
|
|
3338
|
+
return {
|
|
3339
|
+
success: false,
|
|
3340
|
+
message: "Metrics server configuration not found"
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
if (config.config.type !== "semantic") {
|
|
3344
|
+
reply.code(400);
|
|
3345
|
+
return {
|
|
3346
|
+
success: false,
|
|
3347
|
+
message: "This endpoint is only available for semantic metrics servers"
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
const semanticConfig = config.config;
|
|
3351
|
+
const client = new SemanticMetricsClient(semanticConfig);
|
|
3352
|
+
const datasources = await client.getDataSources();
|
|
3353
|
+
return {
|
|
3354
|
+
success: true,
|
|
3355
|
+
message: "Data sources retrieved successfully",
|
|
3356
|
+
data: {
|
|
3357
|
+
datasources
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
} catch (error) {
|
|
3361
|
+
console.error("Failed to get data sources:", error);
|
|
3362
|
+
return {
|
|
3363
|
+
success: false,
|
|
3364
|
+
message: "Failed to retrieve data sources"
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
async function getDatasourceMetrics(request, reply) {
|
|
3369
|
+
const tenantId = getTenantId2(request);
|
|
3370
|
+
const { key, datasourceId } = request.params;
|
|
3371
|
+
try {
|
|
3372
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3373
|
+
const store = storeLattice.store;
|
|
3374
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
3375
|
+
if (!config) {
|
|
3376
|
+
reply.code(404);
|
|
3377
|
+
return {
|
|
3378
|
+
success: false,
|
|
3379
|
+
message: "Metrics server configuration not found"
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
if (config.config.type !== "semantic") {
|
|
3383
|
+
reply.code(400);
|
|
3384
|
+
return {
|
|
3385
|
+
success: false,
|
|
3386
|
+
message: "This endpoint is only available for semantic metrics servers"
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
const semanticConfig = config.config;
|
|
3390
|
+
const client = new SemanticMetricsClient(semanticConfig);
|
|
3391
|
+
const metrics = await client.getDatasourceMetrics(datasourceId);
|
|
3392
|
+
return {
|
|
3393
|
+
success: true,
|
|
3394
|
+
message: "Datasource metrics retrieved successfully",
|
|
3395
|
+
data: metrics
|
|
3396
|
+
};
|
|
3397
|
+
} catch (error) {
|
|
3398
|
+
console.error("Failed to get datasource metrics:", error);
|
|
3399
|
+
return {
|
|
3400
|
+
success: false,
|
|
3401
|
+
message: "Failed to retrieve datasource metrics"
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
async function querySemanticMetrics(request, reply) {
|
|
3406
|
+
const tenantId = getTenantId2(request);
|
|
3407
|
+
const { key } = request.params;
|
|
3408
|
+
const body = request.body;
|
|
3409
|
+
try {
|
|
3410
|
+
const storeLattice = getStoreLattice7("default", "metrics");
|
|
3411
|
+
const store = storeLattice.store;
|
|
3412
|
+
const config = await store.getConfigByKey(tenantId, key);
|
|
3413
|
+
if (!config) {
|
|
3414
|
+
reply.code(404);
|
|
3415
|
+
return {
|
|
3416
|
+
success: false,
|
|
3417
|
+
message: "Metrics server configuration not found"
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
if (config.config.type !== "semantic") {
|
|
3421
|
+
reply.code(400);
|
|
3422
|
+
return {
|
|
3423
|
+
success: false,
|
|
3424
|
+
message: "This endpoint is only available for semantic metrics servers"
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
if (!body.datasourceId || !body.metrics || body.metrics.length === 0) {
|
|
3428
|
+
reply.code(400);
|
|
3429
|
+
return {
|
|
3430
|
+
success: false,
|
|
3431
|
+
message: "datasourceId and metrics array are required"
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
const semanticConfig = config.config;
|
|
3435
|
+
const client = new SemanticMetricsClient(semanticConfig);
|
|
3436
|
+
const result = await client.semanticQuery(body);
|
|
3437
|
+
return {
|
|
3438
|
+
success: true,
|
|
3439
|
+
message: "Semantic query executed successfully",
|
|
3440
|
+
data: {
|
|
3441
|
+
datasourceId: result.datasourceId,
|
|
3442
|
+
metrics: result.metrics,
|
|
3443
|
+
dataPoints: result.dataPoints,
|
|
3444
|
+
metadata: result.metadata
|
|
3445
|
+
}
|
|
3446
|
+
};
|
|
3447
|
+
} catch (error) {
|
|
3448
|
+
console.error("Failed to query semantic metrics:", error);
|
|
3449
|
+
return {
|
|
3450
|
+
success: false,
|
|
3451
|
+
message: "Failed to query semantic metrics"
|
|
3452
|
+
};
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
async function testSemanticDataSources(request, reply) {
|
|
3456
|
+
const body = request.body;
|
|
3457
|
+
try {
|
|
3458
|
+
if (!body.serverUrl) {
|
|
3459
|
+
reply.code(400);
|
|
3460
|
+
return {
|
|
3461
|
+
success: false,
|
|
3462
|
+
message: "serverUrl is required"
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
const testConfig = {
|
|
3466
|
+
type: "semantic",
|
|
3467
|
+
serverUrl: body.serverUrl,
|
|
3468
|
+
apiKey: body.apiKey,
|
|
3469
|
+
username: body.username,
|
|
3470
|
+
password: body.password,
|
|
3471
|
+
headers: body.headers
|
|
3472
|
+
};
|
|
3473
|
+
const client = new SemanticMetricsClient(testConfig);
|
|
3474
|
+
const datasources = await client.getDataSources();
|
|
3475
|
+
return {
|
|
3476
|
+
success: true,
|
|
3477
|
+
message: "Data sources retrieved successfully",
|
|
3478
|
+
data: {
|
|
3479
|
+
datasources
|
|
3480
|
+
}
|
|
3481
|
+
};
|
|
3482
|
+
} catch (error) {
|
|
3483
|
+
console.error("Failed to test datasources:", error);
|
|
3484
|
+
return {
|
|
3485
|
+
success: false,
|
|
3486
|
+
message: `Failed to retrieve data sources: ${error instanceof Error ? error.message : String(error)}`
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
async function testDatasourceMetrics(request, reply) {
|
|
3491
|
+
const { datasourceId } = request.params;
|
|
3492
|
+
const body = request.body;
|
|
3493
|
+
try {
|
|
3494
|
+
if (!body.serverUrl) {
|
|
3495
|
+
reply.code(400);
|
|
3496
|
+
return {
|
|
3497
|
+
success: false,
|
|
3498
|
+
message: "serverUrl is required"
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
const testConfig = {
|
|
3502
|
+
type: "semantic",
|
|
3503
|
+
serverUrl: body.serverUrl,
|
|
3504
|
+
apiKey: body.apiKey,
|
|
3505
|
+
username: body.username,
|
|
3506
|
+
password: body.password,
|
|
3507
|
+
headers: body.headers
|
|
3508
|
+
};
|
|
3509
|
+
const client = new SemanticMetricsClient(testConfig);
|
|
3510
|
+
const metrics = await client.getDatasourceMetrics(datasourceId);
|
|
3511
|
+
return {
|
|
3512
|
+
success: true,
|
|
3513
|
+
message: "Datasource metrics retrieved successfully",
|
|
3514
|
+
data: metrics
|
|
3515
|
+
};
|
|
3516
|
+
} catch (error) {
|
|
3517
|
+
console.error("Failed to test datasource metrics:", error);
|
|
3518
|
+
return {
|
|
3519
|
+
success: false,
|
|
3520
|
+
message: `Failed to retrieve datasource metrics: ${error instanceof Error ? error.message : String(error)}`
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
function registerMetricsServerConfigRoutes(app2) {
|
|
3525
|
+
app2.get("/api/metrics-configs", getMetricsServerConfigList);
|
|
3526
|
+
app2.get("/api/metrics-configs/:key", getMetricsServerConfig);
|
|
3527
|
+
app2.post("/api/metrics-configs", createMetricsServerConfig);
|
|
3528
|
+
app2.put("/api/metrics-configs/:key", updateMetricsServerConfig);
|
|
3529
|
+
app2.delete("/api/metrics-configs/:keyOrId", deleteMetricsServerConfig);
|
|
3530
|
+
app2.post("/api/metrics-configs/:key/test", testMetricsServerConnection);
|
|
3531
|
+
app2.get("/api/metrics-configs/:key/metrics", listAvailableMetrics);
|
|
3532
|
+
app2.post("/api/metrics-configs/:key/query", queryMetricsData);
|
|
3533
|
+
app2.get("/api/metrics-configs/:key/datasources", getDataSources);
|
|
3534
|
+
app2.get("/api/metrics-configs/:key/datasources/:datasourceId/meta", getDatasourceMetrics);
|
|
3535
|
+
app2.post("/api/metrics-configs/:key/semantic-query", querySemanticMetrics);
|
|
3536
|
+
app2.post("/api/metrics-configs/test-datasources", testSemanticDataSources);
|
|
3537
|
+
app2.post("/api/metrics-configs/test-datasources/:datasourceId/meta", testDatasourceMetrics);
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
// src/controllers/users.ts
|
|
3541
|
+
import { getStoreLattice as getStoreLattice8 } from "@axiom-lattice/core";
|
|
3542
|
+
import { v4 as uuidv42 } from "uuid";
|
|
3543
|
+
var UsersController = class {
|
|
3544
|
+
constructor() {
|
|
3545
|
+
this.userStore = getStoreLattice8("default", "user").store;
|
|
3546
|
+
}
|
|
3547
|
+
async listUsers(request, reply) {
|
|
3548
|
+
const { email } = request.query;
|
|
3549
|
+
if (email) {
|
|
3550
|
+
const user = await this.userStore.getUserByEmail(email);
|
|
3551
|
+
return { success: true, data: user ? [user] : [] };
|
|
3552
|
+
}
|
|
3553
|
+
const users = await this.userStore.getAllUsers();
|
|
3554
|
+
return { success: true, data: users };
|
|
3555
|
+
}
|
|
3556
|
+
async createUser(request, reply) {
|
|
3557
|
+
const data = request.body;
|
|
3558
|
+
const id = uuidv42();
|
|
3559
|
+
const existingUser = await this.userStore.getUserByEmail(data.email);
|
|
3560
|
+
if (existingUser) {
|
|
3561
|
+
return reply.status(409).send({
|
|
3562
|
+
success: false,
|
|
3563
|
+
error: `User with email ${data.email} already exists`
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
const user = await this.userStore.createUser(id, data);
|
|
3567
|
+
return reply.status(201).send({ success: true, data: user });
|
|
3568
|
+
}
|
|
3569
|
+
async getUser(request, reply) {
|
|
3570
|
+
const { userId } = request.params;
|
|
3571
|
+
const user = await this.userStore.getUserById(userId);
|
|
3572
|
+
if (!user) {
|
|
3573
|
+
return reply.status(404).send({
|
|
3574
|
+
success: false,
|
|
3575
|
+
error: "User not found"
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
return { success: true, data: user };
|
|
3579
|
+
}
|
|
3580
|
+
async updateUser(request, reply) {
|
|
3581
|
+
const { userId } = request.params;
|
|
3582
|
+
const updates = request.body;
|
|
3583
|
+
if (updates.email) {
|
|
3584
|
+
const existingUser = await this.userStore.getUserByEmail(updates.email);
|
|
3585
|
+
if (existingUser && existingUser.id !== userId) {
|
|
3586
|
+
return reply.status(409).send({
|
|
3587
|
+
success: false,
|
|
3588
|
+
error: `User with email ${updates.email} already exists`
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
const user = await this.userStore.updateUser(userId, updates);
|
|
3593
|
+
if (!user) {
|
|
3594
|
+
return reply.status(404).send({
|
|
3595
|
+
success: false,
|
|
3596
|
+
error: "User not found"
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
return { success: true, data: user };
|
|
3600
|
+
}
|
|
3601
|
+
async deleteUser(request, reply) {
|
|
3602
|
+
const { userId } = request.params;
|
|
3603
|
+
const deleted = await this.userStore.deleteUser(userId);
|
|
3604
|
+
if (!deleted) {
|
|
3605
|
+
return reply.status(404).send({
|
|
3606
|
+
success: false,
|
|
3607
|
+
error: "User not found"
|
|
3608
|
+
});
|
|
3609
|
+
}
|
|
3610
|
+
return { success: true };
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
function registerUserRoutes(app2) {
|
|
3614
|
+
const controller = new UsersController();
|
|
3615
|
+
app2.get("/api/users", controller.listUsers.bind(controller));
|
|
3616
|
+
app2.post("/api/users", controller.createUser.bind(controller));
|
|
3617
|
+
app2.get("/api/users/:userId", controller.getUser.bind(controller));
|
|
3618
|
+
app2.patch("/api/users/:userId", controller.updateUser.bind(controller));
|
|
3619
|
+
app2.delete("/api/users/:userId", controller.deleteUser.bind(controller));
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
// src/controllers/tenants.ts
|
|
3623
|
+
import { getStoreLattice as getStoreLattice9 } from "@axiom-lattice/core";
|
|
3624
|
+
import { v4 as uuidv43 } from "uuid";
|
|
3625
|
+
var TenantsController = class {
|
|
3626
|
+
constructor() {
|
|
3627
|
+
this.tenantStore = getStoreLattice9("default", "tenant").store;
|
|
3628
|
+
}
|
|
3629
|
+
// ==================== Tenant CRUD ====================
|
|
3630
|
+
async listTenants(request, reply) {
|
|
3631
|
+
const tenants = await this.tenantStore.getAllTenants();
|
|
3632
|
+
return { success: true, data: tenants };
|
|
3633
|
+
}
|
|
3634
|
+
async createTenant(request, reply) {
|
|
3635
|
+
const data = request.body;
|
|
3636
|
+
const id = uuidv43();
|
|
3637
|
+
const tenant = await this.tenantStore.createTenant(id, data);
|
|
3638
|
+
return reply.status(201).send({ success: true, data: tenant });
|
|
3639
|
+
}
|
|
3640
|
+
async getTenant(request, reply) {
|
|
3641
|
+
const { tenantId } = request.params;
|
|
3642
|
+
const tenant = await this.tenantStore.getTenantById(tenantId);
|
|
3643
|
+
if (!tenant) {
|
|
3644
|
+
return reply.status(404).send({
|
|
3645
|
+
success: false,
|
|
3646
|
+
error: "Tenant not found"
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
return { success: true, data: tenant };
|
|
3650
|
+
}
|
|
3651
|
+
async updateTenant(request, reply) {
|
|
3652
|
+
const { tenantId } = request.params;
|
|
3653
|
+
const updates = request.body;
|
|
3654
|
+
const tenant = await this.tenantStore.updateTenant(tenantId, updates);
|
|
3655
|
+
if (!tenant) {
|
|
3656
|
+
return reply.status(404).send({
|
|
3657
|
+
success: false,
|
|
3658
|
+
error: "Tenant not found"
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3661
|
+
return { success: true, data: tenant };
|
|
3662
|
+
}
|
|
3663
|
+
async deleteTenant(request, reply) {
|
|
3664
|
+
const { tenantId } = request.params;
|
|
3665
|
+
if (tenantId === "default") {
|
|
3666
|
+
return reply.status(403).send({
|
|
3667
|
+
success: false,
|
|
3668
|
+
error: "Cannot delete the default tenant"
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
const deleted = await this.tenantStore.deleteTenant(tenantId);
|
|
3672
|
+
if (!deleted) {
|
|
3673
|
+
return reply.status(404).send({
|
|
3674
|
+
success: false,
|
|
3675
|
+
error: "Tenant not found"
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
return { success: true };
|
|
3679
|
+
}
|
|
3680
|
+
};
|
|
3681
|
+
function registerTenantRoutes(app2) {
|
|
3682
|
+
const controller = new TenantsController();
|
|
3683
|
+
app2.get("/api/tenants", controller.listTenants.bind(controller));
|
|
3684
|
+
app2.post("/api/tenants", controller.createTenant.bind(controller));
|
|
3685
|
+
app2.get("/api/tenants/:tenantId", controller.getTenant.bind(controller));
|
|
3686
|
+
app2.patch("/api/tenants/:tenantId", controller.updateTenant.bind(controller));
|
|
3687
|
+
app2.delete("/api/tenants/:tenantId", controller.deleteTenant.bind(controller));
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
// src/controllers/auth.ts
|
|
3691
|
+
import { getStoreLattice as getStoreLattice10 } from "@axiom-lattice/core";
|
|
3692
|
+
import { v4 as uuidv44 } from "uuid";
|
|
3693
|
+
var defaultAuthConfig = {
|
|
3694
|
+
autoApproveUsers: true,
|
|
3695
|
+
allowTenantRegistration: true,
|
|
3696
|
+
tokenExpiration: 86400
|
|
3697
|
+
};
|
|
3698
|
+
var AuthController = class {
|
|
3699
|
+
constructor(config = {}) {
|
|
3700
|
+
this.userStore = getStoreLattice10("default", "user").store;
|
|
3701
|
+
this.tenantStore = getStoreLattice10("default", "tenant").store;
|
|
3702
|
+
this.userTenantLinkStore = getStoreLattice10("default", "userTenantLink").store;
|
|
3703
|
+
this.config = { ...defaultAuthConfig, ...config };
|
|
3704
|
+
}
|
|
3705
|
+
async register(request, reply) {
|
|
3706
|
+
const { email, password, name } = request.body;
|
|
3707
|
+
try {
|
|
3708
|
+
const existingUser = await this.userStore.getUserByEmail(email);
|
|
3709
|
+
if (existingUser) {
|
|
3710
|
+
return reply.status(409).send({
|
|
3711
|
+
success: false,
|
|
3712
|
+
error: "User with this email already exists"
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
const userId = uuidv44();
|
|
3716
|
+
const userStatus = this.config.autoApproveUsers ? "active" : "pending";
|
|
3717
|
+
const userData = {
|
|
3718
|
+
email,
|
|
3719
|
+
name,
|
|
3720
|
+
status: userStatus,
|
|
3721
|
+
metadata: {
|
|
3722
|
+
passwordHash: await this.hashPassword(password)
|
|
3723
|
+
}
|
|
3724
|
+
};
|
|
3725
|
+
const user = await this.userStore.createUser(userId, userData);
|
|
3726
|
+
return reply.status(201).send({
|
|
3727
|
+
success: true,
|
|
3728
|
+
message: this.config.autoApproveUsers ? "Registration successful" : "Registration successful. Please wait for admin approval.",
|
|
3729
|
+
data: {
|
|
3730
|
+
user: {
|
|
3731
|
+
id: user.id,
|
|
3732
|
+
email: user.email,
|
|
3733
|
+
name: user.name,
|
|
3734
|
+
status: user.status
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
});
|
|
3738
|
+
} catch (error) {
|
|
3739
|
+
console.error("Registration error:", error);
|
|
3740
|
+
return reply.status(500).send({
|
|
3741
|
+
success: false,
|
|
3742
|
+
error: "Registration failed"
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
async login(request, reply) {
|
|
3747
|
+
const { email, password } = request.body;
|
|
3748
|
+
try {
|
|
3749
|
+
const user = await this.userStore.getUserByEmail(email);
|
|
3750
|
+
if (!user) {
|
|
3751
|
+
return reply.status(401).send({
|
|
3752
|
+
success: false,
|
|
3753
|
+
error: "Invalid credentials"
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
if (user.status !== "active") {
|
|
3757
|
+
return reply.status(403).send({
|
|
3758
|
+
success: false,
|
|
3759
|
+
error: `Account is ${user.status}. Please wait for admin approval.`
|
|
3760
|
+
});
|
|
3761
|
+
}
|
|
3762
|
+
const isValidPassword = await this.verifyPassword(
|
|
3763
|
+
password,
|
|
3764
|
+
user.metadata?.passwordHash || ""
|
|
3765
|
+
);
|
|
3766
|
+
if (!isValidPassword) {
|
|
3767
|
+
return reply.status(401).send({
|
|
3768
|
+
success: false,
|
|
3769
|
+
error: "Invalid credentials"
|
|
3770
|
+
});
|
|
3771
|
+
}
|
|
3772
|
+
const tenantLinks = await this.userTenantLinkStore.getTenantsByUser(user.id);
|
|
3773
|
+
const token = await this.generateToken(user.id);
|
|
3774
|
+
return {
|
|
3775
|
+
success: true,
|
|
3776
|
+
data: {
|
|
3777
|
+
user: {
|
|
3778
|
+
id: user.id,
|
|
3779
|
+
email: user.email,
|
|
3780
|
+
name: user.name,
|
|
3781
|
+
status: user.status
|
|
3782
|
+
},
|
|
3783
|
+
tenants: tenantLinks.map((link) => ({
|
|
3784
|
+
id: link.tenantId,
|
|
3785
|
+
role: link.role
|
|
3786
|
+
})),
|
|
3787
|
+
token,
|
|
3788
|
+
requiresTenantSelection: tenantLinks.length > 1,
|
|
3789
|
+
hasTenants: tenantLinks.length > 0
|
|
3790
|
+
}
|
|
3791
|
+
};
|
|
3792
|
+
} catch (error) {
|
|
3793
|
+
console.error("Login error:", error);
|
|
3794
|
+
return reply.status(500).send({
|
|
3795
|
+
success: false,
|
|
3796
|
+
error: "Login failed"
|
|
3797
|
+
});
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
async getUserTenants(request, reply) {
|
|
3801
|
+
const userId = request.user?.id;
|
|
3802
|
+
if (!userId) {
|
|
3803
|
+
return reply.status(401).send({
|
|
3804
|
+
success: false,
|
|
3805
|
+
error: "Unauthorized"
|
|
3806
|
+
});
|
|
3807
|
+
}
|
|
3808
|
+
try {
|
|
3809
|
+
const links = await this.userTenantLinkStore.getTenantsByUser(userId);
|
|
3810
|
+
const tenantsWithDetails = await Promise.all(
|
|
3811
|
+
links.map(async (link) => {
|
|
3812
|
+
const tenant = await this.tenantStore.getTenantById(link.tenantId);
|
|
3813
|
+
return {
|
|
3814
|
+
...link,
|
|
3815
|
+
tenant: tenant || null
|
|
3816
|
+
};
|
|
3817
|
+
})
|
|
3818
|
+
);
|
|
3819
|
+
return {
|
|
3820
|
+
success: true,
|
|
3821
|
+
data: tenantsWithDetails
|
|
3822
|
+
};
|
|
3823
|
+
} catch (error) {
|
|
3824
|
+
console.error("Get user tenants error:", error);
|
|
3825
|
+
return reply.status(500).send({
|
|
3826
|
+
success: false,
|
|
3827
|
+
error: "Failed to get user tenants"
|
|
3828
|
+
});
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
async selectTenant(request, reply) {
|
|
3832
|
+
const userId = request.user?.id;
|
|
3833
|
+
const { tenantId } = request.body;
|
|
3834
|
+
if (!userId) {
|
|
3835
|
+
return reply.status(401).send({
|
|
3836
|
+
success: false,
|
|
3837
|
+
error: "Unauthorized"
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3840
|
+
try {
|
|
3841
|
+
const hasLink = await this.userTenantLinkStore.hasLink(userId, tenantId);
|
|
3842
|
+
if (!hasLink) {
|
|
3843
|
+
return reply.status(403).send({
|
|
3844
|
+
success: false,
|
|
3845
|
+
error: "You do not have access to this tenant"
|
|
3846
|
+
});
|
|
3847
|
+
}
|
|
3848
|
+
const tenant = await this.tenantStore.getTenantById(tenantId);
|
|
3849
|
+
if (!tenant) {
|
|
3850
|
+
return reply.status(404).send({
|
|
3851
|
+
success: false,
|
|
3852
|
+
error: "Tenant not found"
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
const token = await this.generateToken(userId, tenantId);
|
|
3856
|
+
return {
|
|
3857
|
+
success: true,
|
|
3858
|
+
data: {
|
|
3859
|
+
tenant: {
|
|
3860
|
+
id: tenant.id,
|
|
3861
|
+
name: tenant.name,
|
|
3862
|
+
status: tenant.status
|
|
3863
|
+
},
|
|
3864
|
+
token
|
|
3865
|
+
}
|
|
3866
|
+
};
|
|
3867
|
+
} catch (error) {
|
|
3868
|
+
console.error("Select tenant error:", error);
|
|
3869
|
+
return reply.status(500).send({
|
|
3870
|
+
success: false,
|
|
3871
|
+
error: "Failed to select tenant"
|
|
3872
|
+
});
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
async approveUser(request, reply) {
|
|
3876
|
+
const { userId, approved } = request.body;
|
|
3877
|
+
try {
|
|
3878
|
+
const user = await this.userStore.getUserById(userId);
|
|
3879
|
+
if (!user) {
|
|
3880
|
+
return reply.status(404).send({
|
|
3881
|
+
success: false,
|
|
3882
|
+
error: "User not found"
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
if (user.status !== "pending") {
|
|
3886
|
+
return reply.status(400).send({
|
|
3887
|
+
success: false,
|
|
3888
|
+
error: `User is already ${user.status}`
|
|
3889
|
+
});
|
|
3890
|
+
}
|
|
3891
|
+
const updatedUser = await this.userStore.updateUser(userId, {
|
|
3892
|
+
status: approved ? "active" : "suspended"
|
|
3893
|
+
});
|
|
3894
|
+
return {
|
|
3895
|
+
success: true,
|
|
3896
|
+
data: updatedUser
|
|
3897
|
+
};
|
|
3898
|
+
} catch (error) {
|
|
3899
|
+
console.error("Approval error:", error);
|
|
3900
|
+
return reply.status(500).send({
|
|
3901
|
+
success: false,
|
|
3902
|
+
error: "Approval failed"
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
async listPendingUsers(request, reply) {
|
|
3907
|
+
try {
|
|
3908
|
+
const users = await this.userStore.getAllUsers();
|
|
3909
|
+
const pendingUsers = users.filter((u) => u.status === "pending");
|
|
3910
|
+
return {
|
|
3911
|
+
success: true,
|
|
3912
|
+
data: pendingUsers
|
|
3913
|
+
};
|
|
3914
|
+
} catch (error) {
|
|
3915
|
+
console.error("List pending users error:", error);
|
|
3916
|
+
return reply.status(500).send({
|
|
3917
|
+
success: false,
|
|
3918
|
+
error: "Failed to list pending users"
|
|
3919
|
+
});
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
async assignTenant(request, reply) {
|
|
3923
|
+
const { userId, tenantId, role = "member" } = request.body;
|
|
3924
|
+
try {
|
|
3925
|
+
const user = await this.userStore.getUserById(userId);
|
|
3926
|
+
if (!user) {
|
|
3927
|
+
return reply.status(404).send({
|
|
3928
|
+
success: false,
|
|
3929
|
+
error: "User not found"
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
const tenant = await this.tenantStore.getTenantById(tenantId);
|
|
3933
|
+
if (!tenant) {
|
|
3934
|
+
return reply.status(404).send({
|
|
3935
|
+
success: false,
|
|
3936
|
+
error: "Tenant not found"
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
const link = await this.userTenantLinkStore.createLink({
|
|
3940
|
+
userId,
|
|
3941
|
+
tenantId,
|
|
3942
|
+
role
|
|
3943
|
+
});
|
|
3944
|
+
return {
|
|
3945
|
+
success: true,
|
|
3946
|
+
data: link
|
|
3947
|
+
};
|
|
3948
|
+
} catch (error) {
|
|
3949
|
+
console.error("Assign tenant error:", error);
|
|
3950
|
+
return reply.status(500).send({
|
|
3951
|
+
success: false,
|
|
3952
|
+
error: "Failed to assign tenant"
|
|
3953
|
+
});
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
async hashPassword(password) {
|
|
3957
|
+
const encoder = new TextEncoder();
|
|
3958
|
+
const data = encoder.encode(password + "salt");
|
|
3959
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
3960
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
3961
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3962
|
+
}
|
|
3963
|
+
async verifyPassword(password, hash) {
|
|
3964
|
+
const hashedPassword = await this.hashPassword(password);
|
|
3965
|
+
return hashedPassword === hash;
|
|
3966
|
+
}
|
|
3967
|
+
async generateToken(userId, tenantId) {
|
|
3968
|
+
const payload = {
|
|
3969
|
+
userId,
|
|
3970
|
+
exp: Date.now() + this.config.tokenExpiration * 1e3
|
|
3971
|
+
};
|
|
3972
|
+
if (tenantId) {
|
|
3973
|
+
payload.tenantId = tenantId;
|
|
3974
|
+
}
|
|
3975
|
+
return btoa(JSON.stringify(payload));
|
|
3976
|
+
}
|
|
3977
|
+
};
|
|
3978
|
+
function registerAuthRoutes(app2, config) {
|
|
3979
|
+
const controller = new AuthController(config);
|
|
3980
|
+
const authHook = async (request, reply) => {
|
|
3981
|
+
const authHeader = request.headers.authorization;
|
|
3982
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
3983
|
+
return reply.status(401).send({
|
|
3984
|
+
success: false,
|
|
3985
|
+
error: "Unauthorized - Missing or invalid token"
|
|
3986
|
+
});
|
|
3987
|
+
}
|
|
3988
|
+
const token = authHeader.substring(7);
|
|
3989
|
+
try {
|
|
3990
|
+
const payload = JSON.parse(atob(token));
|
|
3991
|
+
if (payload.exp && payload.exp < Date.now()) {
|
|
3992
|
+
return reply.status(401).send({
|
|
3993
|
+
success: false,
|
|
3994
|
+
error: "Unauthorized - Token expired"
|
|
3995
|
+
});
|
|
3996
|
+
}
|
|
3997
|
+
request.user = {
|
|
3998
|
+
id: payload.userId,
|
|
3999
|
+
tenantId: payload.tenantId
|
|
4000
|
+
};
|
|
4001
|
+
} catch {
|
|
4002
|
+
return reply.status(401).send({
|
|
4003
|
+
success: false,
|
|
4004
|
+
error: "Unauthorized - Invalid token"
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
};
|
|
4008
|
+
app2.post("/api/auth/register", controller.register.bind(controller));
|
|
4009
|
+
app2.post("/api/auth/login", controller.login.bind(controller));
|
|
4010
|
+
app2.get("/api/auth/tenants", { preHandler: authHook }, (req, res) => controller.getUserTenants(req, res));
|
|
4011
|
+
app2.post("/api/auth/select-tenant", { preHandler: authHook }, (req, res) => controller.selectTenant(req, res));
|
|
4012
|
+
app2.post("/api/auth/approve", { preHandler: authHook }, (req, res) => controller.approveUser(req, res));
|
|
4013
|
+
app2.get("/api/auth/pending", { preHandler: authHook }, (req, res) => controller.listPendingUsers(req, res));
|
|
4014
|
+
app2.post("/api/auth/assign-tenant", { preHandler: authHook }, (req, res) => controller.assignTenant(req, res));
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
// src/routes/index.ts
|
|
4018
|
+
var registerLatticeRoutes = (app2) => {
|
|
4019
|
+
app2.post("/api/runs", createRun);
|
|
4020
|
+
app2.post("/api/resume_stream", resumeStream);
|
|
4021
|
+
app2.get(
|
|
4022
|
+
"/api/assistants/:assistantId/:thread_id/memory",
|
|
4023
|
+
{ schema: getAllMemoryItemsSchema },
|
|
4024
|
+
getAllMemoryItems
|
|
4025
|
+
);
|
|
4026
|
+
app2.get(
|
|
4027
|
+
"/api/assistants/:assistantId/:thread_id/state",
|
|
4028
|
+
{ schema: getAgentStateSchema },
|
|
4029
|
+
getAgentState
|
|
4030
|
+
);
|
|
4031
|
+
app2.get(
|
|
4032
|
+
"/api/assistants/:assistantId/memory/:key",
|
|
4033
|
+
{ schema: getMemoryItemSchema },
|
|
4034
|
+
getMemoryItem
|
|
4035
|
+
);
|
|
4036
|
+
app2.put(
|
|
4037
|
+
"/api/assistants/:assistantId/memory/:key",
|
|
4038
|
+
{ schema: setMemoryItemSchema },
|
|
4039
|
+
setMemoryItem
|
|
4040
|
+
);
|
|
4041
|
+
app2.delete(
|
|
4042
|
+
"/api/assistants/:assistantId/memory/:key",
|
|
4043
|
+
{ schema: deleteMemoryItemSchema },
|
|
4044
|
+
deleteMemoryItem
|
|
4045
|
+
);
|
|
4046
|
+
app2.delete(
|
|
4047
|
+
"/api/assistants/:assistantId/memory",
|
|
4048
|
+
{ schema: clearMemorySchema },
|
|
4049
|
+
clearMemory
|
|
4050
|
+
);
|
|
4051
|
+
app2.get("/api/assistants", getAssistantList);
|
|
4052
|
+
app2.get("/api/assistants/:id", getAssistant);
|
|
4053
|
+
app2.post("/api/assistants", createAssistant);
|
|
4054
|
+
app2.put("/api/assistants/:id", updateAssistant);
|
|
4055
|
+
app2.delete("/api/assistants/:id", deleteAssistant);
|
|
4056
|
+
app2.get(
|
|
4057
|
+
"/api/assistants/:assistantId/graph",
|
|
4058
|
+
{ schema: getAgentGraphSchema },
|
|
4059
|
+
getAgentGraph
|
|
4060
|
+
);
|
|
4061
|
+
app2.post(
|
|
4062
|
+
"/api/agent-tasks/trigger",
|
|
4063
|
+
{ schema: triggerAgentTaskSchema },
|
|
4064
|
+
triggerAgentTask
|
|
4065
|
+
);
|
|
4066
|
+
app2.get("/api/assistants/:assistantId/threads", getThreadList);
|
|
4067
|
+
app2.get(
|
|
4068
|
+
"/api/assistants/:assistantId/threads/:threadId",
|
|
4069
|
+
getThread
|
|
4070
|
+
);
|
|
4071
|
+
app2.post("/api/assistants/:assistantId/threads", createThread);
|
|
4072
|
+
app2.put(
|
|
4073
|
+
"/api/assistants/:assistantId/threads/:threadId",
|
|
4074
|
+
updateThread
|
|
4075
|
+
);
|
|
4076
|
+
app2.delete(
|
|
4077
|
+
"/api/assistants/:assistantId/threads/:threadId",
|
|
4078
|
+
deleteThread
|
|
4079
|
+
);
|
|
4080
|
+
app2.get(
|
|
4081
|
+
"/api/config",
|
|
4082
|
+
{ schema: getConfigSchema },
|
|
4083
|
+
getConfig
|
|
4084
|
+
);
|
|
4085
|
+
app2.put(
|
|
4086
|
+
"/api/config",
|
|
4087
|
+
{ schema: updateConfigSchema },
|
|
4088
|
+
updateConfig
|
|
4089
|
+
);
|
|
4090
|
+
app2.get("/api/models", getModels);
|
|
4091
|
+
app2.put("/api/models", updateModels);
|
|
4092
|
+
app2.get("/api/tools", getToolConfigs);
|
|
4093
|
+
app2.get(
|
|
4094
|
+
"/health",
|
|
4095
|
+
{ schema: getHealthSchema },
|
|
4096
|
+
getHealth
|
|
4097
|
+
);
|
|
4098
|
+
app2.get(
|
|
4099
|
+
"/api/assistants/:assistantId/threads/:threadId/schedules",
|
|
4100
|
+
getThreadSchedules
|
|
4101
|
+
);
|
|
4102
|
+
app2.get("/api/schedules/:taskId", getScheduledTask);
|
|
4103
|
+
app2.post("/api/schedules/:taskId/cancel", cancelScheduledTask);
|
|
4104
|
+
app2.post("/api/schedules/:taskId/pause", pauseScheduledTask);
|
|
4105
|
+
app2.post("/api/schedules/:taskId/resume", resumeScheduledTask);
|
|
4106
|
+
app2.get("/api/skills", getSkillList);
|
|
4107
|
+
app2.get(
|
|
4108
|
+
"/api/skills/:id",
|
|
4109
|
+
getSkill
|
|
4110
|
+
);
|
|
4111
|
+
app2.post(
|
|
4112
|
+
"/api/skills",
|
|
4113
|
+
createSkill
|
|
4114
|
+
);
|
|
4115
|
+
app2.put(
|
|
4116
|
+
"/api/skills/:id",
|
|
4117
|
+
updateSkill
|
|
4118
|
+
);
|
|
4119
|
+
app2.delete(
|
|
4120
|
+
"/api/skills/:id",
|
|
4121
|
+
deleteSkill
|
|
4122
|
+
);
|
|
4123
|
+
app2.get(
|
|
4124
|
+
"/api/skills/search/metadata",
|
|
4125
|
+
searchSkillsByMetadata
|
|
4126
|
+
);
|
|
4127
|
+
app2.get(
|
|
4128
|
+
"/api/skills/filter/compatibility",
|
|
4129
|
+
filterSkillsByCompatibility
|
|
4130
|
+
);
|
|
4131
|
+
app2.get(
|
|
4132
|
+
"/api/skills/filter/license",
|
|
4133
|
+
filterSkillsByLicense
|
|
4134
|
+
);
|
|
4135
|
+
registerSandboxProxyRoutes(app2);
|
|
4136
|
+
registerWorkspaceRoutes(app2);
|
|
4137
|
+
registerDatabaseConfigRoutes(app2);
|
|
4138
|
+
registerMetricsServerConfigRoutes(app2);
|
|
4139
|
+
registerUserRoutes(app2);
|
|
4140
|
+
registerTenantRoutes(app2);
|
|
4141
|
+
registerAuthRoutes(app2, {
|
|
4142
|
+
autoApproveUsers: process.env.AUTO_APPROVE_USERS !== "false",
|
|
4143
|
+
allowTenantRegistration: process.env.ALLOW_TENANT_REGISTRATION !== "false"
|
|
4144
|
+
});
|
|
2445
4145
|
};
|
|
2446
4146
|
|
|
2447
4147
|
// src/swagger.ts
|
|
@@ -2854,16 +4554,8 @@ app.addHook("onResponse", (request, reply, done) => {
|
|
|
2854
4554
|
});
|
|
2855
4555
|
app.register(cors, {
|
|
2856
4556
|
origin: true,
|
|
2857
|
-
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
2858
|
-
allowedHeaders:
|
|
2859
|
-
"Content-Type",
|
|
2860
|
-
"Authorization",
|
|
2861
|
-
"X-Requested-With",
|
|
2862
|
-
"x-tenant-id",
|
|
2863
|
-
"x-request-id",
|
|
2864
|
-
"x-assistant-id",
|
|
2865
|
-
"x-thread-id"
|
|
2866
|
-
],
|
|
4557
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
|
|
4558
|
+
allowedHeaders: "*",
|
|
2867
4559
|
exposedHeaders: ["Content-Type"],
|
|
2868
4560
|
credentials: true
|
|
2869
4561
|
});
|