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,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
+ }