lupine.api 1.1.58 → 1.1.59
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/README.md +3 -3
- package/admin/admin-about.tsx +12 -16
- package/admin/admin-config.tsx +47 -44
- package/admin/admin-css.tsx +3 -3
- package/admin/admin-db.tsx +75 -75
- package/admin/admin-frame-helper.tsx +364 -364
- package/admin/admin-frame.tsx +164 -164
- package/admin/admin-index.tsx +65 -65
- package/admin/admin-login.tsx +111 -111
- package/admin/admin-menu-edit.tsx +637 -637
- package/admin/admin-menu-list.tsx +87 -87
- package/admin/admin-page-edit.tsx +564 -564
- package/admin/admin-page-list.tsx +83 -83
- package/admin/admin-performance.tsx +28 -28
- package/admin/admin-release.tsx +427 -426
- package/admin/admin-resources.tsx +382 -382
- package/admin/admin-shell.tsx +89 -89
- package/admin/admin-table-data.tsx +146 -146
- package/admin/admin-table-list.tsx +230 -230
- package/admin/admin-test-animations.tsx +395 -395
- package/admin/admin-test-component.tsx +823 -808
- package/admin/admin-test-edit.tsx +319 -319
- package/admin/admin-test-themes.tsx +56 -56
- package/admin/admin-tokens.tsx +338 -338
- package/admin/design/admin-design.tsx +174 -174
- package/admin/design/block-grid.tsx +36 -36
- package/admin/design/block-grid1.tsx +21 -21
- package/admin/design/block-paragraph.tsx +19 -19
- package/admin/design/block-title.tsx +19 -19
- package/admin/design/design-block-box.tsx +140 -140
- package/admin/design/drag-data.tsx +24 -24
- package/admin/index.ts +9 -9
- package/admin/package.json +15 -15
- package/admin/tsconfig.json +127 -127
- package/dev/copy-folder.js +32 -32
- package/dev/cp-index-html.js +69 -69
- package/dev/file-utils.js +12 -12
- package/dev/index.js +18 -19
- package/dev/package.json +12 -12
- package/dev/plugin-ifelse.js +168 -168
- package/dev/plugin-ifelse.test.js +37 -37
- package/dev/run-cmd.js +14 -14
- package/dev/send-request.js +12 -12
- package/package.json +55 -55
- package/src/admin-api/admin-api-helper.ts +210 -205
- package/src/admin-api/admin-api.ts +65 -65
- package/src/admin-api/admin-auth.ts +152 -146
- package/src/admin-api/admin-config.ts +94 -84
- package/src/admin-api/admin-csv.ts +94 -94
- package/src/admin-api/admin-db.ts +269 -269
- package/src/admin-api/admin-menu.ts +135 -135
- package/src/admin-api/admin-page.ts +135 -135
- package/src/admin-api/admin-performance.ts +128 -128
- package/src/admin-api/admin-release.ts +703 -700
- package/src/admin-api/admin-resources.ts +318 -318
- package/src/admin-api/admin-token-helper.ts +82 -79
- package/src/admin-api/admin-tokens.ts +90 -90
- package/src/admin-api/index.ts +2 -2
- package/src/admin-api/web-config-api.ts +19 -19
- package/src/api/api-cache.ts +103 -103
- package/src/api/api-helper.ts +44 -44
- package/src/api/api-module.ts +67 -60
- package/src/api/api-router.ts +177 -177
- package/src/api/api-shared-storage.ts +64 -64
- package/src/api/async-storage.ts +5 -5
- package/src/api/debug-service.ts +56 -56
- package/src/api/encode-html.ts +27 -27
- package/src/api/handle-status.ts +75 -75
- package/src/api/index.ts +15 -16
- package/src/api/mini-web-socket.ts +270 -270
- package/src/api/server-content-type.ts +82 -82
- package/src/api/server-render.ts +235 -215
- package/src/api/shell-service.ts +74 -74
- package/src/api/simple-storage.ts +80 -80
- package/src/api/static-server.ts +128 -125
- package/src/api/to-client-delivery.ts +26 -26
- package/src/app/app-cache.ts +55 -55
- package/src/app/app-helper.ts +62 -62
- package/src/app/app-message.ts +109 -109
- package/src/app/app-shared-storage.ts +363 -363
- package/src/app/app-start.ts +136 -136
- package/src/app/cleanup-exit.ts +16 -16
- package/src/app/host-to-path.ts +38 -38
- package/src/app/index.ts +11 -11
- package/src/app/process-dev-requests.ts +130 -130
- package/src/app/web-listener.ts +294 -294
- package/src/app/web-processor.ts +47 -42
- package/src/app/web-server.ts +100 -100
- package/src/common-js/web-env.js +104 -104
- package/src/index.ts +7 -7
- package/src/lang/api-lang-en.ts +26 -26
- package/src/lang/api-lang-zh-cn.ts +27 -27
- package/src/lang/index.ts +2 -2
- package/src/lang/lang-helper.ts +76 -76
- package/src/lang/lang-props.ts +6 -6
- package/src/lib/db/db-helper.ts +23 -23
- package/src/lib/db/db-mysql.ts +249 -250
- package/src/lib/db/db-sqlite.ts +101 -101
- package/src/lib/db/db.spec.ts +28 -28
- package/src/lib/db/db.ts +325 -325
- package/src/lib/db/index.ts +5 -5
- package/src/lib/index.ts +3 -3
- package/src/lib/logger.spec.ts +214 -214
- package/src/lib/logger.ts +281 -281
- package/src/lib/runtime-require.ts +37 -37
- package/src/lib/utils/cookie-util.ts +34 -34
- package/src/lib/utils/crypto.ts +58 -58
- package/src/lib/utils/date-utils.ts +317 -317
- package/src/lib/utils/deep-merge.ts +37 -37
- package/src/lib/utils/delay.ts +12 -12
- package/src/lib/utils/file-setting.ts +55 -55
- package/src/lib/utils/format-bytes.ts +11 -11
- package/src/lib/utils/fs-utils.ts +158 -158
- package/src/lib/utils/get-env.ts +27 -27
- package/src/lib/utils/index.ts +12 -12
- package/src/lib/utils/is-type.ts +48 -48
- package/src/lib/utils/load-env.ts +14 -14
- package/src/lib/utils/pad.ts +6 -6
- package/src/models/api-base.ts +5 -5
- package/src/models/api-module-props.ts +10 -11
- package/src/models/api-router-props.ts +26 -26
- package/src/models/app-cache-props.ts +33 -33
- package/src/models/app-data-props.ts +10 -10
- package/src/models/app-helper-props.ts +6 -6
- package/src/models/app-shared-storage-props.ts +38 -38
- package/src/models/app-start-props.ts +18 -18
- package/src/models/async-storage-props.ts +13 -13
- package/src/models/db-config.ts +30 -30
- package/src/models/host-to-path-props.ts +12 -12
- package/src/models/index.ts +16 -16
- package/src/models/json-object.ts +8 -8
- package/src/models/locals-props.ts +36 -36
- package/src/models/logger-props.ts +84 -84
- package/src/models/simple-storage-props.ts +13 -14
- package/src/models/to-client-delivery-props.ts +6 -6
- package/tsconfig.json +115 -115
- package/dev/plugin-gen-versions.js +0 -20
package/src/api/api-helper.ts
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { ServerResponse } from 'http';
|
|
2
|
-
import { ServerRequest } from '../models/locals-props';
|
|
3
|
-
import { JsonObject } from '../models/json-object';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
|
|
6
|
-
export class ApiHelper {
|
|
7
|
-
static sendJson(
|
|
8
|
-
req: ServerRequest,
|
|
9
|
-
res: ServerResponse,
|
|
10
|
-
json: JsonObject,
|
|
11
|
-
statusCode = 200,
|
|
12
|
-
headers?: { [key: string]: string }
|
|
13
|
-
) {
|
|
14
|
-
res.writeHead(statusCode, Object.assign({ 'Content-Type': 'application/json' }, headers));
|
|
15
|
-
res.write(JSON.stringify(json));
|
|
16
|
-
res.end();
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static sendHtml(
|
|
21
|
-
req: ServerRequest,
|
|
22
|
-
res: ServerResponse,
|
|
23
|
-
html: string,
|
|
24
|
-
statusCode = 200,
|
|
25
|
-
headers?: { [key: string]: string }
|
|
26
|
-
) {
|
|
27
|
-
res.writeHead(statusCode, Object.assign({ 'Content-Type': 'text/html' }, headers));
|
|
28
|
-
res.write(html);
|
|
29
|
-
res.end();
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
static sendFile(
|
|
34
|
-
req: ServerRequest,
|
|
35
|
-
res: ServerResponse,
|
|
36
|
-
filepath: string,
|
|
37
|
-
statusCode = 200,
|
|
38
|
-
headers?: { [key: string]: string }
|
|
39
|
-
) {
|
|
40
|
-
res.writeHead(statusCode, Object.assign({ 'Content-Type': 'application/octet-stream' }, headers));
|
|
41
|
-
fs.createReadStream(filepath).pipe(res);
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
1
|
+
import { ServerResponse } from 'http';
|
|
2
|
+
import { ServerRequest } from '../models/locals-props';
|
|
3
|
+
import { JsonObject } from '../models/json-object';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
export class ApiHelper {
|
|
7
|
+
static sendJson(
|
|
8
|
+
req: ServerRequest,
|
|
9
|
+
res: ServerResponse,
|
|
10
|
+
json: JsonObject,
|
|
11
|
+
statusCode = 200,
|
|
12
|
+
headers?: { [key: string]: string }
|
|
13
|
+
) {
|
|
14
|
+
res.writeHead(statusCode, Object.assign({ 'Content-Type': 'application/json' }, headers));
|
|
15
|
+
res.write(JSON.stringify(json));
|
|
16
|
+
res.end();
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static sendHtml(
|
|
21
|
+
req: ServerRequest,
|
|
22
|
+
res: ServerResponse,
|
|
23
|
+
html: string,
|
|
24
|
+
statusCode = 200,
|
|
25
|
+
headers?: { [key: string]: string }
|
|
26
|
+
) {
|
|
27
|
+
res.writeHead(statusCode, Object.assign({ 'Content-Type': 'text/html' }, headers));
|
|
28
|
+
res.write(html);
|
|
29
|
+
res.end();
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static sendFile(
|
|
34
|
+
req: ServerRequest,
|
|
35
|
+
res: ServerResponse,
|
|
36
|
+
filepath: string,
|
|
37
|
+
statusCode = 200,
|
|
38
|
+
headers?: { [key: string]: string }
|
|
39
|
+
) {
|
|
40
|
+
res.writeHead(statusCode, Object.assign({ 'Content-Type': 'application/octet-stream' }, headers));
|
|
41
|
+
fs.createReadStream(filepath).pipe(res);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/api/api-module.ts
CHANGED
|
@@ -1,60 +1,67 @@
|
|
|
1
|
-
import { DbConfig, DbHelper, HostToPathProps, IApiBase, loadEnv, ServerRequest } from 'lupine.api';
|
|
2
|
-
import { ServerResponse } from 'http';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { apiCache, asyncLocalStorage, bindRenderPageFunctions } from '.';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
appConfig
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
import { DbConfig, DbHelper, HostToPathProps, IApiBase, loadEnv, ServerRequest } from 'lupine.api';
|
|
2
|
+
import { ServerResponse } from 'http';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { apiCache, asyncLocalStorage, bindRenderPageFunctions } from '.';
|
|
5
|
+
import {
|
|
6
|
+
AppCacheGlobal,
|
|
7
|
+
AppCacheKeys,
|
|
8
|
+
AsyncStorageProps,
|
|
9
|
+
IApiModule,
|
|
10
|
+
IAppCache,
|
|
11
|
+
IAppSharedStorage,
|
|
12
|
+
setAppCache,
|
|
13
|
+
} from '../models';
|
|
14
|
+
import { apiStorage } from './api-shared-storage';
|
|
15
|
+
|
|
16
|
+
export class ApiModule implements IApiModule {
|
|
17
|
+
rootApi: IApiBase;
|
|
18
|
+
constructor(api: IApiBase) {
|
|
19
|
+
this.rootApi = api;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async processApi(store: AsyncStorageProps, url: string, req: ServerRequest, res: ServerResponse) {
|
|
23
|
+
let result = false;
|
|
24
|
+
await asyncLocalStorage.run(store, async () => {
|
|
25
|
+
if (await this.rootApi.getRouter().findRoute(url, req, res, true)) {
|
|
26
|
+
result = true;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// appCache is from app-helper (parent scope), not the same in current scope
|
|
34
|
+
public async initApi(appConfig: HostToPathProps, appCacheFromApp: IAppCache, appStorageFromApp: IAppSharedStorage) {
|
|
35
|
+
// const evnFile = appCacheFromApp.get(AppCacheGlobal, AppCacheKeys.APP_ENV_FILE);
|
|
36
|
+
// if (evnFile) {
|
|
37
|
+
// await loadEnv(evnFile);
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
// set app's instances to api
|
|
41
|
+
setAppCache(appCacheFromApp);
|
|
42
|
+
// setAppStorage(appStorageFromApp);
|
|
43
|
+
apiStorage.setAppSharedStorage(appStorageFromApp);
|
|
44
|
+
// set RENDER_PAGE_FUNCTIONS to API module
|
|
45
|
+
bindRenderPageFunctions(appCacheFromApp.get(AppCacheGlobal, AppCacheKeys.RENDER_PAGE_FUNCTIONS));
|
|
46
|
+
|
|
47
|
+
console.log(`appConfig: `, appConfig);
|
|
48
|
+
apiCache.set(apiCache.KEYS.APP_DATA, appConfig);
|
|
49
|
+
// apiCache.set(apiCache.KEYS.APP_CACHE, appCache);
|
|
50
|
+
|
|
51
|
+
// await this.initConfig(appConfig);
|
|
52
|
+
apiCache.clearTemplateCache();
|
|
53
|
+
|
|
54
|
+
appConfig.dbConfig.filename = path.join(appConfig.dataPath, 'sqlite3.db');
|
|
55
|
+
await this.initDb(appConfig.dbConfig);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async initDb(config: DbConfig) {
|
|
59
|
+
const db = await DbHelper.createInstance(config);
|
|
60
|
+
apiCache.set(apiCache.KEYS.DB, db);
|
|
61
|
+
return db;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// private async initConfig(appConfig: HostToPathProps) {
|
|
65
|
+
// await AppConfig.load(appConfig.dataPath);
|
|
66
|
+
// }
|
|
67
|
+
}
|
package/src/api/api-router.ts
CHANGED
|
@@ -1,177 +1,177 @@
|
|
|
1
|
-
import { ServerResponse } from 'http';
|
|
2
|
-
import { Logger } from '../lib';
|
|
3
|
-
import { ServerRequest } from '../models/locals-props';
|
|
4
|
-
import { handler404 } from './handle-status';
|
|
5
|
-
import { SimpleStorage } from './simple-storage';
|
|
6
|
-
import { ApiRouterCallback, ApiRouterData, ApiRouterMethod, IApiRouter } from '../models/api-router-props';
|
|
7
|
-
const logger = new Logger('api-router');
|
|
8
|
-
|
|
9
|
-
/*
|
|
10
|
-
If there is a common logic to be called all endpoints, then you can set filter.
|
|
11
|
-
Or, for a particular endpoint, if you want a logic to be called before any other
|
|
12
|
-
WebRouterCallback or WebRouter it can be done like this:
|
|
13
|
-
|
|
14
|
-
const commLogic: WebRouterCallback = async (req: ServerRequest, res: ServerResponse) => {
|
|
15
|
-
console.log('this is called by all...');
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
router.use('/auth', commLogic, otherLogic);
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
function isIApiRouter(handler: ApiRouterCallback | IApiRouter): handler is IApiRouter {
|
|
22
|
-
return (handler as IApiRouter).findRoute !== undefined;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class ApiRouter implements IApiRouter {
|
|
26
|
-
private routerData: ApiRouterData[] = [];
|
|
27
|
-
private filter: ApiRouterCallback | undefined;
|
|
28
|
-
|
|
29
|
-
setFilter(filter: ApiRouterCallback) {
|
|
30
|
-
this.filter = filter;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// the path should start with / and end without /, and it can be
|
|
34
|
-
// /aaa/:bbb/ccc/:ddd (ccc is a fixed section)
|
|
35
|
-
// /aaa/:bbb/ccc/?ddd/?eee (from ddd, all sections are optional)
|
|
36
|
-
private storeRouter(path: string, handler: (ApiRouterCallback | IApiRouter)[], method: ApiRouterMethod) {
|
|
37
|
-
let fixedPath;
|
|
38
|
-
if (path === '*' || path === '/' || path === '' || path === '/*') {
|
|
39
|
-
fixedPath = '*';
|
|
40
|
-
} else {
|
|
41
|
-
fixedPath = path;
|
|
42
|
-
if (!fixedPath.startsWith('/')) {
|
|
43
|
-
fixedPath = '/' + fixedPath;
|
|
44
|
-
}
|
|
45
|
-
if (fixedPath.endsWith('/')) {
|
|
46
|
-
fixedPath = fixedPath.substring(0, fixedPath.length - 1);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let parameterLength = 0;
|
|
51
|
-
let parameterVariables: string[] = [];
|
|
52
|
-
const ind = fixedPath.indexOf('/:');
|
|
53
|
-
if (ind >= 0) {
|
|
54
|
-
parameterVariables = fixedPath.substring(ind + 1).split('/');
|
|
55
|
-
fixedPath = fixedPath.substring(0, ind);
|
|
56
|
-
// from optionInd, all will be optional
|
|
57
|
-
const optionInd = parameterVariables.findIndex((item) => item.startsWith('?'));
|
|
58
|
-
parameterLength = optionInd >= 0 ? optionInd : parameterVariables.length;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
this.routerData.push({
|
|
62
|
-
path: fixedPath,
|
|
63
|
-
handler,
|
|
64
|
-
method: method,
|
|
65
|
-
parameterVariables,
|
|
66
|
-
parameterLength,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
get(path: string, ...handler: (ApiRouterCallback | IApiRouter)[]) {
|
|
71
|
-
this.storeRouter(path, handler, ApiRouterMethod.GET);
|
|
72
|
-
}
|
|
73
|
-
post(path: string, ...handler: (ApiRouterCallback | IApiRouter)[]) {
|
|
74
|
-
this.storeRouter(path, handler, ApiRouterMethod.POST);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
use(path: string, ...handler: (ApiRouterCallback | IApiRouter)[]) {
|
|
78
|
-
this.storeRouter(path, handler, ApiRouterMethod.ALL);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private async callHandle(
|
|
82
|
-
handle: ApiRouterCallback,
|
|
83
|
-
path: string,
|
|
84
|
-
req: ServerRequest,
|
|
85
|
-
res: ServerResponse
|
|
86
|
-
): Promise<boolean> {
|
|
87
|
-
try {
|
|
88
|
-
if ((await handle(req, res, path)) || res.writableEnded) {
|
|
89
|
-
logger.debug(`Processed path: ${path}`);
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
} catch (e: any) {
|
|
93
|
-
logger.error(`Processed path: ${path}, error: ${e.message}`);
|
|
94
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
95
|
-
res.write(
|
|
96
|
-
JSON.stringify({
|
|
97
|
-
status: 'error',
|
|
98
|
-
message: `Processed path: ${path}, error: ${e.message}`,
|
|
99
|
-
})
|
|
100
|
-
);
|
|
101
|
-
res.end();
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async findRoute(url: string, req: ServerRequest, res: ServerResponse, handleNotFound: boolean): Promise<boolean> {
|
|
108
|
-
for (let i = 0, routerList; (routerList = this.routerData[i]); i++) {
|
|
109
|
-
if (
|
|
110
|
-
(routerList.method === ApiRouterMethod.ALL || routerList.method === req.method) &&
|
|
111
|
-
(routerList.path === '*' || url === routerList.path || url.startsWith(routerList.path + '/'))
|
|
112
|
-
) {
|
|
113
|
-
const parameters: { [key: string]: string } = {};
|
|
114
|
-
let meet = true;
|
|
115
|
-
if (routerList.parameterVariables.length > 0) {
|
|
116
|
-
meet = false;
|
|
117
|
-
const restPath = url.substring(routerList.path.length + 1).split('/');
|
|
118
|
-
// the path must have mandatory parameters but some parameters can be optional
|
|
119
|
-
if (
|
|
120
|
-
restPath.length >= routerList.parameterLength &&
|
|
121
|
-
restPath.length <= routerList.parameterVariables.length
|
|
122
|
-
) {
|
|
123
|
-
meet = true;
|
|
124
|
-
for (const [index, item] of routerList.parameterVariables.entries()) {
|
|
125
|
-
if (!item.startsWith(':') && !item.startsWith('?') && item !== restPath[index]) {
|
|
126
|
-
meet = false;
|
|
127
|
-
break;
|
|
128
|
-
} else if ((item.startsWith(':') || item.startsWith('?')) && index < restPath.length) {
|
|
129
|
-
parameters[item.replace(/[:?]/g, '')] = restPath[index];
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
req.locals.urlParameters = new SimpleStorage(parameters);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (meet) {
|
|
137
|
-
for (let j = 0, router; (router = routerList.handler[j]); j++) {
|
|
138
|
-
if (isIApiRouter(router)) {
|
|
139
|
-
// it's a sub-level router
|
|
140
|
-
const nextPath = routerList.path === '*' ? url : url.substring(routerList.path.length);
|
|
141
|
-
// the sub-level router will not have the appName
|
|
142
|
-
if ((await router.findRoute(nextPath, req, res, handleNotFound)) || res.writableEnded) {
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
// it should be a function
|
|
147
|
-
// the query's url should match the api's path
|
|
148
|
-
if (await this.callHandle(router, url, req, res)) {
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (handleNotFound) {
|
|
156
|
-
// no match under this path
|
|
157
|
-
logger.debug(`Processed path: ${url}, router path: ${routerList.path}`);
|
|
158
|
-
const html = JSON.stringify({
|
|
159
|
-
status: 'error',
|
|
160
|
-
message: `Can't find any matches under router ${routerList.path} for path: ${url}.`,
|
|
161
|
-
});
|
|
162
|
-
handler404(res, html);
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async handleRequest(url: string, req: ServerRequest, res: ServerResponse): Promise<boolean> {
|
|
171
|
-
if (this.filter && (await this.callHandle(this.filter, url, req, res))) {
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return await this.findRoute(url, req, res, true);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
1
|
+
import { ServerResponse } from 'http';
|
|
2
|
+
import { Logger } from '../lib';
|
|
3
|
+
import { ServerRequest } from '../models/locals-props';
|
|
4
|
+
import { handler404 } from './handle-status';
|
|
5
|
+
import { SimpleStorage } from './simple-storage';
|
|
6
|
+
import { ApiRouterCallback, ApiRouterData, ApiRouterMethod, IApiRouter } from '../models/api-router-props';
|
|
7
|
+
const logger = new Logger('api-router');
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
If there is a common logic to be called all endpoints, then you can set filter.
|
|
11
|
+
Or, for a particular endpoint, if you want a logic to be called before any other
|
|
12
|
+
WebRouterCallback or WebRouter it can be done like this:
|
|
13
|
+
|
|
14
|
+
const commLogic: WebRouterCallback = async (req: ServerRequest, res: ServerResponse) => {
|
|
15
|
+
console.log('this is called by all...');
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
router.use('/auth', commLogic, otherLogic);
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function isIApiRouter(handler: ApiRouterCallback | IApiRouter): handler is IApiRouter {
|
|
22
|
+
return (handler as IApiRouter).findRoute !== undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ApiRouter implements IApiRouter {
|
|
26
|
+
private routerData: ApiRouterData[] = [];
|
|
27
|
+
private filter: ApiRouterCallback | undefined;
|
|
28
|
+
|
|
29
|
+
setFilter(filter: ApiRouterCallback) {
|
|
30
|
+
this.filter = filter;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// the path should start with / and end without /, and it can be
|
|
34
|
+
// /aaa/:bbb/ccc/:ddd (ccc is a fixed section)
|
|
35
|
+
// /aaa/:bbb/ccc/?ddd/?eee (from ddd, all sections are optional)
|
|
36
|
+
private storeRouter(path: string, handler: (ApiRouterCallback | IApiRouter)[], method: ApiRouterMethod) {
|
|
37
|
+
let fixedPath;
|
|
38
|
+
if (path === '*' || path === '/' || path === '' || path === '/*') {
|
|
39
|
+
fixedPath = '*';
|
|
40
|
+
} else {
|
|
41
|
+
fixedPath = path;
|
|
42
|
+
if (!fixedPath.startsWith('/')) {
|
|
43
|
+
fixedPath = '/' + fixedPath;
|
|
44
|
+
}
|
|
45
|
+
if (fixedPath.endsWith('/')) {
|
|
46
|
+
fixedPath = fixedPath.substring(0, fixedPath.length - 1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let parameterLength = 0;
|
|
51
|
+
let parameterVariables: string[] = [];
|
|
52
|
+
const ind = fixedPath.indexOf('/:');
|
|
53
|
+
if (ind >= 0) {
|
|
54
|
+
parameterVariables = fixedPath.substring(ind + 1).split('/');
|
|
55
|
+
fixedPath = fixedPath.substring(0, ind);
|
|
56
|
+
// from optionInd, all will be optional
|
|
57
|
+
const optionInd = parameterVariables.findIndex((item) => item.startsWith('?'));
|
|
58
|
+
parameterLength = optionInd >= 0 ? optionInd : parameterVariables.length;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.routerData.push({
|
|
62
|
+
path: fixedPath,
|
|
63
|
+
handler,
|
|
64
|
+
method: method,
|
|
65
|
+
parameterVariables,
|
|
66
|
+
parameterLength,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get(path: string, ...handler: (ApiRouterCallback | IApiRouter)[]) {
|
|
71
|
+
this.storeRouter(path, handler, ApiRouterMethod.GET);
|
|
72
|
+
}
|
|
73
|
+
post(path: string, ...handler: (ApiRouterCallback | IApiRouter)[]) {
|
|
74
|
+
this.storeRouter(path, handler, ApiRouterMethod.POST);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
use(path: string, ...handler: (ApiRouterCallback | IApiRouter)[]) {
|
|
78
|
+
this.storeRouter(path, handler, ApiRouterMethod.ALL);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async callHandle(
|
|
82
|
+
handle: ApiRouterCallback,
|
|
83
|
+
path: string,
|
|
84
|
+
req: ServerRequest,
|
|
85
|
+
res: ServerResponse
|
|
86
|
+
): Promise<boolean> {
|
|
87
|
+
try {
|
|
88
|
+
if ((await handle(req, res, path)) || res.writableEnded) {
|
|
89
|
+
logger.debug(`Processed path: ${path}`);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
} catch (e: any) {
|
|
93
|
+
logger.error(`Processed path: ${path}, error: ${e.message}`);
|
|
94
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
95
|
+
res.write(
|
|
96
|
+
JSON.stringify({
|
|
97
|
+
status: 'error',
|
|
98
|
+
message: `Processed path: ${path}, error: ${e.message}`,
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
res.end();
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async findRoute(url: string, req: ServerRequest, res: ServerResponse, handleNotFound: boolean): Promise<boolean> {
|
|
108
|
+
for (let i = 0, routerList; (routerList = this.routerData[i]); i++) {
|
|
109
|
+
if (
|
|
110
|
+
(routerList.method === ApiRouterMethod.ALL || routerList.method === req.method) &&
|
|
111
|
+
(routerList.path === '*' || url === routerList.path || url.startsWith(routerList.path + '/'))
|
|
112
|
+
) {
|
|
113
|
+
const parameters: { [key: string]: string } = {};
|
|
114
|
+
let meet = true;
|
|
115
|
+
if (routerList.parameterVariables.length > 0) {
|
|
116
|
+
meet = false;
|
|
117
|
+
const restPath = url.substring(routerList.path.length + 1).split('/');
|
|
118
|
+
// the path must have mandatory parameters but some parameters can be optional
|
|
119
|
+
if (
|
|
120
|
+
restPath.length >= routerList.parameterLength &&
|
|
121
|
+
restPath.length <= routerList.parameterVariables.length
|
|
122
|
+
) {
|
|
123
|
+
meet = true;
|
|
124
|
+
for (const [index, item] of routerList.parameterVariables.entries()) {
|
|
125
|
+
if (!item.startsWith(':') && !item.startsWith('?') && item !== restPath[index]) {
|
|
126
|
+
meet = false;
|
|
127
|
+
break;
|
|
128
|
+
} else if ((item.startsWith(':') || item.startsWith('?')) && index < restPath.length) {
|
|
129
|
+
parameters[item.replace(/[:?]/g, '')] = restPath[index];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
req.locals.urlParameters = new SimpleStorage(parameters);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (meet) {
|
|
137
|
+
for (let j = 0, router; (router = routerList.handler[j]); j++) {
|
|
138
|
+
if (isIApiRouter(router)) {
|
|
139
|
+
// it's a sub-level router
|
|
140
|
+
const nextPath = routerList.path === '*' ? url : url.substring(routerList.path.length);
|
|
141
|
+
// the sub-level router will not have the appName
|
|
142
|
+
if ((await router.findRoute(nextPath, req, res, handleNotFound)) || res.writableEnded) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// it should be a function
|
|
147
|
+
// the query's url should match the api's path
|
|
148
|
+
if (await this.callHandle(router, url, req, res)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (handleNotFound) {
|
|
156
|
+
// no match under this path
|
|
157
|
+
logger.debug(`Processed path: ${url}, router path: ${routerList.path}`);
|
|
158
|
+
const html = JSON.stringify({
|
|
159
|
+
status: 'error',
|
|
160
|
+
message: `Can't find any matches under router ${routerList.path} for path: ${url}.`,
|
|
161
|
+
});
|
|
162
|
+
handler404(res, html);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async handleRequest(url: string, req: ServerRequest, res: ServerResponse): Promise<boolean> {
|
|
171
|
+
if (this.filter && (await this.callHandle(this.filter, url, req, res))) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return await this.findRoute(url, req, res, true);
|
|
176
|
+
}
|
|
177
|
+
}
|