Skip to content

Commit afa0841

Browse files
author
sunlei
committed
feat: add node-service
1 parent d5bc540 commit afa0841

32 files changed

+8530
-0
lines changed

server/node-service/.gitignore

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/packages/*/node_modules
6+
/packages/*/dist
7+
/packages/*/tsdoc-metadata.json
8+
/.pnp
9+
.pnp.js
10+
11+
# testing
12+
/**/coverage
13+
14+
# production
15+
/build
16+
17+
# misc
18+
.DS_Store
19+
.env.local
20+
.env.development.local
21+
.env.test.local
22+
.env.production.local
23+
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
/out
29+
.idea
30+
.storybook-out/
31+
cypress/videos
32+
cypress/screenshots
33+
/cypress.env.json
34+
35+
storybook-static/*
36+
build-storybook.log
37+
38+
TODO
39+
40+
.yarn/*
41+
!.yarn/cache
42+
!.yarn/patches
43+
!.yarn/plugins
44+
!.yarn/releases
45+
!.yarn/sdks
46+
!.yarn/versions
47+
48+
/ossutil_output
49+
package-lock.json
50+
51+
op.mjs

server/node-service/.yarn/releases/yarn-3.3.1.cjs

Lines changed: 823 additions & 0 deletions
Large diffs are not rendered by default.

server/node-service/.yarnrc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
nodeLinker: node-modules
2+
3+
yarnPath: .yarn/releases/yarn-3.3.1.cjs

server/node-service/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Node Service
2+
3+
Write data source plugins with TypeScript.
4+
5+
- [How to write data source plugin](#)
6+
7+
### Develop
8+
9+
```bash
10+
yarn dev
11+
```
12+
13+
### Production
14+
15+
```bash
16+
yarn build
17+
yarn start
18+
```

server/node-service/jest.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: "ts-jest",
4+
testEnvironment: "node",
5+
testTimeout: 60000,
6+
};

server/node-service/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "taco-js-api",
3+
"version": "0.1.0",
4+
"private": true,
5+
"engines": {
6+
"node": "^14.18.0 || >=16.0.0"
7+
},
8+
"main": "src/server.ts",
9+
"scripts": {
10+
"dev": "nodemon src/server.ts",
11+
"start": "node ./build/src/server.js",
12+
"test": "jest",
13+
"build": "rm -rf build/ && tsc && cp -r src/static build/src/static"
14+
},
15+
"devDependencies": {
16+
"@types/jest": "^29.2.4",
17+
"jest": "^29.3.1",
18+
"nodemon": "^2.0.20",
19+
"ts-jest": "^29.0.3",
20+
"ts-node": "^10.9.1"
21+
},
22+
"dependencies": {
23+
"@aws-sdk/client-s3": "^3.238.0",
24+
"@aws-sdk/s3-request-presigner": "^3.241.0",
25+
"@types/axios": "^0.14.0",
26+
"@types/express": "^4.17.14",
27+
"@types/lodash": "^4.14.190",
28+
"@types/morgan": "^1.9.3",
29+
"@types/node": "^18.11.18",
30+
"axios": "^1.2.0",
31+
"express": "^4.18.2",
32+
"express-async-errors": "^3.1.1",
33+
"lodash": "^4.17.21",
34+
"loglevel": "^1.8.1",
35+
"morgan": "^1.10.0",
36+
"openblocks-core": "^0.0.5",
37+
"openblocks-sdk": "^0.0.30",
38+
"stylis": "^4.1.3",
39+
"typescript": "^4.9.3"
40+
},
41+
"packageManager": "yarn@3.3.1"
42+
}

server/node-service/src/api/api.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
2+
import log from "loglevel";
3+
import { ApiResponse } from "./apiResponse";
4+
5+
const SERVER_HOST = process.env.API_HOST ?? "";
6+
const REQUEST_TIMEOUT_MS = 20000;
7+
const API_REQUEST_HEADERS = {
8+
"Content-Type": "application/json",
9+
};
10+
11+
function apiFailureResponseInterceptor(error: any) {
12+
if (axios.isAxiosError(error)) {
13+
let message = `AxiosError:${error.code}`;
14+
const response = error.response;
15+
if (response) {
16+
message = message + ` status:${response.status}`;
17+
if (response.data) {
18+
const data = response.data as ApiResponse;
19+
message = message + ` code:${data.code} message:${data.message}`;
20+
}
21+
} else if (error.request) {
22+
log.error(error.request);
23+
} else {
24+
log.error("Error", error.message);
25+
}
26+
return Promise.reject(message);
27+
}
28+
return Promise.reject(error);
29+
}
30+
31+
const axiosInstance: AxiosInstance = axios.create({
32+
baseURL: `${SERVER_HOST}/api/`,
33+
timeout: REQUEST_TIMEOUT_MS,
34+
headers: API_REQUEST_HEADERS,
35+
withCredentials: true,
36+
});
37+
axiosInstance.interceptors.request.use((c) => c);
38+
axiosInstance.interceptors.response.use((r) => r, apiFailureResponseInterceptor);
39+
40+
class Api {
41+
static get(url: string, queryParams?: any, config: Partial<AxiosRequestConfig> = {}) {
42+
return axiosInstance.request({
43+
url,
44+
method: "GET",
45+
params: queryParams,
46+
...config,
47+
});
48+
}
49+
50+
static post(
51+
url: string,
52+
body?: any,
53+
queryParams?: any,
54+
config: Partial<AxiosRequestConfig> = {}
55+
) {
56+
return axiosInstance.request({
57+
method: "POST",
58+
url,
59+
data: body,
60+
params: queryParams,
61+
...config,
62+
});
63+
}
64+
65+
static put(url: string, body?: any, queryParams?: any, config: Partial<AxiosRequestConfig> = {}) {
66+
return axiosInstance.request({
67+
method: "PUT",
68+
url,
69+
params: queryParams,
70+
data: body,
71+
...config,
72+
});
73+
}
74+
75+
static patch(
76+
url: string,
77+
body?: any,
78+
queryParams?: any,
79+
config: Partial<AxiosRequestConfig> = {}
80+
) {
81+
return axiosInstance.request({
82+
method: "PATCH",
83+
url,
84+
data: body,
85+
params: queryParams,
86+
...config,
87+
});
88+
}
89+
90+
static delete(url: string, queryParams?: any, config: Partial<AxiosRequestConfig> = {}) {
91+
return axiosInstance.request({
92+
method: "DELETE",
93+
url,
94+
params: queryParams,
95+
...config,
96+
});
97+
}
98+
}
99+
100+
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
101+
102+
export default Api;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type ApiResponse<T = any> = {
2+
success: boolean;
3+
code: number;
4+
message: string;
5+
data: T;
6+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { AxiosPromise } from "axios";
2+
import { ApiResponse } from "./apiResponse";
3+
import Api from "./api";
4+
5+
const QUERY_TIMEOUT_BUFFER_MS = 5000;
6+
const DEFAULT_EXECUTE_ACTION_TIMEOUT_MS = 15000;
7+
8+
type QueryExecuteRequest = {
9+
libraryQueryName: string;
10+
params?: { key: string; value: string }[];
11+
};
12+
13+
export class QueryApi extends Api {
14+
static executeQuery(
15+
request: QueryExecuteRequest,
16+
cookie?: string,
17+
timeout?: number
18+
): AxiosPromise<ApiResponse> {
19+
return Api.post("query/execute-from-node", request, undefined, {
20+
headers: { Cookie: cookie },
21+
timeout: timeout ? timeout + QUERY_TIMEOUT_BUFFER_MS : DEFAULT_EXECUTE_ACTION_TIMEOUT_MS,
22+
});
23+
}
24+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export class ServiceError extends Error {
2+
code: number;
3+
constructor(message: string, code: number = 500) {
4+
super(message);
5+
this.code = code;
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Translator as CoreTranslator } from "openblocks-core";
2+
3+
export class I18n<Messages extends object> extends CoreTranslator<Messages> {
4+
constructor(fileData: object, locales?: string[]) {
5+
super(fileData, "", locales);
6+
}
7+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export function toString(value: any): string {
2+
if (value === undefined || value === null) {
3+
return "";
4+
}
5+
if (typeof value === "string") {
6+
return value;
7+
}
8+
if (value instanceof Date) {
9+
return value.toISOString();
10+
}
11+
if (value instanceof RegExp) {
12+
return value.toString();
13+
}
14+
return JSON.stringify(value, (k, v) => {
15+
switch (typeof v) {
16+
case "bigint":
17+
return v.toString();
18+
}
19+
return v;
20+
});
21+
}
22+
23+
export function toNumber(value: any): number {
24+
if (value === undefined || value === null || value === "" || isNaN(value)) {
25+
return 0;
26+
}
27+
if (typeof value === "number") {
28+
return value;
29+
}
30+
const result = Number(value);
31+
return Number.isFinite(result) ? result : 0;
32+
}
33+
34+
export function toBoolean(value: any): boolean {
35+
if (value === "0" || value === "false") {
36+
return false;
37+
}
38+
return !!value;
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Request, Response } from "express";
2+
import _ from "lodash";
3+
import * as pluginServices from "../services/plugin";
4+
5+
export async function listPlugins(req: Request, res: Response) {
6+
const ctx = pluginServices.getPluginContext(req);
7+
const result = pluginServices.listPlugins(ctx);
8+
return res.status(200).json(result);
9+
}
10+
11+
export async function runPluginQuery(req: Request, res: Response) {
12+
const { pluginName, dsl, context, dataSourceConfig } = req.body;
13+
const ctx = pluginServices.getPluginContext(req);
14+
const result = await pluginServices.runPluginQuery(
15+
pluginName,
16+
dsl,
17+
context,
18+
dataSourceConfig,
19+
ctx
20+
);
21+
return res.status(200).json(result);
22+
}
23+
24+
export async function validatePluginDataSourceConfig(req: Request, res: Response) {
25+
const { pluginName, dataSourceConfig } = req.body;
26+
const ctx = pluginServices.getPluginContext(req);
27+
const result = await pluginServices.validatePluginDataSourceConfig(
28+
pluginName,
29+
dataSourceConfig,
30+
ctx
31+
);
32+
return res.status(200).json(result);
33+
}

0 commit comments

Comments
 (0)