@malloy-publisher/server 0.0.87 → 0.0.89
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/build.ts +26 -0
- package/dist/app/api-doc.yaml +126 -5
- package/dist/app/assets/RenderedResult-BAZuT25g-QakVAbYy.js +2 -0
- package/dist/app/assets/{index-BbW5TZg_.js → index-Bq29VQqL.js} +2 -2
- package/dist/app/assets/{index-2xWCh-ya.css → index-CcIq0aEZ.css} +1 -1
- package/dist/app/assets/index-DZMePHJ5.js +251 -0
- package/dist/app/assets/{index-CIfV3yj1.js → index-TslDWlxH.js} +6 -6
- package/dist/app/assets/{index.umd-x-naS8R7.js → index.umd-BN4_E5KD.js} +259 -259
- package/dist/app/assets/mui-BEbinrI-.js +161 -0
- package/dist/app/assets/vendor-c5ypKtDW.js +17 -0
- package/dist/app/index.html +4 -2
- package/dist/instrumentation.js +67818 -35196
- package/dist/server.js +80404 -82231
- package/package.json +11 -5
- package/publisher.config.json +1 -1
- package/src/config.ts +20 -0
- package/src/constants.ts +14 -0
- package/src/controller/connection.controller.ts +21 -4
- package/src/controller/package.controller.ts +52 -2
- package/src/controller/schedule.controller.ts +3 -3
- package/src/controller/watch-mode.controller.ts +83 -0
- package/src/errors.ts +2 -1
- package/src/logger.ts +9 -0
- package/src/server.ts +33 -19
- package/src/service/connection.ts +159 -161
- package/src/service/model.ts +6 -6
- package/src/service/package.spec.ts +12 -10
- package/src/service/package.ts +15 -8
- package/src/service/project.ts +77 -36
- package/src/service/project_store.spec.ts +83 -56
- package/src/service/project_store.ts +330 -50
- package/src/utils.ts +0 -18
- package/tests/harness/mcp_test_setup.ts +5 -5
- package/dist/app/assets/RenderedResult-BAZuT25g-BMU632YI.js +0 -2
- package/dist/app/assets/index-C7whj6wK.js +0 -432
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloy-publisher/server",
|
|
3
3
|
"description": "Malloy Publisher Server",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.89",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"malloy-publisher": "dist/server.js"
|
|
@@ -10,17 +10,19 @@
|
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "bun test
|
|
14
|
-
"build": "bun generate-api-types &&
|
|
13
|
+
"test": "bun test --timeout 100000",
|
|
14
|
+
"build": "bun generate-api-types && bun build:app && NODE_ENV=production bun run build.ts",
|
|
15
|
+
"build:server-only": "bun generate-api-types && NODE_ENV=production bun run build.ts",
|
|
15
16
|
"start": "NODE_ENV=production node ./dist/server.js",
|
|
16
17
|
"start:dev": "NODE_ENV=development bun --watch src/server.ts",
|
|
17
18
|
"start:instrumented": "node --require ./dist/instrumentation.js ./dist/server.js",
|
|
18
19
|
"lint": "bunx eslint \"./src/**/*.ts\"",
|
|
19
20
|
"format": "bunx prettier --write '**/*.{ts,tsx}'",
|
|
20
|
-
"build:app": "cd ../app &&
|
|
21
|
+
"build:app": "cd ../app && NODE_ENV=production bunx vite build",
|
|
21
22
|
"generate-api-types": "bunx openapi-typescript ../../api-doc.yaml --output src/api.ts"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
25
|
+
"@google-cloud/storage": "^7.16.0",
|
|
24
26
|
"@malloydata/db-bigquery": "^0.0.295",
|
|
25
27
|
"@malloydata/db-duckdb": "^0.0.295",
|
|
26
28
|
"@malloydata/db-mysql": "^0.0.295",
|
|
@@ -36,8 +38,11 @@
|
|
|
36
38
|
"@opentelemetry/sdk-metrics": "^2.0.0",
|
|
37
39
|
"@opentelemetry/sdk-node": "^0.200.0",
|
|
38
40
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
|
41
|
+
"adm-zip": "^0.5.16",
|
|
39
42
|
"async-mutex": "^0.5.0",
|
|
43
|
+
"aws-sdk": "^2.1692.0",
|
|
40
44
|
"body-parser": "^1.20.2",
|
|
45
|
+
"chokidar": "^4.0.3",
|
|
41
46
|
"class-transformer": "^0.5.1",
|
|
42
47
|
"class-validator": "^0.14.1",
|
|
43
48
|
"cors": "^2.8.5",
|
|
@@ -48,6 +53,7 @@
|
|
|
48
53
|
"morgan": "^1.10.0",
|
|
49
54
|
"node-cron": "^3.0.3",
|
|
50
55
|
"recursive-readdir": "^2.2.3",
|
|
56
|
+
"simple-git": "^3.28.0",
|
|
51
57
|
"uuid": "^11.0.3"
|
|
52
58
|
},
|
|
53
59
|
"devDependencies": {
|
|
@@ -56,6 +62,7 @@
|
|
|
56
62
|
"@eslint/eslintrc": "^3.3.1",
|
|
57
63
|
"@eslint/js": "^9.23.0",
|
|
58
64
|
"@faker-js/faker": "^9.4.0",
|
|
65
|
+
"@types/adm-zip": "^0.5.7",
|
|
59
66
|
"@types/cors": "^2.8.12",
|
|
60
67
|
"@types/express": "^4.17.14",
|
|
61
68
|
"@types/morgan": "^1.9.9",
|
|
@@ -65,7 +72,6 @@
|
|
|
65
72
|
"@types/supertest": "^6.0.2",
|
|
66
73
|
"@typescript-eslint/eslint-plugin": "^8.6.0",
|
|
67
74
|
"@typescript-eslint/parser": "^8.6.0",
|
|
68
|
-
"aws-sdk": "^2.1692.0",
|
|
69
75
|
"eslint-config-prettier": "^9.1.0",
|
|
70
76
|
"eslint-plugin-prettier": "^5.2.1",
|
|
71
77
|
"mock-aws-s3": "^4.0.2",
|
package/publisher.config.json
CHANGED
package/src/config.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { PUBLISHER_CONFIG_NAME } from "./constants";
|
|
4
|
+
|
|
5
|
+
type FilesystemPath = `./${string}` | `../${string}` | `/${string}`;
|
|
6
|
+
type GcsPath = `gs://${string}`;
|
|
7
|
+
export type PublisherConfig = {
|
|
8
|
+
frozenConfig: boolean;
|
|
9
|
+
projects: Record<string, FilesystemPath | GcsPath>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const getPublisherConfig = (serverRoot: string): PublisherConfig => {
|
|
13
|
+
const publisherConfigPath = path.join(serverRoot, PUBLISHER_CONFIG_NAME);
|
|
14
|
+
return JSON.parse(fs.readFileSync(publisherConfigPath, "utf8"));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const isPublisherConfigFrozen = (serverRoot: string) => {
|
|
18
|
+
const publisherConfig = getPublisherConfig(serverRoot);
|
|
19
|
+
return Boolean(publisherConfig.frozenConfig);
|
|
20
|
+
};
|
package/src/constants.ts
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
|
+
import fs from "fs";
|
|
1
2
|
export const API_PREFIX = "/api/v0";
|
|
2
3
|
export const README_NAME = "README.md";
|
|
4
|
+
export const PUBLISHER_CONFIG_NAME = "publisher.config.json";
|
|
5
|
+
export const PACKAGE_MANIFEST_NAME = "publisher.json";
|
|
6
|
+
export const CONNECTIONS_MANIFEST_NAME = "publisher.connections.json";
|
|
7
|
+
export const MODEL_FILE_SUFFIX = ".malloy";
|
|
8
|
+
export const NOTEBOOK_FILE_SUFFIX = ".malloynb";
|
|
9
|
+
export const ROW_LIMIT = 1000;
|
|
10
|
+
|
|
11
|
+
export let publisherPath = "/etc/publisher";
|
|
12
|
+
try {
|
|
13
|
+
fs.accessSync(publisherPath, fs.constants.W_OK);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
publisherPath = "/tmp/publisher";
|
|
16
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
RunSQLOptions,
|
|
3
|
+
TableSourceDef,
|
|
4
|
+
TestableConnection,
|
|
5
|
+
} from "@malloydata/malloy";
|
|
2
6
|
import { Connection, PersistSQLResults } from "@malloydata/malloy/connection";
|
|
3
|
-
import { TableSourceDef } from "../../../../../malloy/packages/malloy/dist";
|
|
4
7
|
import { components } from "../api";
|
|
5
8
|
import { ConnectionError } from "../errors";
|
|
6
9
|
import { logger } from "../logger";
|
|
@@ -11,6 +14,7 @@ import {
|
|
|
11
14
|
import { ProjectStore } from "../service/project_store";
|
|
12
15
|
|
|
13
16
|
type ApiConnection = components["schemas"]["Connection"];
|
|
17
|
+
type ApiConnectionStatus = components["schemas"]["ConnectionStatus"];
|
|
14
18
|
type ApiSqlSource = components["schemas"]["SqlSource"];
|
|
15
19
|
type ApiTableSource = components["schemas"]["TableSource"];
|
|
16
20
|
type ApiQueryData = components["schemas"]["QueryData"];
|
|
@@ -57,15 +61,28 @@ export class ConnectionController {
|
|
|
57
61
|
return getTablesForSchema(connection, schemaName);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
public async testConnection(
|
|
64
|
+
public async testConnection(
|
|
65
|
+
projectName: string,
|
|
66
|
+
connectionName: string,
|
|
67
|
+
): Promise<ApiConnectionStatus> {
|
|
61
68
|
const project = await this.projectStore.getProject(projectName, false);
|
|
62
69
|
const connection = project.getMalloyConnection(
|
|
63
70
|
connectionName,
|
|
64
71
|
) as Connection;
|
|
65
72
|
try {
|
|
66
73
|
await (connection as TestableConnection).test();
|
|
74
|
+
return {
|
|
75
|
+
status: "ok",
|
|
76
|
+
errorMessage: "",
|
|
77
|
+
};
|
|
67
78
|
} catch (error) {
|
|
68
|
-
|
|
79
|
+
if (error instanceof ConnectionError) {
|
|
80
|
+
return {
|
|
81
|
+
status: "failed",
|
|
82
|
+
errorMessage: error.message,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
69
86
|
}
|
|
70
87
|
}
|
|
71
88
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { components } from "../api";
|
|
2
|
+
import { publisherPath } from "../constants";
|
|
2
3
|
import { BadRequestError, FrozenConfigError } from "../errors";
|
|
3
4
|
import { ProjectStore } from "../service/project_store";
|
|
4
5
|
|
|
@@ -22,8 +23,12 @@ export class PackageController {
|
|
|
22
23
|
reload: boolean,
|
|
23
24
|
): Promise<ApiPackage> {
|
|
24
25
|
const project = await this.projectStore.getProject(projectName, false);
|
|
25
|
-
const
|
|
26
|
-
|
|
26
|
+
const _package = await project.getPackage(packageName, reload);
|
|
27
|
+
const packageLocation = _package.getPackageMetadata().location;
|
|
28
|
+
if (reload && packageLocation) {
|
|
29
|
+
await this.downloadPackage(projectName, packageName, packageLocation);
|
|
30
|
+
}
|
|
31
|
+
return _package.getPackageMetadata();
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
async addPackage(projectName: string, body: ApiPackage) {
|
|
@@ -34,6 +39,9 @@ export class PackageController {
|
|
|
34
39
|
throw new BadRequestError("Package name is required");
|
|
35
40
|
}
|
|
36
41
|
const project = await this.projectStore.getProject(projectName, false);
|
|
42
|
+
if (body.location) {
|
|
43
|
+
await this.downloadPackage(projectName, body.name, body.location);
|
|
44
|
+
}
|
|
37
45
|
return project.addPackage(body.name);
|
|
38
46
|
}
|
|
39
47
|
|
|
@@ -54,6 +62,48 @@ export class PackageController {
|
|
|
54
62
|
throw new FrozenConfigError();
|
|
55
63
|
}
|
|
56
64
|
const project = await this.projectStore.getProject(projectName, false);
|
|
65
|
+
if (body.location) {
|
|
66
|
+
await this.downloadPackage(projectName, packageName, body.location);
|
|
67
|
+
}
|
|
57
68
|
return project.updatePackage(packageName, body);
|
|
58
69
|
}
|
|
70
|
+
|
|
71
|
+
private async downloadPackage(
|
|
72
|
+
projectName: string,
|
|
73
|
+
packageName: string,
|
|
74
|
+
packageLocation: string,
|
|
75
|
+
) {
|
|
76
|
+
const absoluteTargetPath = `${publisherPath}/${projectName}/${packageName}`;
|
|
77
|
+
if (
|
|
78
|
+
packageLocation.startsWith("https://") ||
|
|
79
|
+
packageLocation.startsWith("git@")
|
|
80
|
+
) {
|
|
81
|
+
await this.projectStore.downloadGitHubDirectory(
|
|
82
|
+
packageLocation,
|
|
83
|
+
absoluteTargetPath,
|
|
84
|
+
);
|
|
85
|
+
} else if (packageLocation.startsWith("gs://")) {
|
|
86
|
+
await this.projectStore.downloadGcsDirectory(
|
|
87
|
+
packageLocation,
|
|
88
|
+
projectName,
|
|
89
|
+
absoluteTargetPath,
|
|
90
|
+
);
|
|
91
|
+
} else if (packageLocation.startsWith("s3://")) {
|
|
92
|
+
await this.projectStore.downloadS3Directory(
|
|
93
|
+
packageLocation,
|
|
94
|
+
projectName,
|
|
95
|
+
absoluteTargetPath,
|
|
96
|
+
);
|
|
97
|
+
} else if (packageLocation.startsWith("/")) {
|
|
98
|
+
if (packageLocation.endsWith(".zip")) {
|
|
99
|
+
packageLocation =
|
|
100
|
+
await this.projectStore.unzipProject(packageLocation);
|
|
101
|
+
}
|
|
102
|
+
await this.projectStore.mountLocalDirectory(
|
|
103
|
+
packageLocation,
|
|
104
|
+
absoluteTargetPath,
|
|
105
|
+
projectName,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
59
109
|
}
|
|
@@ -14,8 +14,8 @@ export class ScheduleController {
|
|
|
14
14
|
projectName: string,
|
|
15
15
|
packageName: string,
|
|
16
16
|
): Promise<ApiSchedule[]> {
|
|
17
|
-
const project = await this.projectStore
|
|
18
|
-
const p = await project
|
|
19
|
-
return p
|
|
17
|
+
const project = await this.projectStore?.getProject?.(projectName);
|
|
18
|
+
const p = await project?.getPackage?.(packageName);
|
|
19
|
+
return p?.listSchedules?.();
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import chokidar, { FSWatcher } from "chokidar";
|
|
2
|
+
import { RequestHandler } from "express";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { components } from "../api";
|
|
5
|
+
import { logger } from "../logger";
|
|
6
|
+
import { ProjectStore } from "../service/project_store";
|
|
7
|
+
|
|
8
|
+
type StartWatchReq = components["schemas"]["StartWatchRequest"];
|
|
9
|
+
type WatchStatusRes = components["schemas"]["WatchStatus"];
|
|
10
|
+
type Handler<Req = {}, Res = void> = RequestHandler<{}, Res, Req>;
|
|
11
|
+
|
|
12
|
+
export class WatchModeController {
|
|
13
|
+
watchingPath: string | null;
|
|
14
|
+
watchingProjectName: string | null;
|
|
15
|
+
watcher: FSWatcher;
|
|
16
|
+
|
|
17
|
+
constructor(private projectStore: ProjectStore) {
|
|
18
|
+
this.watchingPath = null;
|
|
19
|
+
this.watchingProjectName = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public getWatchStatus: Handler<{}, WatchStatusRes> = async (_req, res) => {
|
|
23
|
+
return res.json({
|
|
24
|
+
enabled: !!this.watchingPath,
|
|
25
|
+
watchingPath: this.watchingPath,
|
|
26
|
+
projectName: this.watchingProjectName ?? undefined,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
public startWatching: Handler<StartWatchReq> = async (req, res) => {
|
|
31
|
+
const projectManifest = await ProjectStore.reloadProjectManifest(
|
|
32
|
+
this.projectStore.serverRootPath,
|
|
33
|
+
);
|
|
34
|
+
this.watchingProjectName = req.body.projectName;
|
|
35
|
+
this.watchingPath = path.join(
|
|
36
|
+
this.projectStore.serverRootPath,
|
|
37
|
+
projectManifest.projects[req.body.projectName],
|
|
38
|
+
);
|
|
39
|
+
this.watcher = chokidar.watch(this.watchingPath, {
|
|
40
|
+
ignored: (path, stats) =>
|
|
41
|
+
!!stats?.isFile() &&
|
|
42
|
+
!path.endsWith(".malloy") &&
|
|
43
|
+
!path.endsWith(".md"),
|
|
44
|
+
ignoreInitial: true,
|
|
45
|
+
});
|
|
46
|
+
const reloadProject = async () => {
|
|
47
|
+
// Overwrite the project with it's existing metadata to trigger a re-read
|
|
48
|
+
const project = await this.projectStore.getProject(
|
|
49
|
+
req.body.projectName,
|
|
50
|
+
true,
|
|
51
|
+
);
|
|
52
|
+
await this.projectStore.addProject(project.metadata);
|
|
53
|
+
logger.info(`Reloaded ${req.body.projectName}`);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
this.watcher.on("add", async (path) => {
|
|
57
|
+
logger.info(
|
|
58
|
+
`Detected new file ${path}, reloading ${req.body.projectName}`,
|
|
59
|
+
);
|
|
60
|
+
await reloadProject();
|
|
61
|
+
});
|
|
62
|
+
this.watcher.on("unlink", async (path) => {
|
|
63
|
+
logger.info(
|
|
64
|
+
`Detected deletion of ${path}, reloading ${req.body.projectName}`,
|
|
65
|
+
);
|
|
66
|
+
await reloadProject();
|
|
67
|
+
});
|
|
68
|
+
this.watcher.on("change", async (path) => {
|
|
69
|
+
logger.info(
|
|
70
|
+
`Detected change on ${path}, reloading ${req.body.projectName}`,
|
|
71
|
+
);
|
|
72
|
+
await reloadProject();
|
|
73
|
+
});
|
|
74
|
+
res.json();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
public stopWatchMode: Handler = async (_req, res) => {
|
|
78
|
+
this.watcher.close();
|
|
79
|
+
this.watchingPath = null;
|
|
80
|
+
this.watchingProjectName = null;
|
|
81
|
+
res.json();
|
|
82
|
+
};
|
|
83
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MalloyError } from "@malloydata/malloy";
|
|
2
|
+
import { PUBLISHER_CONFIG_NAME } from "./constants";
|
|
2
3
|
|
|
3
4
|
export function internalErrorToHttpError(error: Error) {
|
|
4
5
|
if (error instanceof BadRequestError) {
|
|
@@ -84,7 +85,7 @@ export class ModelCompilationError extends Error {
|
|
|
84
85
|
|
|
85
86
|
export class FrozenConfigError extends Error {
|
|
86
87
|
constructor(
|
|
87
|
-
message =
|
|
88
|
+
message = `Publisher config can't be updated when ${PUBLISHER_CONFIG_NAME} has { "frozenConfig": true }`,
|
|
88
89
|
) {
|
|
89
90
|
super(message);
|
|
90
91
|
}
|
package/src/logger.ts
CHANGED
|
@@ -20,11 +20,20 @@ export const logger = winston.createLogger({
|
|
|
20
20
|
|
|
21
21
|
export const loggerMiddleware: RequestHandler = (req, res, next) => {
|
|
22
22
|
const startTime = performance.now();
|
|
23
|
+
const resJson = res.json;
|
|
24
|
+
res.json = (body: unknown) => {
|
|
25
|
+
res.locals.body = body;
|
|
26
|
+
return resJson.call(res, body);
|
|
27
|
+
};
|
|
23
28
|
res.on("finish", () => {
|
|
24
29
|
const endTime = performance.now();
|
|
25
30
|
logger.info(`${req.method} ${req.url}`, {
|
|
26
31
|
statusCode: res.statusCode,
|
|
27
32
|
duration: endTime - startTime,
|
|
33
|
+
payload: req.body,
|
|
34
|
+
response: res.locals.body,
|
|
35
|
+
params: req.params,
|
|
36
|
+
query: req.query,
|
|
28
37
|
});
|
|
29
38
|
});
|
|
30
39
|
next();
|
package/src/server.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { ModelController } from "./controller/model.controller";
|
|
|
12
12
|
import { PackageController } from "./controller/package.controller";
|
|
13
13
|
import { QueryController } from "./controller/query.controller";
|
|
14
14
|
import { ScheduleController } from "./controller/schedule.controller";
|
|
15
|
+
import { WatchModeController } from "./controller/watch-mode.controller";
|
|
15
16
|
import { internalErrorToHttpError, NotImplementedError } from "./errors";
|
|
16
17
|
import { logger, loggerMiddleware } from "./logger";
|
|
17
18
|
import { initializeMcpServer } from "./mcp/server";
|
|
@@ -62,7 +63,7 @@ function parseArgs() {
|
|
|
62
63
|
parseArgs();
|
|
63
64
|
|
|
64
65
|
const PUBLISHER_PORT = Number(process.env.PUBLISHER_PORT || 4000);
|
|
65
|
-
const PUBLISHER_HOST = process.env.PUBLISHER_HOST || "
|
|
66
|
+
const PUBLISHER_HOST = process.env.PUBLISHER_HOST || "0.0.0.0";
|
|
66
67
|
const MCP_PORT = Number(process.env.MCP_PORT || 4040);
|
|
67
68
|
const MCP_ENDPOINT = "/mcp";
|
|
68
69
|
// Find the app directory - handle NPX vs local execution
|
|
@@ -83,6 +84,7 @@ app.use(loggerMiddleware);
|
|
|
83
84
|
app.use(cors());
|
|
84
85
|
|
|
85
86
|
const projectStore = new ProjectStore(SERVER_ROOT);
|
|
87
|
+
const watchModeController = new WatchModeController(projectStore);
|
|
86
88
|
const connectionController = new ConnectionController(projectStore);
|
|
87
89
|
const modelController = new ModelController(projectStore);
|
|
88
90
|
const packageController = new PackageController(projectStore);
|
|
@@ -90,7 +92,7 @@ const databaseController = new DatabaseController(projectStore);
|
|
|
90
92
|
const queryController = new QueryController(projectStore);
|
|
91
93
|
const scheduleController = new ScheduleController(projectStore);
|
|
92
94
|
|
|
93
|
-
const mcpApp = express();
|
|
95
|
+
export const mcpApp = express();
|
|
94
96
|
|
|
95
97
|
mcpApp.use(MCP_ENDPOINT, express.json());
|
|
96
98
|
mcpApp.use(MCP_ENDPOINT, cors());
|
|
@@ -206,6 +208,17 @@ const setVersionIdError = (res: express.Response) => {
|
|
|
206
208
|
app.use(cors());
|
|
207
209
|
app.use(bodyParser.json());
|
|
208
210
|
|
|
211
|
+
app.get(`${API_PREFIX}/status`, async (_req, res) => {
|
|
212
|
+
res.status(200).json({
|
|
213
|
+
timestamp: Date.now(),
|
|
214
|
+
projects: await projectStore.listProjects(),
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
app.get(`${API_PREFIX}/watch-mode/status`, watchModeController.getWatchStatus);
|
|
219
|
+
app.post(`${API_PREFIX}/watch-mode/start`, watchModeController.startWatching);
|
|
220
|
+
app.post(`${API_PREFIX}/watch-mode/stop`, watchModeController.stopWatchMode);
|
|
221
|
+
|
|
209
222
|
app.get(`${API_PREFIX}/projects`, async (_req, res) => {
|
|
210
223
|
try {
|
|
211
224
|
res.status(200).json(await projectStore.listProjects());
|
|
@@ -218,7 +231,9 @@ app.get(`${API_PREFIX}/projects`, async (_req, res) => {
|
|
|
218
231
|
|
|
219
232
|
app.post(`${API_PREFIX}/projects`, async (req, res) => {
|
|
220
233
|
try {
|
|
221
|
-
|
|
234
|
+
logger.info("Adding project", { body: req.body });
|
|
235
|
+
const project = await projectStore.addProject(req.body);
|
|
236
|
+
res.status(200).json(await project.serialize());
|
|
222
237
|
} catch (error) {
|
|
223
238
|
logger.error(error);
|
|
224
239
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -232,7 +247,7 @@ app.get(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
|
232
247
|
req.params.projectName,
|
|
233
248
|
req.query.reload === "true",
|
|
234
249
|
);
|
|
235
|
-
res.status(200).json(project.
|
|
250
|
+
res.status(200).json(await project.serialize());
|
|
236
251
|
} catch (error) {
|
|
237
252
|
logger.error(error);
|
|
238
253
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -242,7 +257,8 @@ app.get(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
|
242
257
|
|
|
243
258
|
app.patch(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
244
259
|
try {
|
|
245
|
-
|
|
260
|
+
const project = await projectStore.updateProject(req.body);
|
|
261
|
+
res.status(200).json(await project.serialize());
|
|
246
262
|
} catch (error) {
|
|
247
263
|
logger.error(error);
|
|
248
264
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -252,9 +268,8 @@ app.patch(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
|
252
268
|
|
|
253
269
|
app.delete(`${API_PREFIX}/projects/:projectName`, async (req, res) => {
|
|
254
270
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
);
|
|
271
|
+
const project = await projectStore.deleteProject(req.params.projectName);
|
|
272
|
+
res.status(200).json(await project.serialize());
|
|
258
273
|
} catch (error) {
|
|
259
274
|
logger.error(error);
|
|
260
275
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -296,12 +311,11 @@ app.get(
|
|
|
296
311
|
`${API_PREFIX}/projects/:projectName/connections/:connectionName/test`,
|
|
297
312
|
async (req, res) => {
|
|
298
313
|
try {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
req.params.connectionName,
|
|
303
|
-
),
|
|
314
|
+
const connectionStatus = await connectionController.testConnection(
|
|
315
|
+
req.params.projectName,
|
|
316
|
+
req.params.connectionName,
|
|
304
317
|
);
|
|
318
|
+
res.status(200).json(connectionStatus);
|
|
305
319
|
} catch (error) {
|
|
306
320
|
logger.error(error);
|
|
307
321
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -445,9 +459,11 @@ app.get(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
|
445
459
|
|
|
446
460
|
app.post(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
447
461
|
try {
|
|
448
|
-
|
|
449
|
-
|
|
462
|
+
const _package = await packageController.addPackage(
|
|
463
|
+
req.params.projectName,
|
|
464
|
+
req.body,
|
|
450
465
|
);
|
|
466
|
+
res.status(200).json(_package?.getPackageMetadata());
|
|
451
467
|
} catch (error) {
|
|
452
468
|
logger.error(error);
|
|
453
469
|
const { json, status } = internalErrorToHttpError(error as Error);
|
|
@@ -705,6 +721,7 @@ app.use(
|
|
|
705
721
|
);
|
|
706
722
|
|
|
707
723
|
const mainServer = http.createServer(app);
|
|
724
|
+
|
|
708
725
|
mainServer.listen(PUBLISHER_PORT, PUBLISHER_HOST, () => {
|
|
709
726
|
const address = mainServer.address() as AddressInfo;
|
|
710
727
|
logger.info(
|
|
@@ -716,9 +733,6 @@ mainServer.listen(PUBLISHER_PORT, PUBLISHER_HOST, () => {
|
|
|
716
733
|
);
|
|
717
734
|
}
|
|
718
735
|
});
|
|
719
|
-
|
|
720
|
-
const mcpHttpServer = mcpApp.listen(MCP_PORT, PUBLISHER_HOST, () => {
|
|
736
|
+
mcpApp.listen(MCP_PORT, PUBLISHER_HOST, () => {
|
|
721
737
|
logger.info(`MCP server listening at http://${PUBLISHER_HOST}:${MCP_PORT}`);
|
|
722
738
|
});
|
|
723
|
-
|
|
724
|
-
export { app, mainServer as httpServer, mcpApp, mcpHttpServer };
|