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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/admin/admin-about.tsx +16 -0
- package/admin/admin-config.tsx +44 -0
- package/admin/admin-css.tsx +3 -0
- package/admin/admin-db.tsx +74 -0
- package/admin/admin-frame-props.tsx +9 -0
- package/admin/admin-frame.tsx +466 -0
- package/admin/admin-index.tsx +66 -0
- package/admin/admin-login.tsx +99 -0
- package/admin/admin-menu-edit.tsx +637 -0
- package/admin/admin-menu-list.tsx +87 -0
- package/admin/admin-page-edit.tsx +564 -0
- package/admin/admin-page-list.tsx +83 -0
- package/admin/admin-performance.tsx +28 -0
- package/admin/admin-release.tsx +320 -0
- package/admin/admin-resources.tsx +385 -0
- package/admin/admin-shell.tsx +89 -0
- package/admin/admin-table-data.tsx +146 -0
- package/admin/admin-table-list.tsx +231 -0
- package/admin/admin-test-animations.tsx +379 -0
- package/admin/admin-test-component.tsx +808 -0
- package/admin/admin-test-edit.tsx +319 -0
- package/admin/admin-test-themes.tsx +56 -0
- package/admin/admin-tokens.tsx +338 -0
- package/admin/design/admin-design.tsx +174 -0
- package/admin/design/block-grid.tsx +36 -0
- package/admin/design/block-grid1.tsx +21 -0
- package/admin/design/block-paragraph.tsx +19 -0
- package/admin/design/block-title.tsx +19 -0
- package/admin/design/design-block-box.tsx +140 -0
- package/admin/design/drag-data.tsx +24 -0
- package/admin/index.ts +6 -0
- package/admin/package.json +15 -0
- package/admin/tsconfig.json +127 -0
- package/dev/copy-folder.js +32 -0
- package/dev/cp-index-html.js +69 -0
- package/dev/file-utils.js +12 -0
- package/dev/index.js +19 -0
- package/dev/package.json +12 -0
- package/dev/plugin-gen-versions.js +20 -0
- package/dev/plugin-ifelse.js +155 -0
- package/dev/plugin-ifelse.test.js +37 -0
- package/dev/run-cmd.js +14 -0
- package/dev/send-request.js +12 -0
- package/package.json +55 -0
- package/src/admin-api/admin-api.ts +59 -0
- package/src/admin-api/admin-auth.ts +87 -0
- package/src/admin-api/admin-config.ts +93 -0
- package/src/admin-api/admin-csv.ts +81 -0
- package/src/admin-api/admin-db.ts +269 -0
- package/src/admin-api/admin-helper.ts +111 -0
- package/src/admin-api/admin-menu.ts +135 -0
- package/src/admin-api/admin-page.ts +135 -0
- package/src/admin-api/admin-performance.ts +128 -0
- package/src/admin-api/admin-release.ts +498 -0
- package/src/admin-api/admin-resources.ts +318 -0
- package/src/admin-api/admin-token-helper.ts +79 -0
- package/src/admin-api/admin-tokens.ts +90 -0
- package/src/admin-api/index.ts +2 -0
- package/src/api/api-cache.ts +103 -0
- package/src/api/api-helper.ts +44 -0
- package/src/api/api-module.ts +60 -0
- package/src/api/api-router.ts +177 -0
- package/src/api/api-shared-storage.ts +64 -0
- package/src/api/async-storage.ts +5 -0
- package/src/api/debug-service.ts +56 -0
- package/src/api/encode-html.ts +27 -0
- package/src/api/handle-status.ts +71 -0
- package/src/api/index.ts +16 -0
- package/src/api/mini-web-socket.ts +270 -0
- package/src/api/server-content-type.ts +82 -0
- package/src/api/server-render.ts +216 -0
- package/src/api/shell-service.ts +66 -0
- package/src/api/simple-storage.ts +80 -0
- package/src/api/static-server.ts +125 -0
- package/src/api/to-client-delivery.ts +26 -0
- package/src/app/app-cache.ts +55 -0
- package/src/app/app-loader.ts +62 -0
- package/src/app/app-message.ts +60 -0
- package/src/app/app-shared-storage.ts +317 -0
- package/src/app/app-start.ts +117 -0
- package/src/app/cleanup-exit.ts +12 -0
- package/src/app/host-to-path.ts +38 -0
- package/src/app/index.ts +11 -0
- package/src/app/process-dev-requests.ts +90 -0
- package/src/app/web-listener.ts +230 -0
- package/src/app/web-processor.ts +42 -0
- package/src/app/web-server.ts +86 -0
- package/src/common-js/web-env.js +104 -0
- package/src/index.ts +7 -0
- package/src/lang/api-lang-en.ts +27 -0
- package/src/lang/api-lang-zh-cn.ts +28 -0
- package/src/lang/index.ts +2 -0
- package/src/lang/lang-helper.ts +76 -0
- package/src/lang/lang-props.ts +6 -0
- package/src/lib/db/db-helper.ts +23 -0
- package/src/lib/db/db-mysql.ts +250 -0
- package/src/lib/db/db-sqlite.ts +101 -0
- package/src/lib/db/db.spec.ts +28 -0
- package/src/lib/db/db.ts +304 -0
- package/src/lib/db/index.ts +5 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/logger.spec.ts +214 -0
- package/src/lib/logger.ts +274 -0
- package/src/lib/runtime-require.ts +37 -0
- package/src/lib/utils/cookie-util.ts +34 -0
- package/src/lib/utils/crypto.ts +58 -0
- package/src/lib/utils/date-utils.ts +317 -0
- package/src/lib/utils/deep-merge.ts +37 -0
- package/src/lib/utils/delay.ts +12 -0
- package/src/lib/utils/file-setting.ts +55 -0
- package/src/lib/utils/format-bytes.ts +11 -0
- package/src/lib/utils/fs-utils.ts +144 -0
- package/src/lib/utils/get-env.ts +27 -0
- package/src/lib/utils/index.ts +12 -0
- package/src/lib/utils/is-type.ts +48 -0
- package/src/lib/utils/load-env.ts +14 -0
- package/src/lib/utils/pad.ts +6 -0
- package/src/models/api-base.ts +5 -0
- package/src/models/api-module-props.ts +11 -0
- package/src/models/api-router-props.ts +26 -0
- package/src/models/app-cache-props.ts +33 -0
- package/src/models/app-data-props.ts +10 -0
- package/src/models/app-loader-props.ts +6 -0
- package/src/models/app-shared-storage-props.ts +37 -0
- package/src/models/app-start-props.ts +18 -0
- package/src/models/async-storage-props.ts +13 -0
- package/src/models/db-config.ts +30 -0
- package/src/models/host-to-path-props.ts +12 -0
- package/src/models/index.ts +16 -0
- package/src/models/json-object.ts +8 -0
- package/src/models/locals-props.ts +36 -0
- package/src/models/logger-props.ts +84 -0
- package/src/models/simple-storage-props.ts +14 -0
- package/src/models/to-client-delivery-props.ts +6 -0
- 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
|
+
};
|
package/dev/package.json
ADDED
|
@@ -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
|
+
}
|