lupine.api 1.1.50 → 1.1.51

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.
@@ -37,7 +37,7 @@ exports.cpIndexHtml = async (htmlFile, outputFile, appName, isMobile, defaultThe
37
37
  // if isMobile=true, then the last number is 1, or if isMobile=false, the last is 2
38
38
  const chgTime = Math.trunc(f1.mtime.getTime() / 10) * 10 + (isMobile ? 1 : 2);
39
39
 
40
- // when it's isMobile, need to update env and configs
40
+ // when it's isMobile, need to update env and configs => no configs as mobile app fetches it from api
41
41
  if (!f2 || f2.mtime.getTime() !== chgTime || isMobile) {
42
42
  const inHtml = await fs.readFile(htmlFile, 'utf-8');
43
43
  let outStr = inHtml.replace(/{hash}/gi, new Date().getTime().toString(36));
@@ -48,7 +48,7 @@ exports.cpIndexHtml = async (htmlFile, outputFile, appName, isMobile, defaultThe
48
48
  const metaIndexStart = inHtml.indexOf(metaTextStart);
49
49
  const metaIndexEnd = inHtml.indexOf(metaTextEnd);
50
50
 
51
- const webConfig = await readWebConfig(outdirData);
51
+ // const webConfig = await readWebConfig(outdirData);
52
52
  const webEnvData = webEnv.getWebEnv(appName);
53
53
 
54
54
  outStr =
@@ -56,9 +56,9 @@ exports.cpIndexHtml = async (htmlFile, outputFile, appName, isMobile, defaultThe
56
56
  '<script id="web-env" type="application/json">' +
57
57
  JSON.stringify(webEnvData) +
58
58
  '</script>\r\n' +
59
- '<script id="web-setting" type="application/json">' +
60
- JSON.stringify(webConfig) +
61
- '</script>' +
59
+ // '<script id="web-setting" type="application/json">' +
60
+ // JSON.stringify(webConfig) +
61
+ // '</script>' +
62
62
  outStr.substring(metaIndexEnd + metaTextEnd.length);
63
63
  // outStr = webEnv.replaceWebEnv(inHtml, appName, true);
64
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.api",
3
- "version": "1.1.50",
3
+ "version": "1.1.51",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -11,6 +11,7 @@ import { AdminConfig } from './admin-config';
11
11
  import { Logger } from '../lib';
12
12
  import { IApiBase, ServerRequest } from '../models';
13
13
  import { ApiRouter } from '../api';
14
+ import { readWebConfig } from './web-config-api';
14
15
 
15
16
  const logger = new Logger('admin-api');
16
17
 
@@ -27,6 +28,8 @@ export class AdminApi implements IApiBase {
27
28
  }
28
29
 
29
30
  protected mountDashboard() {
31
+ this.router.use('/web-config', readWebConfig);
32
+
30
33
  const adminDb = new AdminDb();
31
34
  this.router.use('/db', needDevAdminSession, adminDb.getRouter());
32
35
 
@@ -10,6 +10,11 @@ export const needDevAdminSession = async (req: ServerRequest, res: ServerRespons
10
10
  const devAdminSession = await adminApiHelper.getDevAdminFromCookie(req, res, true);
11
11
  if (!devAdminSession) {
12
12
  // return true to skip the rest of the middleware
13
+ const response = {
14
+ status: 'error',
15
+ message: langHelper.getLang('shared:permission_denied'),
16
+ };
17
+ ApiHelper.sendJson(req, res, response);
13
18
  return true;
14
19
  }
15
20
  return false;
@@ -32,7 +32,7 @@ export class AdminRelease implements IApiBase {
32
32
  protected mountDashboard() {
33
33
  // called by FE
34
34
  this.router.use('/check', needDevAdminSession, this.check.bind(this));
35
- this.router.use('/update', needDevAdminSession, this.update.bind(this));
35
+ this.router.use('/update', needDevAdminSession, this.callUpdate.bind(this));
36
36
  this.router.use('/view-log', needDevAdminSession, this.viewLog.bind(this));
37
37
  // called online or by clients
38
38
  this.router.use('/refresh-cache', needDevAdminSession, this.refreshCache.bind(this));
@@ -0,0 +1,19 @@
1
+ import { ServerResponse } from 'http';
2
+ import { Logger } from '../lib';
3
+ import { ServerRequest } from '../models';
4
+ import { langHelper } from '../lang';
5
+ import { ApiHelper, apiStorage } from '../api';
6
+
7
+ // only used by mobile app. For web, it's injected in the html by server-side render
8
+ const logger = new Logger('web-cfg-api');
9
+ export const readWebConfig = async (req: ServerRequest, res: ServerResponse) => {
10
+ logger.info('readWebConfig');
11
+
12
+ const response = {
13
+ status: 'ok',
14
+ result: await apiStorage.getWebAll(),
15
+ message: langHelper.getLang('shared:operation_success'),
16
+ };
17
+ ApiHelper.sendJson(req, res, response);
18
+ return true;
19
+ };
@@ -9,7 +9,6 @@ import { IToClientDelivery } from '../models/to-client-delivery-props';
9
9
  import { JsonObject } from '../models/json-object';
10
10
  import { getTemplateCache } from './api-cache';
11
11
  import { apiStorage } from './api-shared-storage';
12
- import { SimpleStorageDataProps } from '../models';
13
12
  import { RuntimeRequire } from '../lib/runtime-require';
14
13
 
15
14
  const logger = new Logger('StaticServer');
@@ -159,13 +158,13 @@ export const serverSideRenderPage = async (
159
158
  const _lupineJs = cachedHtml[nearRoot]._lupineJs;
160
159
  const currentCache = cachedHtml[nearRoot] as CachedHtmlProps;
161
160
  const webSetting = await apiStorage.getWebAll();
162
- const webSettingShortKey: SimpleStorageDataProps = {};
163
- for (let item of Object.keys(webSetting)) {
164
- const newItem = item.substring(4);
165
- webSettingShortKey[newItem] = webSetting[item];
166
- }
161
+ // const webSettingShortKey: SimpleStorageDataProps = {};
162
+ // for (let item of Object.keys(webSetting)) {
163
+ // const newItem = item.substring(4);
164
+ // webSettingShortKey[newItem] = webSetting[item];
165
+ // }
167
166
  // const webSetting = AppConfig.get(AppConfig.WEB_SETTINGS_KEY) || {};
168
- const clientDelivery = new ToClientDelivery(currentCache.webEnv, webSettingShortKey, req.locals.cookies());
167
+ const clientDelivery = new ToClientDelivery(currentCache.webEnv, webSetting, req.locals.cookies());
169
168
  const page = await _lupineJs.generatePage(props, clientDelivery);
170
169
  // console.log(`=========load lupin: `, content);
171
170
 
@@ -199,7 +198,7 @@ export const serverSideRenderPage = async (
199
198
  res.write(page.metaData);
200
199
  res.write(page.globalCss);
201
200
  res.write('<script id="web-env" type="application/json">' + JSON.stringify(currentCache.webEnv) + '</script>');
202
- res.write('<script id="web-setting" type="application/json">' + JSON.stringify(webSettingShortKey) + '</script>');
201
+ res.write('<script id="web-setting" type="application/json">' + JSON.stringify(webSetting) + '</script>');
203
202
  res.write(
204
203
  currentCache.content.substring(
205
204
  currentCache.metaIndexEnd + metaTextEnd.length,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * A persistent storage to store data in primary process and share to all workers
3
- *
3
+ *
4
4
  * You should use apiStorage in api module
5
5
  */
6
6
  import * as fs from 'fs/promises';
@@ -141,9 +141,9 @@ export class AppSharedStorage implements IAppSharedStorage {
141
141
  }
142
142
 
143
143
  // called from primary before exit, or from api to save changes
144
- async save(appName?: string) {
144
+ async save(appName?: string, exit?: boolean) {
145
145
  if (!cluster.isPrimary) {
146
- AppSharedStorageWorker.save(appName);
146
+ await AppSharedStorageWorker.save(appName);
147
147
  return;
148
148
  }
149
149
 
@@ -183,8 +183,16 @@ export class AppSharedStorage implements IAppSharedStorage {
183
183
  getApi(appName: string, key: string): Promise<string> {
184
184
  return this.get(appName, AppSharedStorageApiPrefix + key);
185
185
  }
186
- getWebAll(appName: string): Promise<SimpleStorageDataProps> {
187
- return this.getWithPrefix(appName, AppSharedStorageWebPrefix);
186
+ async getWebAll(appName: string): Promise<SimpleStorageDataProps> {
187
+ const webAll = await this.getWithPrefix(appName, AppSharedStorageWebPrefix);
188
+
189
+ const webSettingShortKey: SimpleStorageDataProps = {};
190
+ for (let item of Object.keys(webAll)) {
191
+ const newItem = item.substring(AppSharedStorageWebPrefix.length);
192
+ webSettingShortKey[newItem] = webAll[item];
193
+ }
194
+
195
+ return webSettingShortKey;
188
196
  }
189
197
  getWithPrefix(appName: string, prefixKey: string): Promise<SimpleStorageDataProps> {
190
198
  return new Promise((resolve, reject) => {
@@ -267,7 +275,7 @@ class AppSharedStorageWorker {
267
275
  workerId: cluster.worker?.id || 0,
268
276
  action: 'save',
269
277
  appName: appName || '',
270
- key: '',
278
+ key: 'save',
271
279
  };
272
280
  process.send!(obj);
273
281
  }
@@ -6,7 +6,7 @@ export const cleanupAndExit = async () => {
6
6
  // save shared storage first
7
7
  if (cluster.isPrimary) {
8
8
  // save only happens once
9
- await appStorage.save();
9
+ await appStorage.save('', true);
10
10
  }
11
11
  process.exit(0);
12
12
  };
package/src/lib/db/db.ts CHANGED
@@ -2,7 +2,12 @@ import { Logger } from '../logger';
2
2
  import { DbConfig } from '../../models/db-config';
3
3
 
4
4
  // Instead, Boolean values are stored as integers 0 (false) and 1 (true).
5
+ export type DbFieldExprssionProps = { exprssion: string; params?: (string | number)[] };
5
6
  export type DbFieldValue = { [key: string]: string | number };
7
+ export type DbFieldExpression = { [key: string]: string | number | DbFieldExprssionProps };
8
+ const isDbFieldExprssion = (value: any): value is DbFieldExprssionProps => {
9
+ return value && typeof value === 'object' && 'exprssion' in value;
10
+ };
6
11
 
7
12
  const logger = new Logger('db');
8
13
  export class Db {
@@ -252,12 +257,28 @@ export class Db {
252
257
  return await this.execute(sql, params);
253
258
  }
254
259
 
255
- public async updateObject(table: string, updateFieldValues: DbFieldValue, whereFieldValues: DbFieldValue) {
260
+ public async updateObject(table: string, updateFieldValues: DbFieldExpression, whereFieldValues: DbFieldValue) {
256
261
  table = this.replacePrefix(table);
257
262
  const fields = Object.keys(updateFieldValues);
258
- let sql = 'UPDATE ' + table + ' SET ' + fields.map((item) => `${item}=?`).join(',');
259
- const params = Object.values(updateFieldValues);
263
+ const setClauseParts: string[] = [];
264
+ const params: (string | number)[] = [];
265
+ // let sql = 'UPDATE ' + table + ' SET ' + fields.map((item) => `${item}=?`).join(',');
266
+ // const params = Object.values(updateFieldValues);
267
+ for (const field of fields) {
268
+ const value = updateFieldValues[field];
269
+
270
+ // expression
271
+ if (isDbFieldExprssion(value)) {
272
+ setClauseParts.push(`${field} = ${value.exprssion}`);
273
+ if (value.params) params.push(...value.params);
274
+ } else {
275
+ // static value
276
+ setClauseParts.push(`${field} = ?`);
277
+ params.push(value);
278
+ }
279
+ }
260
280
 
281
+ let sql = `UPDATE ${table} SET ${setClauseParts.join(', ')}`;
261
282
  if (whereFieldValues && Object.keys(whereFieldValues).length > 0) {
262
283
  sql +=
263
284
  ' WHERE ' +
@@ -25,7 +25,7 @@ export interface IAppSharedStorage {
25
25
  // this is primary, msg is from a client
26
26
  messageFromSubProcess(msgObject: any): void;
27
27
  load(appName: string, rootPath: string): Promise<void>;
28
- save(appName?: string): Promise<void>;
28
+ save(appName?: string, exit?: boolean): Promise<void>;
29
29
  get(appName: string, key: string): Promise<string>;
30
30
  getWeb(appName: string, key: string): Promise<string>;
31
31
  getApi(appName: string, key: string): Promise<string>;