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,498 @@
|
|
|
1
|
+
import { ServerResponse } from 'http';
|
|
2
|
+
import {
|
|
3
|
+
IApiBase,
|
|
4
|
+
Logger,
|
|
5
|
+
apiCache,
|
|
6
|
+
ServerRequest,
|
|
7
|
+
ApiRouter,
|
|
8
|
+
ApiHelper,
|
|
9
|
+
langHelper,
|
|
10
|
+
FsUtils,
|
|
11
|
+
adminHelper,
|
|
12
|
+
processRefreshCache,
|
|
13
|
+
} from 'lupine.api';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { needDevAdminSession } from './admin-auth';
|
|
16
|
+
import { adminTokenHelper } from './admin-token-helper';
|
|
17
|
+
import { Readable } from 'stream';
|
|
18
|
+
|
|
19
|
+
export class AdminRelease implements IApiBase {
|
|
20
|
+
private logger = new Logger('release-api');
|
|
21
|
+
protected router = new ApiRouter();
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
this.mountDashboard();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public getRouter(): ApiRouter {
|
|
28
|
+
return this.router;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected mountDashboard() {
|
|
32
|
+
// called by FE
|
|
33
|
+
this.router.use('/check', needDevAdminSession, this.check.bind(this));
|
|
34
|
+
this.router.use('/update', needDevAdminSession, this.update.bind(this));
|
|
35
|
+
this.router.use('/view-log', needDevAdminSession, this.viewLog.bind(this));
|
|
36
|
+
// called online or by clients
|
|
37
|
+
this.router.use('/refresh-cache', needDevAdminSession, this.refreshCache.bind(this));
|
|
38
|
+
|
|
39
|
+
// ...ByClient will verify credentials from post, so it doesn't need AdminSession
|
|
40
|
+
this.router.use('/byClientCheck', this.byClientCheck.bind(this));
|
|
41
|
+
this.router.use('/byClientUpdate', this.byClientUpdate.bind(this));
|
|
42
|
+
this.router.use('/byClientRefreshCache', this.byClientRefreshCache.bind(this));
|
|
43
|
+
this.router.use('/byClientViewLog', this.byClientViewLog.bind(this));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async viewLog(req: ServerRequest, res: ServerResponse) {
|
|
47
|
+
const jsonData = req.locals.json();
|
|
48
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
49
|
+
if (!data) return true;
|
|
50
|
+
|
|
51
|
+
let targetUrl = data.targetUrl as string;
|
|
52
|
+
if (targetUrl.endsWith('/')) {
|
|
53
|
+
targetUrl = targetUrl.slice(0, -1);
|
|
54
|
+
}
|
|
55
|
+
const remoteData = await fetch(targetUrl + '/api/admin/release/byClientViewLog', {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
body: JSON.stringify(data),
|
|
58
|
+
});
|
|
59
|
+
// (remoteData.body as any).pipe(res);
|
|
60
|
+
const data2 = await remoteData.text();
|
|
61
|
+
// res.setHeader('Content-Disposition', 'attachment; filename="log.txt"');
|
|
62
|
+
res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
|
|
63
|
+
res.write(data2);
|
|
64
|
+
res.end();
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async byClientViewLog(req: ServerRequest, res: ServerResponse) {
|
|
69
|
+
const jsonData = req.locals.json();
|
|
70
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
71
|
+
if (!data) return true;
|
|
72
|
+
|
|
73
|
+
const appData = apiCache.getAppData();
|
|
74
|
+
const logFile = path.join(appData.apiPath, '../../log', data.logName);
|
|
75
|
+
if (!(await FsUtils.pathExist(logFile))) {
|
|
76
|
+
const response = {
|
|
77
|
+
status: 'error',
|
|
78
|
+
message: 'Log file not found.',
|
|
79
|
+
};
|
|
80
|
+
ApiHelper.sendJson(req, res, response);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
ApiHelper.sendFile(req, res, logFile);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async refreshCache(req: ServerRequest, res: ServerResponse) {
|
|
88
|
+
// check whether it's from online admin
|
|
89
|
+
const json = await adminHelper.getDevAdminFromCookie(req, res, false);
|
|
90
|
+
const jsonData = req.locals.json();
|
|
91
|
+
if (json && jsonData && !Array.isArray(jsonData) && jsonData.isLocal) {
|
|
92
|
+
await processRefreshCache(req);
|
|
93
|
+
const response = {
|
|
94
|
+
status: 'ok',
|
|
95
|
+
message: 'Cache refreshed successfully.',
|
|
96
|
+
};
|
|
97
|
+
ApiHelper.sendJson(req, res, response);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
102
|
+
if (!data) return true;
|
|
103
|
+
|
|
104
|
+
let targetUrl = data.targetUrl as string;
|
|
105
|
+
if (targetUrl.endsWith('/')) {
|
|
106
|
+
targetUrl = targetUrl.slice(0, -1);
|
|
107
|
+
}
|
|
108
|
+
const remoteData = await fetch(targetUrl + '/api/admin/release/byClientRefreshCache', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
body: JSON.stringify(data),
|
|
111
|
+
});
|
|
112
|
+
const resultText = await remoteData.text();
|
|
113
|
+
let remoteResult: any;
|
|
114
|
+
try {
|
|
115
|
+
remoteResult = JSON.parse(resultText);
|
|
116
|
+
} catch (e: any) {
|
|
117
|
+
remoteResult = { status: 'error', message: resultText };
|
|
118
|
+
}
|
|
119
|
+
const response = {
|
|
120
|
+
status: 'ok',
|
|
121
|
+
message: 'check.',
|
|
122
|
+
...remoteResult,
|
|
123
|
+
};
|
|
124
|
+
ApiHelper.sendJson(req, res, response);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public async chkData(data: any, req: ServerRequest, res: ServerResponse, chkCredential: boolean) {
|
|
129
|
+
// add access token
|
|
130
|
+
if (!data || Array.isArray(data) || typeof data !== 'object' || !data.accessToken || !data.targetUrl) {
|
|
131
|
+
const response = {
|
|
132
|
+
status: 'error',
|
|
133
|
+
message: 'Wrong data [missing parameters].', //langHelper.getLang('shared:wrong_data'),
|
|
134
|
+
};
|
|
135
|
+
ApiHelper.sendJson(req, res, response);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (chkCredential) {
|
|
139
|
+
if (await adminTokenHelper.validateToken(data.accessToken)) {
|
|
140
|
+
return data;
|
|
141
|
+
} else if (
|
|
142
|
+
process.env['DEV_ADMIN_PASS'] !== '' &&
|
|
143
|
+
(data.accessToken === `${process.env['DEV_ADMIN_USER']}@${process.env['DEV_ADMIN_PASS']}` ||
|
|
144
|
+
data.accessToken === `${process.env['DEV_ADMIN_USER']}:${process.env['DEV_ADMIN_PASS']}`)
|
|
145
|
+
) {
|
|
146
|
+
return data;
|
|
147
|
+
} else {
|
|
148
|
+
const response = {
|
|
149
|
+
status: 'error',
|
|
150
|
+
message: 'Wrong data [wrong token].', //langHelper.getLang('shared:wrong_data'),
|
|
151
|
+
};
|
|
152
|
+
ApiHelper.sendJson(req, res, response);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// this is called by the FE, then call byClientCheck to get remote server's information
|
|
160
|
+
async check(req: ServerRequest, res: ServerResponse) {
|
|
161
|
+
const jsonData = req.locals.json();
|
|
162
|
+
const data = await this.chkData(jsonData, req, res, false);
|
|
163
|
+
if (!data) return true;
|
|
164
|
+
|
|
165
|
+
// From app list is from local
|
|
166
|
+
const appData = apiCache.getAppData();
|
|
167
|
+
const folders = await FsUtils.getDirAndFiles(path.join(appData.apiPath, '..'));
|
|
168
|
+
const apps = folders.filter((app: string) => app.endsWith('_web')).map((app: string) => app.replace('_web', ''));
|
|
169
|
+
|
|
170
|
+
let targetUrl = data.targetUrl as string;
|
|
171
|
+
if (targetUrl.endsWith('/')) {
|
|
172
|
+
targetUrl = targetUrl.slice(0, -1);
|
|
173
|
+
}
|
|
174
|
+
const remoteData = await fetch(targetUrl + '/api/admin/release/byClientCheck', {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
body: JSON.stringify(data),
|
|
177
|
+
});
|
|
178
|
+
const resultText = await remoteData.text();
|
|
179
|
+
let remoteResult: any;
|
|
180
|
+
try {
|
|
181
|
+
remoteResult = JSON.parse(resultText);
|
|
182
|
+
} catch (e: any) {
|
|
183
|
+
remoteResult = { status: 'error', message: resultText };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// local dirs under _web
|
|
187
|
+
const webSubFolders = await FsUtils.getDirsFullpath(appData.webPath);
|
|
188
|
+
const response = {
|
|
189
|
+
status: 'ok',
|
|
190
|
+
message: 'check.',
|
|
191
|
+
appsFrom: apps,
|
|
192
|
+
...remoteResult,
|
|
193
|
+
webSub: webSubFolders.filter((folder) => folder.isDirectory()).map((folder) => folder.name),
|
|
194
|
+
};
|
|
195
|
+
ApiHelper.sendJson(req, res, response);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async getFileList(parentPath: string, subFolders: string[]) {
|
|
200
|
+
const subFoldersWithTime = [];
|
|
201
|
+
for (let j = 0; j < subFolders.length; j++) {
|
|
202
|
+
const subFolder = subFolders[j];
|
|
203
|
+
const fileInfo = await FsUtils.fileInfo(path.join(parentPath, subFolder));
|
|
204
|
+
subFoldersWithTime.push({
|
|
205
|
+
name: subFolder,
|
|
206
|
+
time: new Date(fileInfo!.mtime).toLocaleString(),
|
|
207
|
+
size: fileInfo?.size,
|
|
208
|
+
dir: fileInfo?.isDir,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return subFoldersWithTime;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// called by clients
|
|
215
|
+
async byClientCheck(req: ServerRequest, res: ServerResponse) {
|
|
216
|
+
const jsonData = req.locals.json();
|
|
217
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
218
|
+
if (!data) return true;
|
|
219
|
+
|
|
220
|
+
const appData = apiCache.getAppData();
|
|
221
|
+
const folders = await FsUtils.getDirAndFiles(path.join(appData.apiPath, '..'));
|
|
222
|
+
const apps = folders.filter((app: string) => app.endsWith('_web')).map((app: string) => app.replace('_web', ''));
|
|
223
|
+
|
|
224
|
+
const foldersWithTime = [];
|
|
225
|
+
for (let i = 0; i < folders.length; i++) {
|
|
226
|
+
const folder = folders[i];
|
|
227
|
+
const subFolders = await FsUtils.getDirAndFiles(path.join(appData.apiPath, '..', folder));
|
|
228
|
+
const subFoldersWithTime = await this.getFileList(path.join(appData.apiPath, '..', folder), subFolders);
|
|
229
|
+
const fileInfo = await FsUtils.fileInfo(path.join(appData.apiPath, '..', folder));
|
|
230
|
+
foldersWithTime.push({
|
|
231
|
+
name: folder,
|
|
232
|
+
time: new Date(fileInfo!.mtime).toLocaleString(),
|
|
233
|
+
items: subFoldersWithTime,
|
|
234
|
+
dir: fileInfo?.isDir,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const logFolders = await FsUtils.getDirAndFiles(path.join(appData.apiPath, '../../log'));
|
|
239
|
+
const logFoldersWithTime = await this.getFileList(path.join(appData.apiPath, '../../log'), logFolders);
|
|
240
|
+
const response = {
|
|
241
|
+
status: 'ok',
|
|
242
|
+
message: 'Remote server information called from a client.',
|
|
243
|
+
appData: appData as any,
|
|
244
|
+
apps,
|
|
245
|
+
folders,
|
|
246
|
+
foldersWithTime,
|
|
247
|
+
logs: logFoldersWithTime,
|
|
248
|
+
};
|
|
249
|
+
ApiHelper.sendJson(req, res, response);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async update(req: ServerRequest, res: ServerResponse) {
|
|
254
|
+
const jsonData = req.locals.json();
|
|
255
|
+
const data = await this.chkData(jsonData, req, res, false);
|
|
256
|
+
if (!data) return true;
|
|
257
|
+
|
|
258
|
+
if (!data.chkServer && !data.chkApi && !data.chkWeb && !data.chkEnv) {
|
|
259
|
+
const response = {
|
|
260
|
+
status: 'error',
|
|
261
|
+
message: langHelper.getLang('shared:wrong_data'),
|
|
262
|
+
};
|
|
263
|
+
ApiHelper.sendJson(req, res, response);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let targetUrl = data.targetUrl as string;
|
|
268
|
+
if (targetUrl.endsWith('/')) {
|
|
269
|
+
targetUrl = targetUrl.slice(0, -1);
|
|
270
|
+
}
|
|
271
|
+
if (data.chkEnv) {
|
|
272
|
+
const result = await this.updateSendFile(data, '.env');
|
|
273
|
+
if (!result || result.status !== 'ok') {
|
|
274
|
+
ApiHelper.sendJson(req, res, result);
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
const result2 = await this.updateSendFile(data, '.env.development');
|
|
278
|
+
if (!result2 || result2.status !== 'ok') {
|
|
279
|
+
ApiHelper.sendJson(req, res, result2);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
const result3 = await this.updateSendFile(data, '.env.production');
|
|
283
|
+
if (!result3 || result3.status !== 'ok') {
|
|
284
|
+
ApiHelper.sendJson(req, res, result3);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (data.chkWeb) {
|
|
289
|
+
const result = await this.updateSendFile(data, 'web');
|
|
290
|
+
if (!result || result.status !== 'ok') {
|
|
291
|
+
ApiHelper.sendJson(req, res, result);
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
if (data.webSub) {
|
|
295
|
+
const result2 = await this.updateSendFile(data, 'web-sub');
|
|
296
|
+
if (!result2 || result2.status !== 'ok') {
|
|
297
|
+
ApiHelper.sendJson(req, res, result2);
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (data.webSubs && data.webSubs.length > 0) {
|
|
303
|
+
for (let i = 0; i < data.webSubs.length; i++) {
|
|
304
|
+
data.webSub = data.webSubs[i];
|
|
305
|
+
const result2 = await this.updateSendFile(data, 'web-sub');
|
|
306
|
+
if (!result2 || result2.status !== 'ok') {
|
|
307
|
+
ApiHelper.sendJson(req, res, result2);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (data.chkApi) {
|
|
314
|
+
const result = await this.updateSendFile(data, 'api');
|
|
315
|
+
if (!result || result.status !== 'ok') {
|
|
316
|
+
ApiHelper.sendJson(req, res, result);
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// update server at the last
|
|
321
|
+
if (data.chkServer) {
|
|
322
|
+
const result = await this.updateSendFile(data, 'server');
|
|
323
|
+
if (!result || result.status !== 'ok') {
|
|
324
|
+
ApiHelper.sendJson(req, res, result);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const response = {
|
|
330
|
+
status: 'ok',
|
|
331
|
+
message: 'updated',
|
|
332
|
+
};
|
|
333
|
+
ApiHelper.sendJson(req, res, response);
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async updateSendFile(data: any, chkOption: string) {
|
|
338
|
+
let targetUrl = data.targetUrl;
|
|
339
|
+
if (targetUrl.endsWith('/')) {
|
|
340
|
+
targetUrl = targetUrl.slice(0, -1);
|
|
341
|
+
}
|
|
342
|
+
const fromList = data.fromList;
|
|
343
|
+
const appData = apiCache.getAppData();
|
|
344
|
+
let sendFile = '';
|
|
345
|
+
if (chkOption === 'server') {
|
|
346
|
+
sendFile = path.join(appData.apiPath, '..', 'server', 'index.js');
|
|
347
|
+
} else if (chkOption === 'api') {
|
|
348
|
+
sendFile = path.join(appData.apiPath, '..', fromList + '_api', 'index.js');
|
|
349
|
+
} else if (chkOption === 'web') {
|
|
350
|
+
sendFile = path.join(appData.apiPath, '..', fromList + '_web', 'index.js');
|
|
351
|
+
} else if (chkOption === 'web-sub' && data.webSub) {
|
|
352
|
+
sendFile = path.join(appData.apiPath, '..', fromList + '_web', data.webSub, 'index.js');
|
|
353
|
+
} else if (chkOption.startsWith('.env')) {
|
|
354
|
+
sendFile = path.join(appData.apiPath, '../../..', chkOption);
|
|
355
|
+
}
|
|
356
|
+
if (!(await FsUtils.pathExist(sendFile))) {
|
|
357
|
+
return { status: 'error', message: 'Client file not found: ' + sendFile };
|
|
358
|
+
}
|
|
359
|
+
const fileContent = (await FsUtils.readFile(sendFile))!;
|
|
360
|
+
// const compressedContent = await new Promise<Buffer>((resolve, reject) => {
|
|
361
|
+
// zlib.gzip(fileContent, (err, buffer) => {
|
|
362
|
+
// if (err) {
|
|
363
|
+
// reject(err);
|
|
364
|
+
// } else {
|
|
365
|
+
// resolve(buffer);
|
|
366
|
+
// }
|
|
367
|
+
// });
|
|
368
|
+
// })
|
|
369
|
+
const chunkSize = 1024 * 500;
|
|
370
|
+
let cnt = 0;
|
|
371
|
+
for (let i = 0; i < fileContent.length; i += chunkSize) {
|
|
372
|
+
const chunk = fileContent.slice(i, i + chunkSize);
|
|
373
|
+
if (!chunk) break;
|
|
374
|
+
|
|
375
|
+
const postData = {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
body: JSON.stringify({ ...data, chkOption, index: cnt, size: fileContent.length }) + '\n\n' + chunk,
|
|
378
|
+
};
|
|
379
|
+
this.logger.debug(`updateSendFile, index: ${cnt}, sending (max): ${i + chunkSize} / ${fileContent.length}`);
|
|
380
|
+
const remoteData = await fetch(targetUrl + '/api/admin/release/byClientUpdate', postData);
|
|
381
|
+
const resultText = await remoteData.text();
|
|
382
|
+
let remoteResult: any;
|
|
383
|
+
try {
|
|
384
|
+
remoteResult = JSON.parse(resultText);
|
|
385
|
+
} catch (e: any) {
|
|
386
|
+
remoteResult = { status: 'error', message: resultText };
|
|
387
|
+
}
|
|
388
|
+
if (!remoteResult || remoteResult.status !== 'ok') {
|
|
389
|
+
return remoteResult;
|
|
390
|
+
}
|
|
391
|
+
cnt++;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const remoteResult = { status: 'ok', message: 'updated' };
|
|
395
|
+
return remoteResult;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// called by clients
|
|
399
|
+
async byClientUpdate(req: ServerRequest, res: ServerResponse) {
|
|
400
|
+
const body = req.locals.body as Buffer;
|
|
401
|
+
let jsonData = {};
|
|
402
|
+
let fileContent = null;
|
|
403
|
+
try {
|
|
404
|
+
const index = body.indexOf('\n\n');
|
|
405
|
+
if (index !== -1) {
|
|
406
|
+
jsonData = JSON.parse(body.subarray(0, index).toString());
|
|
407
|
+
fileContent = body.subarray(index + 2);
|
|
408
|
+
}
|
|
409
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
410
|
+
if (!data) return true;
|
|
411
|
+
|
|
412
|
+
const toList = data.toList as string;
|
|
413
|
+
const chkOption = data.chkOption as string;
|
|
414
|
+
if (
|
|
415
|
+
!chkOption ||
|
|
416
|
+
!toList ||
|
|
417
|
+
(chkOption !== 'server' &&
|
|
418
|
+
chkOption !== 'api' &&
|
|
419
|
+
chkOption !== 'web' &&
|
|
420
|
+
chkOption !== 'web-sub' &&
|
|
421
|
+
!chkOption.startsWith('.env'))
|
|
422
|
+
) {
|
|
423
|
+
const response = {
|
|
424
|
+
status: 'error',
|
|
425
|
+
message: 'Wrong data.',
|
|
426
|
+
};
|
|
427
|
+
ApiHelper.sendJson(req, res, response);
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const appData = apiCache.getAppData();
|
|
432
|
+
let saveFile = '';
|
|
433
|
+
if (chkOption === 'server') {
|
|
434
|
+
saveFile = path.join(appData.apiPath, '..', 'server', 'index.js');
|
|
435
|
+
} else if (chkOption === 'api') {
|
|
436
|
+
saveFile = path.join(appData.apiPath, '..', toList + '_api', 'index.js');
|
|
437
|
+
} else if (chkOption === 'web') {
|
|
438
|
+
saveFile = path.join(appData.apiPath, '..', toList + '_web', 'index.js');
|
|
439
|
+
} else if (chkOption === 'web-sub' && data.webSub) {
|
|
440
|
+
const folder = path.join(appData.apiPath, '..', toList + '_web', data.webSub);
|
|
441
|
+
if (!(await FsUtils.pathExist(folder))) {
|
|
442
|
+
await FsUtils.mkdir(folder);
|
|
443
|
+
}
|
|
444
|
+
saveFile = path.join(appData.apiPath, '..', toList + '_web', data.webSub, 'index.js');
|
|
445
|
+
} else if ((chkOption as string).startsWith('.env')) {
|
|
446
|
+
saveFile = path.join(appData.apiPath, '../../..', chkOption);
|
|
447
|
+
}
|
|
448
|
+
if (chkOption !== 'web-sub' && !(await FsUtils.pathExist(saveFile))) {
|
|
449
|
+
const response = {
|
|
450
|
+
status: 'error',
|
|
451
|
+
message: 'Server file not found: ' + saveFile,
|
|
452
|
+
};
|
|
453
|
+
ApiHelper.sendJson(req, res, response);
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
if (data.chkBackup && data.index === 0) {
|
|
457
|
+
const bakContent = await FsUtils.readFile(saveFile);
|
|
458
|
+
if (bakContent) {
|
|
459
|
+
const bakFile = saveFile + '.bak-' + new Date().toISOString().replace(/:/g, '-');
|
|
460
|
+
await FsUtils.writeFile(bakFile, bakContent);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (data.index === 0) {
|
|
464
|
+
await FsUtils.writeFile(saveFile, fileContent || '');
|
|
465
|
+
} else {
|
|
466
|
+
await FsUtils.appendFile(saveFile, fileContent || '');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const response = {
|
|
470
|
+
status: 'ok',
|
|
471
|
+
message: 'Remote server updated by a client.',
|
|
472
|
+
};
|
|
473
|
+
ApiHelper.sendJson(req, res, response);
|
|
474
|
+
} catch (e: any) {
|
|
475
|
+
console.log('byClientUpdate failed', e);
|
|
476
|
+
const response = {
|
|
477
|
+
status: 'error',
|
|
478
|
+
message: 'byClientUpdate failed',
|
|
479
|
+
};
|
|
480
|
+
ApiHelper.sendJson(req, res, response);
|
|
481
|
+
}
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async byClientRefreshCache(req: ServerRequest, res: ServerResponse) {
|
|
486
|
+
const jsonData = req.locals.json();
|
|
487
|
+
const data = await this.chkData(jsonData, req, res, true);
|
|
488
|
+
if (!data) return true;
|
|
489
|
+
|
|
490
|
+
await processRefreshCache(req);
|
|
491
|
+
const response = {
|
|
492
|
+
status: 'ok',
|
|
493
|
+
message: 'Cache refreshed successfully.',
|
|
494
|
+
};
|
|
495
|
+
ApiHelper.sendJson(req, res, response);
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
}
|