lupine.api 1.0.41

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/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/admin/admin-about.tsx +16 -0
  4. package/admin/admin-config.tsx +44 -0
  5. package/admin/admin-css.tsx +3 -0
  6. package/admin/admin-db.tsx +74 -0
  7. package/admin/admin-frame-props.tsx +9 -0
  8. package/admin/admin-frame.tsx +466 -0
  9. package/admin/admin-index.tsx +66 -0
  10. package/admin/admin-login.tsx +99 -0
  11. package/admin/admin-menu-edit.tsx +637 -0
  12. package/admin/admin-menu-list.tsx +87 -0
  13. package/admin/admin-page-edit.tsx +564 -0
  14. package/admin/admin-page-list.tsx +83 -0
  15. package/admin/admin-performance.tsx +28 -0
  16. package/admin/admin-release.tsx +320 -0
  17. package/admin/admin-resources.tsx +385 -0
  18. package/admin/admin-shell.tsx +89 -0
  19. package/admin/admin-table-data.tsx +146 -0
  20. package/admin/admin-table-list.tsx +231 -0
  21. package/admin/admin-test-animations.tsx +379 -0
  22. package/admin/admin-test-component.tsx +808 -0
  23. package/admin/admin-test-edit.tsx +319 -0
  24. package/admin/admin-test-themes.tsx +56 -0
  25. package/admin/admin-tokens.tsx +338 -0
  26. package/admin/design/admin-design.tsx +174 -0
  27. package/admin/design/block-grid.tsx +36 -0
  28. package/admin/design/block-grid1.tsx +21 -0
  29. package/admin/design/block-paragraph.tsx +19 -0
  30. package/admin/design/block-title.tsx +19 -0
  31. package/admin/design/design-block-box.tsx +140 -0
  32. package/admin/design/drag-data.tsx +24 -0
  33. package/admin/index.ts +6 -0
  34. package/admin/package.json +15 -0
  35. package/admin/tsconfig.json +127 -0
  36. package/dev/copy-folder.js +32 -0
  37. package/dev/cp-index-html.js +69 -0
  38. package/dev/file-utils.js +12 -0
  39. package/dev/index.js +19 -0
  40. package/dev/package.json +12 -0
  41. package/dev/plugin-gen-versions.js +20 -0
  42. package/dev/plugin-ifelse.js +155 -0
  43. package/dev/plugin-ifelse.test.js +37 -0
  44. package/dev/run-cmd.js +14 -0
  45. package/dev/send-request.js +12 -0
  46. package/package.json +55 -0
  47. package/src/admin-api/admin-api.ts +59 -0
  48. package/src/admin-api/admin-auth.ts +87 -0
  49. package/src/admin-api/admin-config.ts +93 -0
  50. package/src/admin-api/admin-csv.ts +81 -0
  51. package/src/admin-api/admin-db.ts +269 -0
  52. package/src/admin-api/admin-helper.ts +111 -0
  53. package/src/admin-api/admin-menu.ts +135 -0
  54. package/src/admin-api/admin-page.ts +135 -0
  55. package/src/admin-api/admin-performance.ts +128 -0
  56. package/src/admin-api/admin-release.ts +498 -0
  57. package/src/admin-api/admin-resources.ts +318 -0
  58. package/src/admin-api/admin-token-helper.ts +79 -0
  59. package/src/admin-api/admin-tokens.ts +90 -0
  60. package/src/admin-api/index.ts +2 -0
  61. package/src/api/api-cache.ts +103 -0
  62. package/src/api/api-helper.ts +44 -0
  63. package/src/api/api-module.ts +60 -0
  64. package/src/api/api-router.ts +177 -0
  65. package/src/api/api-shared-storage.ts +64 -0
  66. package/src/api/async-storage.ts +5 -0
  67. package/src/api/debug-service.ts +56 -0
  68. package/src/api/encode-html.ts +27 -0
  69. package/src/api/handle-status.ts +71 -0
  70. package/src/api/index.ts +16 -0
  71. package/src/api/mini-web-socket.ts +270 -0
  72. package/src/api/server-content-type.ts +82 -0
  73. package/src/api/server-render.ts +216 -0
  74. package/src/api/shell-service.ts +66 -0
  75. package/src/api/simple-storage.ts +80 -0
  76. package/src/api/static-server.ts +125 -0
  77. package/src/api/to-client-delivery.ts +26 -0
  78. package/src/app/app-cache.ts +55 -0
  79. package/src/app/app-loader.ts +62 -0
  80. package/src/app/app-message.ts +60 -0
  81. package/src/app/app-shared-storage.ts +317 -0
  82. package/src/app/app-start.ts +117 -0
  83. package/src/app/cleanup-exit.ts +12 -0
  84. package/src/app/host-to-path.ts +38 -0
  85. package/src/app/index.ts +11 -0
  86. package/src/app/process-dev-requests.ts +90 -0
  87. package/src/app/web-listener.ts +230 -0
  88. package/src/app/web-processor.ts +42 -0
  89. package/src/app/web-server.ts +86 -0
  90. package/src/common-js/web-env.js +104 -0
  91. package/src/index.ts +7 -0
  92. package/src/lang/api-lang-en.ts +27 -0
  93. package/src/lang/api-lang-zh-cn.ts +28 -0
  94. package/src/lang/index.ts +2 -0
  95. package/src/lang/lang-helper.ts +76 -0
  96. package/src/lang/lang-props.ts +6 -0
  97. package/src/lib/db/db-helper.ts +23 -0
  98. package/src/lib/db/db-mysql.ts +250 -0
  99. package/src/lib/db/db-sqlite.ts +101 -0
  100. package/src/lib/db/db.spec.ts +28 -0
  101. package/src/lib/db/db.ts +304 -0
  102. package/src/lib/db/index.ts +5 -0
  103. package/src/lib/index.ts +3 -0
  104. package/src/lib/logger.spec.ts +214 -0
  105. package/src/lib/logger.ts +274 -0
  106. package/src/lib/runtime-require.ts +37 -0
  107. package/src/lib/utils/cookie-util.ts +34 -0
  108. package/src/lib/utils/crypto.ts +58 -0
  109. package/src/lib/utils/date-utils.ts +317 -0
  110. package/src/lib/utils/deep-merge.ts +37 -0
  111. package/src/lib/utils/delay.ts +12 -0
  112. package/src/lib/utils/file-setting.ts +55 -0
  113. package/src/lib/utils/format-bytes.ts +11 -0
  114. package/src/lib/utils/fs-utils.ts +144 -0
  115. package/src/lib/utils/get-env.ts +27 -0
  116. package/src/lib/utils/index.ts +12 -0
  117. package/src/lib/utils/is-type.ts +48 -0
  118. package/src/lib/utils/load-env.ts +14 -0
  119. package/src/lib/utils/pad.ts +6 -0
  120. package/src/models/api-base.ts +5 -0
  121. package/src/models/api-module-props.ts +11 -0
  122. package/src/models/api-router-props.ts +26 -0
  123. package/src/models/app-cache-props.ts +33 -0
  124. package/src/models/app-data-props.ts +10 -0
  125. package/src/models/app-loader-props.ts +6 -0
  126. package/src/models/app-shared-storage-props.ts +37 -0
  127. package/src/models/app-start-props.ts +18 -0
  128. package/src/models/async-storage-props.ts +13 -0
  129. package/src/models/db-config.ts +30 -0
  130. package/src/models/host-to-path-props.ts +12 -0
  131. package/src/models/index.ts +16 -0
  132. package/src/models/json-object.ts +8 -0
  133. package/src/models/locals-props.ts +36 -0
  134. package/src/models/logger-props.ts +84 -0
  135. package/src/models/simple-storage-props.ts +14 -0
  136. package/src/models/to-client-delivery-props.ts +6 -0
  137. package/tsconfig.json +115 -0
@@ -0,0 +1,69 @@
1
+ const path = require('path');
2
+ const fs = require('fs/promises');
3
+ const webEnv = require('../src/common-js/web-env.js');
4
+ const fileUtils = require('./file-utils.js');
5
+
6
+ const fileSizeAndTime = async (filename) => {
7
+ try {
8
+ var stats = await fs.stat(filename);
9
+ return { size: stats.size, mtime: stats.mtime };
10
+ } catch (e) {
11
+ return false;
12
+ }
13
+ };
14
+ const fileUpdateTime = async (filename, time) => {
15
+ fs.utimes(filename, time, time);
16
+ };
17
+
18
+ const readWebConfig = async (outdirData) => {
19
+ let tempPath = path.join(outdirData, 'config.json');
20
+ if (!(await fileUtils.pathExists(tempPath))) {
21
+ tempPath = path.join(outdirData, 'resources', 'config_default.json');
22
+ }
23
+
24
+ // file should exist
25
+ const data = await fs.readFile(tempPath, 'utf-8');
26
+ const json = JSON.parse(data || {});
27
+ return webEnv.getWebConfig(json);
28
+ };
29
+
30
+ const metaTextStart = '<!--META-ENV-START-->';
31
+ const metaTextEnd = '<!--META-ENV-END-->';
32
+ exports.cpIndexHtml = async (htmlFile, outputFile, appName, isMobile, defaultThemeName, outdirData) => {
33
+ const f1 = await fileSizeAndTime(htmlFile);
34
+ const f2 = await fileSizeAndTime(outputFile);
35
+
36
+ // once isMobile is changed, then index.html file needs to be rebuilt
37
+ // if isMobile=true, then the last number is 1, or if isMobile=false, the last is 2
38
+ const chgTime = Math.trunc(f1.mtime.getTime() / 10) * 10 + (isMobile ? 1 : 2);
39
+
40
+ // when it's isMobile, need to update env and configs
41
+ if (!f2 || f2.mtime.getTime() !== chgTime || isMobile) {
42
+ const inHtml = await fs.readFile(htmlFile, 'utf-8');
43
+ let outStr = inHtml.replace(/{hash}/gi, new Date().getTime().toString(36));
44
+ if (isMobile) {
45
+ // const outStr = inHtml.replace(/{hash}/gi, new Date().getTime().toString(36)).replace('\<\!--META-ENV--\>', JSON.stringify(envWeb));
46
+ // env is replaced here for the mobile app. And the env is replaced again for the web app at each startup
47
+
48
+ const metaIndexStart = inHtml.indexOf(metaTextStart);
49
+ const metaIndexEnd = inHtml.indexOf(metaTextEnd);
50
+
51
+ const webConfig = await readWebConfig(outdirData);
52
+ const webEnvData = webEnv.getWebEnv(appName);
53
+
54
+ outStr =
55
+ outStr.substring(0, metaIndexStart).replace('<!--META-THEME-->', defaultThemeName) +
56
+ '<script id="web-env" type="application/json">' +
57
+ JSON.stringify(webEnvData) +
58
+ '</script>\r\n' +
59
+ '<script id="web-setting" type="application/json">' +
60
+ JSON.stringify(webConfig) +
61
+ '</script>' +
62
+ outStr.substring(metaIndexEnd + metaTextEnd.length);
63
+ // outStr = webEnv.replaceWebEnv(inHtml, appName, true);
64
+ }
65
+ await fs.mkdir(path.dirname(outputFile), { recursive: true });
66
+ await fs.writeFile(outputFile, outStr);
67
+ await fileUpdateTime(outputFile, new Date(chgTime));
68
+ }
69
+ };
@@ -0,0 +1,12 @@
1
+ const fs = require('fs/promises');
2
+
3
+ exports.readJson = async (filename) => {
4
+ return JSON.parse((await fs.readFile(filename)).toString());
5
+ };
6
+
7
+ exports.pathExists = async (path) => {
8
+ return await fs
9
+ .access(path)
10
+ .then(() => true)
11
+ .catch(() => false);
12
+ };
package/dev/index.js ADDED
@@ -0,0 +1,19 @@
1
+ const { copyFolder } = require('./copy-folder.js');
2
+ const { cpIndexHtml } = require('./cp-index-html.js');
3
+ const fileUtils = require('./file-utils.js');
4
+ const webEnv = require('../src/common-js/web-env.js');
5
+ const { runCmd } = require('./run-cmd.js');
6
+ const { sendRequest } = require('./send-request.js');
7
+ module.exports = {
8
+ copyFolder,
9
+ cpIndexHtml,
10
+ runCmd,
11
+ sendRequest,
12
+ readJson: fileUtils.readJson,
13
+ pathExists: fileUtils.pathExists,
14
+ parseEnv: webEnv.parseEnv,
15
+ loadEnv: webEnv.loadEnv,
16
+ getWebConfig: webEnv.getWebConfig,
17
+ pluginIfelse: require('./plugin-ifelse.js'),
18
+ genVersions: require('./plugin-gen-versions.js'),
19
+ };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "lupine.api-dev",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "dev tools for lupine.api",
6
+ "main": "index.js",
7
+ "module": "index.js",
8
+ "umd:main": "index.js",
9
+ "source": "index.js",
10
+ "types": "index.js",
11
+ "license": "MIT"
12
+ }
@@ -0,0 +1,20 @@
1
+ const fs = require('fs/promises');
2
+
3
+ module.exports = (
4
+ packageDependencies,
5
+ outputFilename,
6
+ ) => {
7
+ return {
8
+ name: 'gen-versions',
9
+ setup(build) {
10
+ build.onStart(async () => {
11
+ const content = `export const LUPINE_WEB_VERSION = "${packageDependencies['lupine.web']}";
12
+ export const LUPINE_COMPONENTS_VERSION = "${packageDependencies['lupine.components']}";
13
+ export const LUPINE_API_VERSION = "${packageDependencies['lupine.api']}";
14
+ `;
15
+ await fs.writeFile(outputFilename, content);
16
+ console.log(`Generated ${outputFilename}`);
17
+ });
18
+ },
19
+ };
20
+ };
@@ -0,0 +1,155 @@
1
+ const fs = require('fs/promises');
2
+ const path = require('path');
3
+
4
+ const _saved = {
5
+ vars: [],
6
+ baseDir: '',
7
+ exclude: [],
8
+ };
9
+
10
+ const LINE_FLAG = {
11
+ code: 0,
12
+ if: 1,
13
+ else: 2,
14
+ endif: 3,
15
+ };
16
+
17
+ const ifRegExp = /^\/\/\s*#if\s*(.*)$/;
18
+ const elseifRegExp = /^\/\/\s*#elseif\s*(.*)$/;
19
+ const elseRegExp = /^\/\/\s*#else\s*(.*)$/;
20
+ const endifRegExp = /^\/\/\s*#endif$/;
21
+ const ifdefRegExpMultiLine = new RegExp(`^${ifRegExp.source}`, 'gm');
22
+
23
+ // if a variable is not defined, exception will be thrown
24
+ // if a variable is '', 0, false, undefined, null, NaN, or any other value, it will be considered as false
25
+ /*
26
+ var vars = {
27
+ TEST1: '', // false
28
+ TEST2: '2',
29
+ }
30
+ evalExpression(vars, 'TEST1'); // false
31
+ evalExpression(vars, 'TEST2'); // true
32
+ try {
33
+ evalExpression(vars, 'TEST3'); // exception
34
+ } catch {}
35
+ evalExpression(vars, "TEST2===`2` && TEST2==='2'"); // true, can't use ["] in the exception
36
+ evalExpression(vars, "TEST1 || TEST2"); // true
37
+
38
+ TODO: define variables if it's not defined?
39
+ function extractVariables(expression) {
40
+ // Match words that are not numbers or keywords
41
+ const variableRegex = /\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
42
+ return [...new Set(expression.match(variableRegex) || [])];
43
+ }
44
+ */
45
+ const evalExpression = (vars, expression) => {
46
+ const variables = Object.freeze({ ...vars });
47
+ const fn = new Function(...Object.keys(variables), 'return eval("' + expression + '")');
48
+ const result = !!fn(...Object.values(variables));
49
+ console.log(`Expression [${expression}], result: ${result}`);
50
+ return result;
51
+ };
52
+
53
+ const processOneFile = async (vars, fpath) => {
54
+ let text = await fs.readFile(fpath, 'utf8');
55
+ if (!ifdefRegExpMultiLine.test(text)) {
56
+ return null;
57
+ }
58
+
59
+ let lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
60
+ let line = '';
61
+ let expression = '';
62
+ let keepLine = true;
63
+ let lineType = LINE_FLAG.code;
64
+ const saveLines = [];
65
+ for (let i = 0; i < lines.length; i++) {
66
+ line = lines[i];
67
+
68
+ let matchIf = line.match(ifRegExp);
69
+ let matchElseIf = line.match(elseifRegExp);
70
+ if (matchIf) {
71
+ lineType = LINE_FLAG.if;
72
+ expression = matchIf[1].trim();
73
+ } else if (matchElseIf) {
74
+ lineType = LINE_FLAG.if;
75
+ expression = matchElseIf[1].trim();
76
+ } else if (line.match(elseRegExp)) {
77
+ lineType = LINE_FLAG.else;
78
+ } else if (line.match(endifRegExp)) {
79
+ lineType = LINE_FLAG.endif;
80
+ } else {
81
+ lineType = LINE_FLAG.code;
82
+ }
83
+
84
+ if (lineType === LINE_FLAG.if) {
85
+ keepLine = true;
86
+ const result = evalExpression(vars, expression);
87
+ if (!result) {
88
+ keepLine = false;
89
+ }
90
+ }
91
+ if (lineType === LINE_FLAG.else) {
92
+ console.log(`Expression else`);
93
+ keepLine = !keepLine;
94
+ }
95
+ if (lineType === LINE_FLAG.code) {
96
+ if (keepLine) {
97
+ saveLines.push(line);
98
+ }
99
+ }
100
+ if (lineType === LINE_FLAG.endif) {
101
+ keepLine = true;
102
+ }
103
+ }
104
+
105
+ text = saveLines.join('\n');
106
+ return text;
107
+ };
108
+
109
+ async function pluginOnLoad(args) {
110
+ let fPath = path.relative(_saved.baseDir, args.path);
111
+ let skip = false;
112
+
113
+ for (let filter of _saved.exclude) {
114
+ if (fPath.startsWith(filter)) {
115
+ skip = true;
116
+ break;
117
+ }
118
+ }
119
+
120
+ if (skip) {
121
+ return null;
122
+ }
123
+
124
+ const text = await processOneFile(_saved.vars, args.path);
125
+ if (text) {
126
+ return {
127
+ contents: text,
128
+ loader: path.extname(args.path).substring(1),
129
+ };
130
+ } else {
131
+ return null;
132
+ }
133
+ }
134
+
135
+ const DEFAULT_EXCLUDE_LIST = ['dist', 'node_modules'];
136
+ const DEFAULT_EXTENSION_LIST = ['.js', '.jsx', '.ts', '.tsx'];
137
+ module.exports = (
138
+ vars,
139
+ baseDir = process.cwd(),
140
+ exclude = DEFAULT_EXCLUDE_LIST,
141
+ extension = DEFAULT_EXTENSION_LIST
142
+ ) => {
143
+ _saved.vars = vars;
144
+ _saved.baseDir = baseDir;
145
+ _saved.exclude = exclude;
146
+ const filter = {
147
+ filter: new RegExp(`(${extension.join('|')})$`),
148
+ };
149
+ return {
150
+ name: 'plugin-ifdef',
151
+ setup(build) {
152
+ build.onLoad(filter, pluginOnLoad);
153
+ },
154
+ };
155
+ };
@@ -0,0 +1,37 @@
1
+ /*
2
+ (async () => {
3
+ var vars = {
4
+ TEST: undefined,
5
+ TEST1: '', // false
6
+ TEST2: '2',
7
+ PROD: false,
8
+ }
9
+ const x = await processOneFile(vars, 'test.js');
10
+ console.log(x);
11
+ process.exit(0);
12
+ })();
13
+ */
14
+ console.log('test start');
15
+ console.log('test start');
16
+ // #if TEST
17
+ console.log('test');
18
+ // #elseif PROD
19
+ console.log('prod');
20
+ // #else
21
+ console.log('not test');
22
+ // #endif
23
+ console.log('test end');
24
+ console.log('test end');
25
+ // #if TEST1 && TEST2
26
+ console.log('test1 and test2');
27
+ // #endif
28
+
29
+ // #if TEST1 || TEST2
30
+ console.log('test1 or test2');
31
+ // #endif
32
+
33
+ // #if TEST2
34
+ console.log('test2');
35
+ // #endif
36
+ console.log('test end');
37
+ console.log('test end');
package/dev/run-cmd.js ADDED
@@ -0,0 +1,14 @@
1
+ const cp = require('child_process');
2
+
3
+ exports.runCmd = (cmd) => {
4
+ const child = cp.spawn(`npm run ${cmd}`, { shell: true });
5
+ console.log(`[dev-server] Run: ${cmd}`);
6
+ // child.stdout.on('data', (data) => {
7
+ // process.stdout.write(`${data}`);
8
+ // });
9
+ // child.stderr.on('data', (data) => {
10
+ // process.stdout.write(`${data}`);
11
+ // });
12
+ child.stdout.pipe(process.stdout);
13
+ child.stderr.pipe(process.stderr);
14
+ };
@@ -0,0 +1,12 @@
1
+ exports.sendRequest = async (url, waitSeconds) => {
2
+ try {
3
+ // need node 18
4
+ const ret = await fetch(url, {
5
+ signal: AbortSignal.timeout(waitSeconds * 1000),
6
+ });
7
+ waitSeconds > 0 && (await new Promise((r) => setTimeout(r, waitSeconds * 1000)));
8
+ return ret;
9
+ } catch (err) {
10
+ console.error('sendRequest error: ', err);
11
+ }
12
+ };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "lupine.api",
3
+ "version": "1.0.41",
4
+ "license": "MIT",
5
+ "author": "uuware.com",
6
+ "homepage": "https://github.com/uuware/lupine.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/uuware/lupine.js.git",
10
+ "directory": "packages/lupine.api"
11
+ },
12
+ "description": "lupine.api is a fast, lightweight, and flexible node.js based server, working with lupine.web to provide SSR and modern JavaScript features for web applications and APIs.",
13
+ "main": "src/index.ts",
14
+ "source": "src/index.ts",
15
+ "types": "src/index.ts",
16
+ "engines": {
17
+ "node": ">= 20"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./src/index.ts",
22
+ "umd": "./src/index.ts",
23
+ "import": "./src/index.ts",
24
+ "require": "./src/index.ts"
25
+ },
26
+ "./admin": {
27
+ "types": "./admin/index.ts",
28
+ "import": "./admin/index.ts",
29
+ "require": "./admin/index.ts"
30
+ },
31
+ "./dev": {
32
+ "require": "./dev/index.js"
33
+ }
34
+ },
35
+ "keywords": [
36
+ "backend-end",
37
+ "lightweight"
38
+ ],
39
+ "scripts": {
40
+ "note": "echo 'build is not needed as the typescript code is supposed to be referred directly from other projects'",
41
+ "npm-publish": "npm publish --access public",
42
+ "build": "tsc"
43
+ },
44
+ "dependencies": {
45
+ "better-sqlite3": "^11.9.1",
46
+ "lupine.web": "^1.0.0",
47
+ "lupine.components": "^1.0.0"
48
+ },
49
+ "optionalDependencies": {
50
+ "mysql2": "^3.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/better-sqlite3": "^7.6.12"
54
+ }
55
+ }
@@ -0,0 +1,59 @@
1
+ import { ServerResponse } from 'http';
2
+ // import { AdminUser } from './admin-user';
3
+ import { AdminDb } from './admin-db';
4
+ import { AdminMenu } from './admin-menu';
5
+ import { devAdminAuth, needDevAdminSession } from './admin-auth';
6
+ import { AdminPerformance } from './admin-performance';
7
+ import { AdminRelease } from './admin-release';
8
+ import { AdminResources } from './admin-resources';
9
+ import { AdminTokens } from './admin-tokens';
10
+ import { AdminConfig } from './admin-config';
11
+ import { Logger } from '../lib';
12
+ import { IApiBase, ServerRequest } from '../models';
13
+ import {ApiRouter } from '../api';
14
+
15
+ const logger = new Logger('admin-api');
16
+
17
+ export class AdminApi implements IApiBase {
18
+ protected router = new ApiRouter();
19
+ adminUser: any;
20
+
21
+ constructor() {
22
+ this.mountDashboard();
23
+ }
24
+
25
+ public getRouter(): ApiRouter {
26
+ return this.router;
27
+ }
28
+
29
+ protected mountDashboard() {
30
+ const adminDb = new AdminDb();
31
+ this.router.use('/db', needDevAdminSession, adminDb.getRouter());
32
+
33
+ // const adminUsers = new AdminUser();
34
+ // this.router.use('/user', needDevAdminSession, adminUsers.getRouter());
35
+
36
+ const adminMenus = new AdminMenu();
37
+ this.router.use('/menu', needDevAdminSession, adminMenus.getRouter());
38
+
39
+ const adminPerformance = new AdminPerformance();
40
+ this.router.use('/performance', needDevAdminSession, adminPerformance.getRouter());
41
+
42
+ const adminRelease = new AdminRelease();
43
+ // as some endpoints check the token, so add needDevAdminSession inside
44
+ this.router.use('/release', adminRelease.getRouter());
45
+
46
+ const adminResources = new AdminResources();
47
+ this.router.use('/resources', needDevAdminSession, adminResources.getRouter());
48
+
49
+ const adminConfig = new AdminConfig();
50
+ this.router.use('/config', needDevAdminSession, adminConfig.getRouter());
51
+
52
+ const adminTokens = new AdminTokens();
53
+ this.router.use('/tokens', needDevAdminSession, adminTokens.getRouter());
54
+
55
+ this.router.use('/auth', async (req: ServerRequest, res: ServerResponse) => {
56
+ return devAdminAuth(req, res);
57
+ });
58
+ }
59
+ }
@@ -0,0 +1,87 @@
1
+ import { ServerResponse } from 'http';
2
+ import { Logger, ServerRequest, ApiHelper } from 'lupine.api';
3
+ import { CryptoUtils } from '../lib/utils/crypto';
4
+ import { adminHelper, DEV_ADMIN_CRYPTO_KEY_NAME, DEV_ADMIN_TYPE, DevAdminSessionProps } from './admin-helper';
5
+ import { langHelper } from '../lang';
6
+
7
+ const logger = new Logger('admin-auth');
8
+ export const needDevAdminSession = async (req: ServerRequest, res: ServerResponse) => {
9
+ const devAdminSession = await adminHelper.getDevAdminFromCookie(req, res, true);
10
+ if (!devAdminSession) {
11
+ // return true to skip the rest of the middleware
12
+ return true;
13
+ }
14
+ return false;
15
+ };
16
+
17
+ // dev admin, for development only
18
+ export const devAdminAuth = async (req: ServerRequest, res: ServerResponse) => {
19
+ const cryptoKey = process.env[DEV_ADMIN_CRYPTO_KEY_NAME];
20
+ if (!cryptoKey) {
21
+ const msg = langHelper.getLang('shared:crypto_key_not_set', {
22
+ cryptoKey: DEV_ADMIN_CRYPTO_KEY_NAME,
23
+ });
24
+ logger.error(msg);
25
+ const response = {
26
+ status: 'error',
27
+ message: msg,
28
+ };
29
+ ApiHelper.sendJson(req, res, response);
30
+ return true;
31
+ }
32
+ if (!process.env['DEV_ADMIN_PASS']) {
33
+ const msg = langHelper.getLang('shared:name_not_set', {
34
+ name: 'DEV_ADMIN_PASS',
35
+ });
36
+ logger.error(msg);
37
+ const response = {
38
+ status: 'error',
39
+ message: msg,
40
+ };
41
+ ApiHelper.sendJson(req, res, response);
42
+ return true;
43
+ }
44
+
45
+ // TODO: secure and httpOnly cookies
46
+ const data = req.locals.json();
47
+ if (!data || Array.isArray(data) || !data.u || !data.p) {
48
+ // if session already exists, use session data login
49
+ const devAdminSession = await adminHelper.getDevAdminFromCookie(req, res, true);
50
+ if (!devAdminSession) {
51
+ return true;
52
+ }
53
+ const response = {
54
+ status: 'ok',
55
+ message: langHelper.getLang('shared:login_success'),
56
+ result: CryptoUtils.encrypt(JSON.stringify(devAdminSession), cryptoKey),
57
+ };
58
+ ApiHelper.sendJson(req, res, response);
59
+ return true;
60
+ }
61
+
62
+ if (data.u === process.env['DEV_ADMIN_USER'] && data.p === process.env['DEV_ADMIN_PASS']) {
63
+ logger.info('dev admin logged in');
64
+ const devSession: DevAdminSessionProps = {
65
+ u: data.u,
66
+ t: DEV_ADMIN_TYPE,
67
+ ip: req.socket.remoteAddress as string,
68
+ h: CryptoUtils.hash(data.u + ':' + data.p),
69
+ };
70
+ const token = JSON.stringify(devSession);
71
+ const response = {
72
+ status: 'ok',
73
+ message: langHelper.getLang('shared:login_success'),
74
+ result: CryptoUtils.encrypt(token, cryptoKey),
75
+ };
76
+ ApiHelper.sendJson(req, res, response);
77
+ return true;
78
+ }
79
+
80
+ logger.info(`dev admin login failed: ${((data.u as string) || '').substring(0, 30)}`);
81
+ const response = {
82
+ status: 'error',
83
+ message: langHelper.getLang('shared:login_failed'),
84
+ };
85
+ ApiHelper.sendJson(req, res, response);
86
+ return true;
87
+ };
@@ -0,0 +1,93 @@
1
+ import { ServerResponse } from 'http';
2
+ import * as fs from 'fs/promises';
3
+ import {
4
+ IApiBase,
5
+ Logger,
6
+ apiCache,
7
+ ServerRequest,
8
+ ApiRouter,
9
+ ApiHelper,
10
+ langHelper,
11
+ FsUtils,
12
+ adminHelper,
13
+ } from 'lupine.api';
14
+ import path from 'path';
15
+ import { needDevAdminSession } from './admin-auth';
16
+
17
+ export class AdminConfig implements IApiBase {
18
+ private logger = new Logger('config-api');
19
+ protected router = new ApiRouter();
20
+
21
+ constructor() {
22
+ this.mountDashboard();
23
+ }
24
+
25
+ public getRouter(): ApiRouter {
26
+ return this.router;
27
+ }
28
+
29
+ protected mountDashboard() {
30
+ // called by FE
31
+ this.router.use('/load', this.load.bind(this));
32
+ this.router.use('/save', this.save.bind(this));
33
+ }
34
+
35
+ async load(req: ServerRequest, res: ServerResponse) {
36
+ const appData = apiCache.getAppData();
37
+ let cfgPath = path.join(appData.dataPath, 'config.json');
38
+ if (!(await FsUtils.pathExist(cfgPath))) {
39
+ cfgPath = path.join(appData.dataPath, 'resources', 'config_default.json');
40
+ }
41
+ if (!(await FsUtils.pathExist(cfgPath))) {
42
+ const response = {
43
+ status: 'error',
44
+ message: langHelper.getLang('shared:not_found_file', { fileName: 'config.json' }),
45
+ };
46
+ ApiHelper.sendJson(req, res, response);
47
+ return true;
48
+ }
49
+
50
+ let jsonCfg: any;
51
+ try {
52
+ jsonCfg = JSON.parse(await fs.readFile(cfgPath, 'utf8'));
53
+ } catch (e) {
54
+ const response = {
55
+ status: 'error',
56
+ message: langHelper.getLang('shared:not_found_file', { fileName: 'config.json' }),
57
+ };
58
+ ApiHelper.sendJson(req, res, response);
59
+ return true;
60
+ }
61
+ const response = {
62
+ status: 'ok',
63
+ message: langHelper.getLang('shared:operation_success'),
64
+ result: jsonCfg,
65
+ };
66
+ ApiHelper.sendJson(req, res, response);
67
+ return true;
68
+ }
69
+
70
+ // called by clients
71
+ async save(req: ServerRequest, res: ServerResponse) {
72
+ const data = req.locals.json();
73
+ if (!data || Array.isArray(data) || !data.json) {
74
+ const response = {
75
+ status: 'error',
76
+ message: langHelper.getLang('shared:wrong_data'),
77
+ };
78
+ ApiHelper.sendJson(req, res, response);
79
+ return true;
80
+ }
81
+
82
+ const appData = apiCache.getAppData();
83
+ const cfgPath = path.join(appData.dataPath, 'config.json');
84
+ await fs.writeFile(cfgPath, JSON.stringify(data.json));
85
+
86
+ const response = {
87
+ status: 'ok',
88
+ message: langHelper.getLang('shared:operation_success'),
89
+ };
90
+ ApiHelper.sendJson(req, res, response);
91
+ return true;
92
+ }
93
+ }