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,318 @@
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
+
16
+ export class AdminResources implements IApiBase {
17
+ private logger = new Logger('resources-api');
18
+ protected router = new ApiRouter();
19
+
20
+ constructor() {
21
+ this.mountDashboard();
22
+ }
23
+
24
+ public getRouter(): ApiRouter {
25
+ return this.router;
26
+ }
27
+
28
+ protected mountDashboard() {
29
+ // called by FE
30
+ this.router.use('/data', this.data.bind(this));
31
+ this.router.use('/download', this.download.bind(this));
32
+ this.router.use('/upload', this.upload.bind(this));
33
+ this.router.use('/rename', this.rename.bind(this));
34
+ this.router.use('/newFolder', this.newFolder.bind(this));
35
+ this.router.use('/remove', this.remove.bind(this));
36
+ this.router.use('/removeDir', this.removeDir.bind(this));
37
+ }
38
+
39
+ async upload(req: ServerRequest, res: ServerResponse) {
40
+ const fPath = req.locals.query.get('p');
41
+ const fName = req.locals.query.get('n');
42
+ const data = req.locals.body;
43
+ if (!data || data.length < 1 || !fPath || !fName) {
44
+ const response = {
45
+ status: 'error',
46
+ message: langHelper.getLang('shared:wrong_data'),
47
+ };
48
+ ApiHelper.sendJson(req, res, response);
49
+ return true;
50
+ }
51
+
52
+ const appData = apiCache.getAppData();
53
+ const locPath = this.pathJoin(fPath, appData.webPath);
54
+ if (!(await FsUtils.pathExist(locPath))) {
55
+ const response = {
56
+ status: 'error',
57
+ message: langHelper.getLang('shared:wrong_data'),
58
+ };
59
+ ApiHelper.sendJson(req, res, response);
60
+ return true;
61
+ }
62
+
63
+ const key = req.locals.query.get('key') as string;
64
+ const chunkNumberStr = req.locals.query.get('chunkNumber') as string;
65
+ const chunkNumber = parseInt(chunkNumberStr);
66
+ const totalChunks = parseInt(req.locals.query.get('totalChunks') as string);
67
+ const decryptedKey = key && adminHelper.decryptJson(key.replace(/ /g, '+'));
68
+ const keyNG =
69
+ !chunkNumberStr ||
70
+ !totalChunks ||
71
+ (chunkNumber !== 0 && (!decryptedKey || decryptedKey.ind !== chunkNumber || decryptedKey.cnt !== totalChunks));
72
+ if (keyNG) {
73
+ const response = {
74
+ status: 'error',
75
+ message: langHelper.getLang('shared:permission_denied'),
76
+ };
77
+ ApiHelper.sendJson(req, res, response);
78
+ return true;
79
+ }
80
+ const MAX_FILE_SIZE = 1024 * 1024 * 800; // MB
81
+ if (totalChunks * data.length > MAX_FILE_SIZE) {
82
+ const response = {
83
+ status: 'error',
84
+ message: langHelper.getLang('shared:file_too_large'),
85
+ };
86
+ ApiHelper.sendJson(req, res, response);
87
+ return true;
88
+ }
89
+
90
+ const filename = path.join(fPath, fName);
91
+ // write data to a file
92
+ if (chunkNumber === 0) {
93
+ await fs.writeFile(filename, data, 'binary');
94
+ } else {
95
+ await fs.appendFile(filename, data, 'binary');
96
+ }
97
+
98
+ const response = {
99
+ status: 'ok',
100
+ chunkNumber,
101
+ totalChunks,
102
+ message: langHelper.getLang('shared:file_part_updated'),
103
+ key: adminHelper.encryptJson({ ind: chunkNumber + 1, cnt: totalChunks, t: new Date().getTime() }),
104
+ };
105
+ ApiHelper.sendJson(req, res, response);
106
+ return true;
107
+ }
108
+
109
+ private chkData(data: any, req: ServerRequest, res: ServerResponse, chkCredential: boolean) {
110
+ if (!data || Array.isArray(data) || typeof data !== 'object') {
111
+ this.logger.error(`chkData, missing parameters`, data);
112
+ const response = {
113
+ status: 'error',
114
+ message: 'Wrong data [missing parameters].', //langHelper.getLang('shared:wrong_data'),
115
+ };
116
+ ApiHelper.sendJson(req, res, response);
117
+ return false;
118
+ }
119
+ return data;
120
+ }
121
+
122
+ private async getFolders(locPath: string) {
123
+ const results: any[] = [];
124
+ const folders = await FsUtils.getDirAndFiles(locPath);
125
+
126
+ results.push({
127
+ fullPath: locPath,
128
+ });
129
+ for (let i = 0; i < folders.length; i++) {
130
+ const oneFolder = folders[i];
131
+ const fileInfo = await FsUtils.fileInfo(path.join(locPath, oneFolder));
132
+ if (fileInfo?.isFile) {
133
+ results.push({
134
+ name: oneFolder,
135
+ time: new Date(fileInfo!.mtime).toLocaleString(),
136
+ size: fileInfo?.size,
137
+ });
138
+ } else {
139
+ const subFolders = await FsUtils.getDirAndFiles(path.join(locPath, oneFolder));
140
+ const subFoldersWithTime = [];
141
+ for (let j = 0; j < subFolders.length; j++) {
142
+ const subFile = subFolders[j];
143
+ const fileInfo2 = await FsUtils.fileInfo(path.join(locPath, oneFolder, subFile));
144
+
145
+ if (fileInfo2?.isFile) {
146
+ subFoldersWithTime.push({
147
+ name: subFile,
148
+ time: new Date(fileInfo2.mtime).toLocaleString(),
149
+ size: fileInfo2?.size,
150
+ });
151
+ } else {
152
+ subFoldersWithTime.push({
153
+ name: subFile,
154
+ time: fileInfo2?.mtime && new Date(fileInfo2.mtime).toLocaleString(),
155
+ });
156
+ }
157
+ }
158
+ results.push({
159
+ name: oneFolder,
160
+ time: fileInfo?.mtime && new Date(fileInfo.mtime).toLocaleString(),
161
+ items: subFoldersWithTime,
162
+ });
163
+ }
164
+ }
165
+ return results;
166
+ }
167
+
168
+ private pathJoin(folder: string, apiPath: string) {
169
+ return folder !== '/' && folder !== '' && (folder.startsWith('/') || folder.startsWith('\\') || folder[1] === ':')
170
+ ? path.join(folder)
171
+ : path.join(apiPath, folder);
172
+ }
173
+
174
+ async data(req: ServerRequest, res: ServerResponse) {
175
+ const data = this.chkData(req.locals.json(), req, res, false);
176
+ if (!data) return true;
177
+ let results: any[] = [];
178
+ if (typeof data.folder === 'string') {
179
+ const appData = apiCache.getAppData();
180
+ const locPath = this.pathJoin(data.folder, appData.webPath);
181
+ results = await this.getFolders(locPath);
182
+ }
183
+ const response = {
184
+ status: 'ok',
185
+ message: 'OK',
186
+ results,
187
+ };
188
+ ApiHelper.sendJson(req, res, response);
189
+ return true;
190
+ }
191
+
192
+ // called by clients
193
+ async download(req: ServerRequest, res: ServerResponse) {
194
+ const data = this.chkData(req.locals.json(), req, res, true);
195
+ if (!data) return true;
196
+
197
+ if (typeof data.resource === 'string') {
198
+ const resPath = path.join(data.resource);
199
+ if (await FsUtils.pathExist(resPath)) {
200
+ ApiHelper.sendFile(req, res, resPath);
201
+ return true;
202
+ }
203
+ }
204
+ const response = {
205
+ status: 'error',
206
+ message: 'Resource not found',
207
+ };
208
+ ApiHelper.sendJson(req, res, response);
209
+ return true;
210
+ }
211
+
212
+ async rename(req: ServerRequest, res: ServerResponse) {
213
+ const data = this.chkData(req.locals.json(), req, res, true);
214
+ if (!data) return true;
215
+
216
+ if (typeof data.resource === 'string' && data.oldName && data.newName) {
217
+ const resPath = path.join(data.resource);
218
+ const basePath = path.dirname(resPath);
219
+ const oldPath = path.join(basePath, data.oldName);
220
+ if (await FsUtils.pathExist(oldPath)) {
221
+ const newPath = path.join(basePath, data.newName);
222
+ await FsUtils.rename(oldPath, newPath);
223
+ if (await FsUtils.pathExist(newPath)) {
224
+ const response = {
225
+ status: 'ok',
226
+ message: 'Resource renamed to ' + newPath,
227
+ };
228
+ ApiHelper.sendJson(req, res, response);
229
+ } else {
230
+ const response = {
231
+ status: 'error',
232
+ message: 'Resource not renamed',
233
+ };
234
+ ApiHelper.sendJson(req, res, response);
235
+ }
236
+ return true;
237
+ }
238
+ }
239
+ const response = {
240
+ status: 'error',
241
+ message: 'Resource not found',
242
+ };
243
+ ApiHelper.sendJson(req, res, response);
244
+ return true;
245
+ }
246
+
247
+ async remove(req: ServerRequest, res: ServerResponse) {
248
+ const data = this.chkData(req.locals.json(), req, res, true);
249
+ if (!data) return true;
250
+
251
+ if (typeof data.resource === 'string') {
252
+ const resPath = path.join(data.resource);
253
+ if (await FsUtils.pathExist(resPath)) {
254
+ await FsUtils.unlinkFile(resPath);
255
+ const response = {
256
+ status: 'ok',
257
+ message: 'Resource removed',
258
+ };
259
+ ApiHelper.sendJson(req, res, response);
260
+ return true;
261
+ }
262
+ }
263
+ const response = {
264
+ status: 'error',
265
+ message: 'Resource not found',
266
+ };
267
+ ApiHelper.sendJson(req, res, response);
268
+ return true;
269
+ }
270
+
271
+ async removeDir(req: ServerRequest, res: ServerResponse) {
272
+ const data = this.chkData(req.locals.json(), req, res, true);
273
+ if (!data) return true;
274
+
275
+ if (typeof data.resource === 'string') {
276
+ const resPath = path.join(data.resource);
277
+ if (await FsUtils.pathExist(resPath)) {
278
+ await FsUtils.unlinkFolderEmpty(resPath);
279
+ const response = {
280
+ status: 'ok',
281
+ message: 'Directory removed',
282
+ };
283
+ ApiHelper.sendJson(req, res, response);
284
+ return true;
285
+ }
286
+ }
287
+ const response = {
288
+ status: 'error',
289
+ message: 'Directory not found or has files',
290
+ };
291
+ ApiHelper.sendJson(req, res, response);
292
+ return true;
293
+ }
294
+
295
+ async newFolder(req: ServerRequest, res: ServerResponse) {
296
+ const data = this.chkData(req.locals.json(), req, res, true);
297
+ if (!data) return true;
298
+
299
+ if (typeof data.resource === 'string' && data.newName) {
300
+ const resPath = path.join(data.resource, data.newName);
301
+ if (!await FsUtils.pathExist(resPath)) {
302
+ await FsUtils.mkdir(resPath);
303
+ const response = {
304
+ status: 'ok',
305
+ message: 'Folder created',
306
+ };
307
+ ApiHelper.sendJson(req, res, response);
308
+ return true;
309
+ }
310
+ }
311
+ const response = {
312
+ status: 'error',
313
+ message: 'Folder not created',
314
+ };
315
+ ApiHelper.sendJson(req, res, response);
316
+ return true;
317
+ }
318
+ }
@@ -0,0 +1,79 @@
1
+ import { apiStorage } from '../api';
2
+ import { CryptoUtils, Logger } from '../lib';
3
+ import { adminHelper } from './admin-helper';
4
+
5
+ export type TokenProps = {
6
+ token: string;
7
+ description: string;
8
+ timestamp?: number;
9
+ };
10
+ export class AdminTokenHelper {
11
+ private static instance: AdminTokenHelper;
12
+ private logger = new Logger('admin-token-api');
13
+
14
+ private constructor() {}
15
+
16
+ public static getInstance(): AdminTokenHelper {
17
+ if (!AdminTokenHelper.instance) {
18
+ AdminTokenHelper.instance = new AdminTokenHelper();
19
+ }
20
+ return AdminTokenHelper.instance;
21
+ }
22
+
23
+ async list(search?: string): Promise<TokenProps[]> {
24
+ const tokens = await apiStorage.getApi('access-tokens');
25
+ const tokenJson = JSON.parse(tokens || '[]');
26
+
27
+ const searchTexts = (search || '').trim().split(' ').filter(item => !!item);
28
+ const results = tokenJson.filter((item: TokenProps) =>
29
+ searchTexts.every((text) => item.description.toLowerCase().includes(text.toLowerCase()))
30
+ );
31
+
32
+ return results;
33
+ }
34
+ async add(tokenData: TokenProps) {
35
+ const tokens = await apiStorage.getApi('access-tokens');
36
+ const tokensJson = JSON.parse(tokens || '[]');
37
+ tokenData.timestamp = new Date().getTime();
38
+ tokensJson.push(tokenData);
39
+ apiStorage.setApi('access-tokens', JSON.stringify(tokensJson));
40
+ await apiStorage.save();
41
+ }
42
+
43
+ async update(tokenData: TokenProps) {
44
+ const tokens = await apiStorage.getApi('access-tokens');
45
+ const tokensJson = JSON.parse(tokens || '{}');
46
+ const idx = tokensJson.findIndex((item: TokenProps) => item.token === tokenData.token);
47
+ if (idx !== -1) {
48
+ tokensJson[idx].description = tokenData.description;
49
+ }
50
+ apiStorage.setApi('access-tokens', JSON.stringify(tokensJson));
51
+ apiStorage.save();
52
+ }
53
+
54
+ async remove(token: string) {
55
+ const tokens = await apiStorage.getApi('access-tokens');
56
+ const tokensJson = JSON.parse(tokens || '[]');
57
+ const idx = tokensJson.findIndex((item: TokenProps) => item.token === token);
58
+ if (idx !== -1) {
59
+ tokensJson.splice(idx, 1);
60
+ }
61
+ apiStorage.setApi('access-tokens', JSON.stringify(tokensJson));
62
+ apiStorage.save();
63
+ }
64
+
65
+ generate() {
66
+ const salt = 'Lupine:' + CryptoUtils.uuid() + ':' + new Date().getTime().toString();
67
+ return adminHelper.encryptJson(salt) as string;
68
+ }
69
+
70
+ async validateToken(token: string) {
71
+ const tokens = await apiStorage.getApi('access-tokens');
72
+ const tokensJson = JSON.parse(tokens || '[]');
73
+ const idx = tokensJson.findIndex((item: TokenProps) => item.token === token);
74
+ return idx !== -1;
75
+ }
76
+ }
77
+
78
+ // add comment for tree shaking
79
+ export const adminTokenHelper = /* @__PURE__ */ AdminTokenHelper.getInstance();
@@ -0,0 +1,90 @@
1
+ import { ServerResponse } from 'http';
2
+ import { IApiBase, Logger, ServerRequest, ApiRouter, ApiHelper, apiStorage } from 'lupine.api';
3
+ import { adminTokenHelper } from './admin-token-helper';
4
+
5
+ export class AdminTokens implements IApiBase {
6
+ logger = new Logger('admin-page');
7
+ protected router = new ApiRouter();
8
+ adminUser: any;
9
+
10
+ constructor() {
11
+ this.mountDashboard();
12
+ }
13
+
14
+ public getRouter(): ApiRouter {
15
+ return this.router;
16
+ }
17
+
18
+ protected mountDashboard() {
19
+ this.router.use('/list', this.list.bind(this));
20
+ this.router.use('/add', this.add.bind(this));
21
+ this.router.use('/generate', this.generateToken.bind(this));
22
+ this.router.use('/update', this.update.bind(this));
23
+ this.router.use('/remove', this.remove.bind(this));
24
+ }
25
+
26
+ async list(req: ServerRequest, res: ServerResponse) {
27
+ const pageLimit = await apiStorage.getWeb('pageLimit') || '15';
28
+ const data = req.locals.json() as any;
29
+ const search = data['q'];
30
+ const list = await adminTokenHelper.list(search);
31
+ const response = {
32
+ status: 'ok',
33
+ message: 'Token List.',
34
+ result: list,
35
+ pageLimit,
36
+ };
37
+ ApiHelper.sendJson(req, res, response);
38
+ return true;
39
+ }
40
+
41
+ async add(req: ServerRequest, res: ServerResponse) {
42
+ const data = req.locals.json() as any;
43
+ const token = data['token'];
44
+ const description = data['description'];
45
+ token && (await adminTokenHelper.add({token, description}));
46
+ const response = {
47
+ status: 'ok',
48
+ message: 'Added token.',
49
+ };
50
+ ApiHelper.sendJson(req, res, response);
51
+ return true;
52
+ }
53
+
54
+ async generateToken(req: ServerRequest, res: ServerResponse) {
55
+ const newToken = adminTokenHelper.generate();
56
+ const response = {
57
+ status: 'ok',
58
+ message: 'Generated new token.',
59
+ result: newToken,
60
+ };
61
+ ApiHelper.sendJson(req, res, response);
62
+ return true;
63
+ }
64
+
65
+ async update(req: ServerRequest, res: ServerResponse) {
66
+ const data = req.locals.json() as any;
67
+ const token = data['token'];
68
+ const description = data['description'];
69
+ token && (await adminTokenHelper.update({token, description}));
70
+ const response = {
71
+ status: 'ok',
72
+ message: 'Updated token.',
73
+ };
74
+ ApiHelper.sendJson(req, res, response);
75
+ return true;
76
+ }
77
+
78
+ async remove(req: ServerRequest, res: ServerResponse) {
79
+ const data = req.locals.json() as any;
80
+ const token = data['token'];
81
+ token && (await adminTokenHelper.remove(token));
82
+
83
+ const response = {
84
+ status: 'ok',
85
+ message: 'Removed token.',
86
+ };
87
+ ApiHelper.sendJson(req, res, response);
88
+ return true;
89
+ }
90
+ }
@@ -0,0 +1,2 @@
1
+ export * from './admin-api';
2
+ export * from './admin-helper';
@@ -0,0 +1,103 @@
1
+ /**
2
+ * A simple settings/config class for storing key/value pairs in memory
3
+ */
4
+ import { Db } from '../lib';
5
+ import { asyncLocalStorage } from './async-storage';
6
+ import { AsyncStorageProps, HostToPathProps } from '../models';
7
+
8
+ // This data doesn't need to be shared to other workers
9
+ export const getTemplateCache = () => {
10
+ let cachedHtml = apiCache.get(apiCache.KEYS.TEMPLATE);
11
+ if (!cachedHtml) {
12
+ cachedHtml = {};
13
+ apiCache.set(apiCache.KEYS.TEMPLATE, cachedHtml);
14
+ }
15
+ return cachedHtml;
16
+ };
17
+
18
+ enum ApiCacheKeys {
19
+ TEMPLATE = 'TEMPLATE',
20
+ DB = 'DB',
21
+ APP_DATA = 'APP_DATA',
22
+ // APP_CACHE = 'APP_CACHE',
23
+ }
24
+
25
+ // For cross clusters sharing, use AppSharedStorage
26
+ // ApiCache doesn't share cross clusters
27
+ // Since apis and app are independent, so ApiCache in apis and app are different instances.
28
+ // ApiCache is not shared cross app and apis, so `set` is only supposed to be called when app starts, or
29
+ // `set/get` is only used inside one api only
30
+ export class ApiCache {
31
+ private static instance: ApiCache;
32
+ KEYS = ApiCacheKeys;
33
+
34
+ private cacheMap: { [key: string]: any } = {};
35
+
36
+ private constructor() {}
37
+
38
+ public static getInstance(): ApiCache {
39
+ if (!ApiCache.instance) {
40
+ ApiCache.instance = new ApiCache();
41
+ }
42
+ return ApiCache.instance;
43
+ }
44
+
45
+ clear() {
46
+ Object.keys(this.cacheMap).forEach((key) => {
47
+ delete this.cacheMap[key];
48
+ });
49
+ }
50
+
51
+ get(key: string) {
52
+ return this.cacheMap[key];
53
+ }
54
+
55
+ set(key: string, value: any) {
56
+ if (typeof value === 'undefined') {
57
+ delete this.cacheMap[key];
58
+ } else {
59
+ this.cacheMap[key] = value;
60
+ }
61
+ }
62
+
63
+ // get scope variables inside of asyncLocalStorage.run
64
+ getAsyncStore(): AsyncStorageProps {
65
+ const store = asyncLocalStorage.getStore();
66
+ if (!store) {
67
+ throw new Error('This function should be called inside of asyncLocalStorage.run');
68
+ }
69
+
70
+ return store;
71
+ }
72
+
73
+ // get scope variables inside of asyncLocalStorage.run
74
+ getAppData() {
75
+ return this.get(ApiCacheKeys.APP_DATA) as HostToPathProps;
76
+ }
77
+
78
+ // get scope variables inside of asyncLocalStorage.run
79
+ getAppName() {
80
+ return this.getAsyncStore().appName;
81
+ }
82
+
83
+ // get scope variables inside of asyncLocalStorage.run
84
+ getDb() {
85
+ return this.get(ApiCacheKeys.DB) as Db;
86
+ }
87
+
88
+ // get scope variables inside of asyncLocalStorage.run
89
+ getUuid() {
90
+ return this.getAsyncStore().uuid;
91
+ }
92
+
93
+ // get scope variables inside of asyncLocalStorage.run
94
+ getLang() {
95
+ return this.getAsyncStore().lang;
96
+ }
97
+
98
+ clearTemplateCache() {
99
+ this.set(ApiCacheKeys.TEMPLATE, undefined);
100
+ }
101
+ }
102
+
103
+ export const apiCache = /* @__PURE__ */ ApiCache.getInstance();
@@ -0,0 +1,44 @@
1
+ import { ServerResponse } from 'http';
2
+ import { ServerRequest } from '../models/locals-props';
3
+ import { JsonObject } from '../models/json-object';
4
+ import fs from 'fs';
5
+
6
+ export class ApiHelper {
7
+ static sendJson(
8
+ req: ServerRequest,
9
+ res: ServerResponse,
10
+ json: JsonObject,
11
+ statusCode = 200,
12
+ headers?: { [key: string]: string }
13
+ ) {
14
+ res.writeHead(statusCode, Object.assign({ 'Content-Type': 'application/json' }, headers));
15
+ res.write(JSON.stringify(json));
16
+ res.end();
17
+ return true;
18
+ }
19
+
20
+ static sendHtml(
21
+ req: ServerRequest,
22
+ res: ServerResponse,
23
+ html: string,
24
+ statusCode = 200,
25
+ headers?: { [key: string]: string }
26
+ ) {
27
+ res.writeHead(statusCode, Object.assign({ 'Content-Type': 'text/html' }, headers));
28
+ res.write(html);
29
+ res.end();
30
+ return true;
31
+ }
32
+
33
+ static sendFile(
34
+ req: ServerRequest,
35
+ res: ServerResponse,
36
+ filepath: string,
37
+ statusCode = 200,
38
+ headers?: { [key: string]: string }
39
+ ) {
40
+ res.writeHead(statusCode, Object.assign({ 'Content-Type': 'application/octet-stream' }, headers));
41
+ fs.createReadStream(filepath).pipe(res);
42
+ return true;
43
+ }
44
+ }
@@ -0,0 +1,60 @@
1
+ import { DbConfig, DbHelper, HostToPathProps, IApiBase, loadEnv, ServerRequest } from 'lupine.api';
2
+ import { ServerResponse } from 'http';
3
+ import path from 'path';
4
+ import { apiCache, asyncLocalStorage, bindRenderPageFunctions } from '.';
5
+ import { AppCacheGlobal, AppCacheKeys, AsyncStorageProps, IApiModule, IAppCache, IAppSharedStorage, setAppCache } from '../models';
6
+ import { apiStorage } from './api-shared-storage';
7
+
8
+ export class ApiModule implements IApiModule {
9
+ rootApi: IApiBase;
10
+ constructor(api: IApiBase) {
11
+ this.rootApi = api;
12
+ }
13
+
14
+ public async processApi(store: AsyncStorageProps, url: string, req: ServerRequest, res: ServerResponse) {
15
+ let result = false;
16
+ await asyncLocalStorage.run(store, async () => {
17
+ if (await this.rootApi.getRouter().findRoute(url, req, res, true)) {
18
+ result = true;
19
+ return true;
20
+ }
21
+ });
22
+ return result;
23
+ }
24
+
25
+ // appCache is from app-loader (parent scope), not the same in current scope
26
+ public async initApi(appConfig: HostToPathProps, appCacheFromApp: IAppCache, appStorageFromApp: IAppSharedStorage) {
27
+
28
+ // const evnFile = appCacheFromApp.get(AppCacheGlobal, AppCacheKeys.APP_ENV_FILE);
29
+ // if (evnFile) {
30
+ // await loadEnv(evnFile);
31
+ // }
32
+
33
+ // set app's instances to api
34
+ setAppCache(appCacheFromApp);
35
+ // setAppStorage(appStorageFromApp);
36
+ apiStorage.setAppSharedStorage(appStorageFromApp);
37
+ // set RENDER_PAGE_FUNCTIONS to API module
38
+ bindRenderPageFunctions(appCacheFromApp.get(AppCacheGlobal, AppCacheKeys.RENDER_PAGE_FUNCTIONS));
39
+
40
+ console.log(`appConfig: `, appConfig);
41
+ apiCache.set(apiCache.KEYS.APP_DATA, appConfig);
42
+ // apiCache.set(apiCache.KEYS.APP_CACHE, appCache);
43
+
44
+ // await this.initConfig(appConfig);
45
+ apiCache.clearTemplateCache();
46
+
47
+ appConfig.dbConfig.filename = path.join(appConfig.dataPath, 'sqlite3.db');
48
+ await this.initDb(appConfig.dbConfig);
49
+ }
50
+
51
+ private async initDb(config: DbConfig) {
52
+ const db = await DbHelper.createInstance(config);
53
+ apiCache.set(apiCache.KEYS.DB, db);
54
+ return db;
55
+ }
56
+
57
+ // private async initConfig(appConfig: HostToPathProps) {
58
+ // await AppConfig.load(appConfig.dataPath);
59
+ // }
60
+ }