lupine.api 1.1.57 → 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.
Files changed (137) hide show
  1. package/README.md +3 -3
  2. package/admin/admin-about.tsx +12 -16
  3. package/admin/admin-config.tsx +47 -44
  4. package/admin/admin-css.tsx +3 -3
  5. package/admin/admin-db.tsx +75 -75
  6. package/admin/admin-frame-helper.tsx +364 -364
  7. package/admin/admin-frame.tsx +164 -164
  8. package/admin/admin-index.tsx +65 -65
  9. package/admin/admin-login.tsx +111 -111
  10. package/admin/admin-menu-edit.tsx +637 -637
  11. package/admin/admin-menu-list.tsx +87 -87
  12. package/admin/admin-page-edit.tsx +564 -564
  13. package/admin/admin-page-list.tsx +83 -83
  14. package/admin/admin-performance.tsx +28 -28
  15. package/admin/admin-release.tsx +427 -404
  16. package/admin/admin-resources.tsx +382 -382
  17. package/admin/admin-shell.tsx +89 -89
  18. package/admin/admin-table-data.tsx +146 -146
  19. package/admin/admin-table-list.tsx +230 -230
  20. package/admin/admin-test-animations.tsx +395 -395
  21. package/admin/admin-test-component.tsx +823 -808
  22. package/admin/admin-test-edit.tsx +319 -319
  23. package/admin/admin-test-themes.tsx +56 -56
  24. package/admin/admin-tokens.tsx +338 -338
  25. package/admin/design/admin-design.tsx +174 -174
  26. package/admin/design/block-grid.tsx +36 -36
  27. package/admin/design/block-grid1.tsx +21 -21
  28. package/admin/design/block-paragraph.tsx +19 -19
  29. package/admin/design/block-title.tsx +19 -19
  30. package/admin/design/design-block-box.tsx +140 -140
  31. package/admin/design/drag-data.tsx +24 -24
  32. package/admin/index.ts +9 -9
  33. package/admin/package.json +15 -15
  34. package/admin/tsconfig.json +127 -127
  35. package/dev/copy-folder.js +32 -32
  36. package/dev/cp-index-html.js +69 -69
  37. package/dev/file-utils.js +12 -12
  38. package/dev/index.js +18 -19
  39. package/dev/package.json +12 -12
  40. package/dev/plugin-ifelse.js +168 -168
  41. package/dev/plugin-ifelse.test.js +37 -37
  42. package/dev/run-cmd.js +14 -14
  43. package/dev/send-request.js +12 -12
  44. package/package.json +55 -55
  45. package/src/admin-api/admin-api-helper.ts +210 -205
  46. package/src/admin-api/admin-api.ts +65 -65
  47. package/src/admin-api/admin-auth.ts +152 -146
  48. package/src/admin-api/admin-config.ts +94 -84
  49. package/src/admin-api/admin-csv.ts +94 -94
  50. package/src/admin-api/admin-db.ts +269 -269
  51. package/src/admin-api/admin-menu.ts +135 -135
  52. package/src/admin-api/admin-page.ts +135 -135
  53. package/src/admin-api/admin-performance.ts +128 -128
  54. package/src/admin-api/admin-release.ts +703 -700
  55. package/src/admin-api/admin-resources.ts +318 -318
  56. package/src/admin-api/admin-token-helper.ts +82 -79
  57. package/src/admin-api/admin-tokens.ts +90 -90
  58. package/src/admin-api/index.ts +2 -2
  59. package/src/admin-api/web-config-api.ts +19 -19
  60. package/src/api/api-cache.ts +103 -103
  61. package/src/api/api-helper.ts +44 -44
  62. package/src/api/api-module.ts +67 -60
  63. package/src/api/api-router.ts +177 -177
  64. package/src/api/api-shared-storage.ts +64 -64
  65. package/src/api/async-storage.ts +5 -5
  66. package/src/api/debug-service.ts +56 -56
  67. package/src/api/encode-html.ts +27 -27
  68. package/src/api/handle-status.ts +75 -75
  69. package/src/api/index.ts +15 -16
  70. package/src/api/mini-web-socket.ts +270 -270
  71. package/src/api/server-content-type.ts +82 -82
  72. package/src/api/server-render.ts +235 -215
  73. package/src/api/shell-service.ts +74 -74
  74. package/src/api/simple-storage.ts +80 -80
  75. package/src/api/static-server.ts +128 -125
  76. package/src/api/to-client-delivery.ts +26 -26
  77. package/src/app/app-cache.ts +55 -55
  78. package/src/app/app-helper.ts +62 -62
  79. package/src/app/app-message.ts +109 -109
  80. package/src/app/app-shared-storage.ts +363 -363
  81. package/src/app/app-start.ts +136 -136
  82. package/src/app/cleanup-exit.ts +16 -16
  83. package/src/app/host-to-path.ts +38 -38
  84. package/src/app/index.ts +11 -11
  85. package/src/app/process-dev-requests.ts +130 -130
  86. package/src/app/web-listener.ts +294 -294
  87. package/src/app/web-processor.ts +47 -42
  88. package/src/app/web-server.ts +100 -100
  89. package/src/common-js/web-env.js +104 -104
  90. package/src/index.ts +7 -7
  91. package/src/lang/api-lang-en.ts +26 -26
  92. package/src/lang/api-lang-zh-cn.ts +27 -27
  93. package/src/lang/index.ts +2 -2
  94. package/src/lang/lang-helper.ts +76 -76
  95. package/src/lang/lang-props.ts +6 -6
  96. package/src/lib/db/db-helper.ts +23 -23
  97. package/src/lib/db/db-mysql.ts +249 -250
  98. package/src/lib/db/db-sqlite.ts +101 -101
  99. package/src/lib/db/db.spec.ts +28 -28
  100. package/src/lib/db/db.ts +325 -325
  101. package/src/lib/db/index.ts +5 -5
  102. package/src/lib/index.ts +3 -3
  103. package/src/lib/logger.spec.ts +214 -214
  104. package/src/lib/logger.ts +281 -281
  105. package/src/lib/runtime-require.ts +37 -37
  106. package/src/lib/utils/cookie-util.ts +34 -34
  107. package/src/lib/utils/crypto.ts +58 -58
  108. package/src/lib/utils/date-utils.ts +317 -317
  109. package/src/lib/utils/deep-merge.ts +37 -37
  110. package/src/lib/utils/delay.ts +12 -12
  111. package/src/lib/utils/file-setting.ts +55 -55
  112. package/src/lib/utils/format-bytes.ts +11 -11
  113. package/src/lib/utils/fs-utils.ts +158 -158
  114. package/src/lib/utils/get-env.ts +27 -27
  115. package/src/lib/utils/index.ts +12 -12
  116. package/src/lib/utils/is-type.ts +48 -48
  117. package/src/lib/utils/load-env.ts +14 -14
  118. package/src/lib/utils/pad.ts +6 -6
  119. package/src/models/api-base.ts +5 -5
  120. package/src/models/api-module-props.ts +10 -11
  121. package/src/models/api-router-props.ts +26 -26
  122. package/src/models/app-cache-props.ts +33 -33
  123. package/src/models/app-data-props.ts +10 -10
  124. package/src/models/app-helper-props.ts +6 -6
  125. package/src/models/app-shared-storage-props.ts +38 -38
  126. package/src/models/app-start-props.ts +18 -18
  127. package/src/models/async-storage-props.ts +13 -13
  128. package/src/models/db-config.ts +30 -30
  129. package/src/models/host-to-path-props.ts +12 -12
  130. package/src/models/index.ts +16 -16
  131. package/src/models/json-object.ts +8 -8
  132. package/src/models/locals-props.ts +36 -36
  133. package/src/models/logger-props.ts +84 -84
  134. package/src/models/simple-storage-props.ts +13 -14
  135. package/src/models/to-client-delivery-props.ts +6 -6
  136. package/tsconfig.json +115 -115
  137. package/dev/plugin-gen-versions.js +0 -20
@@ -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
+ }
@@ -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 { AppCacheGlobal, AppCacheKeys, AsyncStorageProps, IApiModule, IAppCache, IAppSharedStorage, setAppCache } from '../models';
6
- import { apiStorage } from './api-shared-storage';
7
-
8
- export class ApiModule implements IApiModule {
9
- rootApi: IApiBase;
10
- constructor(api: IApiBase) {
11
- this.rootApi = api;
12
- }
13
-
14
- public async processApi(store: AsyncStorageProps, url: string, req: ServerRequest, res: ServerResponse) {
15
- let result = false;
16
- await asyncLocalStorage.run(store, async () => {
17
- if (await this.rootApi.getRouter().findRoute(url, req, res, true)) {
18
- result = true;
19
- return true;
20
- }
21
- });
22
- return result;
23
- }
24
-
25
- // appCache is from app-helper (parent scope), not the same in current scope
26
- public async initApi(appConfig: HostToPathProps, appCacheFromApp: IAppCache, appStorageFromApp: IAppSharedStorage) {
27
-
28
- // const evnFile = appCacheFromApp.get(AppCacheGlobal, AppCacheKeys.APP_ENV_FILE);
29
- // if (evnFile) {
30
- // await loadEnv(evnFile);
31
- // }
32
-
33
- // set app's instances to api
34
- setAppCache(appCacheFromApp);
35
- // setAppStorage(appStorageFromApp);
36
- apiStorage.setAppSharedStorage(appStorageFromApp);
37
- // set RENDER_PAGE_FUNCTIONS to API module
38
- bindRenderPageFunctions(appCacheFromApp.get(AppCacheGlobal, AppCacheKeys.RENDER_PAGE_FUNCTIONS));
39
-
40
- console.log(`appConfig: `, appConfig);
41
- apiCache.set(apiCache.KEYS.APP_DATA, appConfig);
42
- // apiCache.set(apiCache.KEYS.APP_CACHE, appCache);
43
-
44
- // await this.initConfig(appConfig);
45
- apiCache.clearTemplateCache();
46
-
47
- appConfig.dbConfig.filename = path.join(appConfig.dataPath, 'sqlite3.db');
48
- await this.initDb(appConfig.dbConfig);
49
- }
50
-
51
- private async initDb(config: DbConfig) {
52
- const db = await DbHelper.createInstance(config);
53
- apiCache.set(apiCache.KEYS.DB, db);
54
- return db;
55
- }
56
-
57
- // private async initConfig(appConfig: HostToPathProps) {
58
- // await AppConfig.load(appConfig.dataPath);
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
+ }
@@ -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
+ }