@underpostnet/underpost 2.7.94 → 2.8.1

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 (44) hide show
  1. package/.vscode/settings.json +5 -1
  2. package/bin/deploy.js +41 -16
  3. package/bin/index.js +4 -5
  4. package/bin/util.js +18 -0
  5. package/conf.js +8 -1
  6. package/docker-compose.yml +1 -1
  7. package/package.json +11 -8
  8. package/src/api/user/user.model.js +1 -1
  9. package/src/api/user/user.service.js +22 -21
  10. package/src/client/components/core/Account.js +144 -124
  11. package/src/client/components/core/Auth.js +94 -1
  12. package/src/client/components/core/Docs.js +2 -1
  13. package/src/client/components/core/JoyStick.js +8 -5
  14. package/src/client/components/core/LogIn.js +8 -1
  15. package/src/client/components/core/LogOut.js +3 -2
  16. package/src/client/components/core/Modal.js +14 -9
  17. package/src/client/components/core/Panel.js +17 -3
  18. package/src/client/components/core/PanelForm.js +8 -1
  19. package/src/client/components/core/SignUp.js +3 -3
  20. package/src/client/components/core/SocketIo.js +2 -0
  21. package/src/client/components/core/Translate.js +14 -0
  22. package/src/client/components/core/Validator.js +9 -2
  23. package/src/client/components/core/VanillaJs.js +4 -1
  24. package/src/client/components/default/LogInDefault.js +2 -23
  25. package/src/client/components/default/LogOutDefault.js +3 -5
  26. package/src/client/public/default/plantuml/client-conf.svg +1 -1
  27. package/src/client/public/default/plantuml/server-conf.svg +1 -1
  28. package/src/client/public/default/plantuml/server-schema.svg +1 -1
  29. package/src/client/public/default/plantuml/ssr-conf.svg +1 -1
  30. package/src/client/public/default/plantuml/ssr-schema.svg +1 -1
  31. package/src/client/services/core/core.service.js +2 -0
  32. package/src/client/services/user/user.service.js +9 -1
  33. package/src/client/ssr/body/CacheControl.js +2 -1
  34. package/src/client/ssr/body/DefaultSplashScreen.js +3 -3
  35. package/src/client/ssr/offline/Maintenance.js +63 -0
  36. package/src/client/sw/default.sw.js +31 -5
  37. package/src/index.js +8 -0
  38. package/src/server/client-build.js +27 -25
  39. package/src/server/client-icons.js +13 -3
  40. package/src/server/conf.js +5 -4
  41. package/src/server/logger.js +8 -6
  42. package/src/server/network.js +4 -0
  43. package/src/server/ssl.js +2 -2
  44. package/src/server/valkey.js +126 -0
@@ -0,0 +1,63 @@
1
+ const s = (el) => document.querySelector(el);
2
+
3
+ const append = (el, html) => s(el).insertAdjacentHTML('beforeend', html);
4
+
5
+ const getLang = () =>
6
+ (localStorage.getItem('lang') || navigator.language || navigator.userLanguage || s('html').lang)
7
+ .slice(0, 2)
8
+ .toLowerCase();
9
+
10
+ const main = () => {
11
+ const Translate = {
12
+ Data: {
13
+ ['server-maintenance']: {
14
+ en: "The server is under maintenance <br> we'll be back soon.",
15
+ es: 'El servidor está en mantenimiento <br> volveremos pronto.',
16
+ },
17
+ },
18
+ Render: function (id) {
19
+ return this.Data[id][getLang()] ? this.Data[id][getLang()] : this.Data[id]['en'];
20
+ },
21
+ };
22
+ const icon = html`<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 24 24">
23
+ <path
24
+ fill="none"
25
+ stroke="currentColor"
26
+ stroke-linecap="round"
27
+ stroke-linejoin="round"
28
+ stroke-width="2"
29
+ d="M3 7a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3zm9 13H6a3 3 0 0 1-3-3v-2a3 3 0 0 1 3-3h10.5m-.5 6a2 2 0 1 0 4 0a2 2 0 1 0-4 0m2-3.5V16m0 4v1.5m3.032-5.25l-1.299.75m-3.463 2l-1.3.75m0-3.5l1.3.75m3.463 2l1.3.75M7 8v.01M7 16v.01"
30
+ />
31
+ </svg>`;
32
+
33
+ append(
34
+ 'body',
35
+ html` <style>
36
+ body {
37
+ font-family: arial;
38
+ font-size: 20px;
39
+ background-color: #d8d8d8;
40
+ color: #333;
41
+ }
42
+ a {
43
+ color: black;
44
+ }
45
+ </style>
46
+
47
+ <div class="abs center" style="top: 45%">
48
+ ${icon}
49
+ <br />
50
+ <br />${Translate.Render('server-maintenance')}
51
+ </div>`,
52
+ );
53
+ };
54
+
55
+ SrrComponent = () => html`<script>
56
+ {
57
+ const s = ${s};
58
+ const append = ${append};
59
+ const getLang = ${getLang};
60
+ const main = ${main};
61
+ window.onload = main;
62
+ }
63
+ </script>`;
@@ -62,12 +62,38 @@ self.addEventListener('fetch', (event) => {
62
62
  if (!preCachedResponse) throw new Error(error.message);
63
63
  return preCachedResponse;
64
64
  } catch (error) {
65
- console.error('Error opening cache for pre cached page', event.request.url, error);
65
+ console.error('Error opening cache for pre cached page', {
66
+ url: event.request.url,
67
+ error,
68
+ onLine: navigator.onLine,
69
+ });
66
70
  try {
67
- const cache = await caches.open(CACHE_NAME);
68
- const preCachedResponse = await cache.match(`${PROXY_PATH === '/' ? '' : PROXY_PATH}/offline/index.html`);
69
- if (!preCachedResponse) throw new Error(error.message);
70
- return preCachedResponse;
71
+ if (!navigator.onLine) {
72
+ if (event.request.method.toUpperCase() === 'GET') {
73
+ const cache = await caches.open(CACHE_NAME);
74
+ const preCachedResponse = await cache.match(
75
+ `${PROXY_PATH === '/' ? '' : PROXY_PATH}/offline/index.html`,
76
+ );
77
+ if (!preCachedResponse) throw new Error(error.message);
78
+ return preCachedResponse;
79
+ }
80
+ const response = new Response(JSON.stringify({ status: 'error', message: 'offline test response' }));
81
+ // response.status = 200;
82
+ response.headers.set('Content-Type', 'application/json');
83
+ return response;
84
+ }
85
+ if (event.request.method.toUpperCase() === 'GET') {
86
+ const cache = await caches.open(CACHE_NAME);
87
+ const preCachedResponse = await cache.match(
88
+ `${PROXY_PATH === '/' ? '' : PROXY_PATH}/maintenance/index.html`,
89
+ );
90
+ if (!preCachedResponse) throw new Error(error.message);
91
+ return preCachedResponse;
92
+ }
93
+ const response = new Response(JSON.stringify({ status: 'error', message: 'server in maintenance' }));
94
+ // response.status = 200;
95
+ response.headers.set('Content-Type', 'application/json');
96
+ return response;
71
97
  } catch (error) {
72
98
  console.error('Error opening cache for offline page', event.request.url, error);
73
99
  const response = new Response(JSON.stringify({ status: 'error', message: error.message }));
package/src/index.js CHANGED
@@ -14,6 +14,14 @@ const logger = loggerFactory(import.meta);
14
14
  * @memberof Underpost
15
15
  */
16
16
  class Underpost {
17
+ /**
18
+ * Underpost engine version
19
+ * @static
20
+ * @type {String}
21
+ * @memberof Underpost
22
+ */
23
+ static version = 'v2.8.1';
24
+
17
25
  constructor() {}
18
26
 
19
27
  /**
@@ -465,10 +465,11 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }
465
465
  case 'DefaultSplashScreen':
466
466
  if (backgroundImage) {
467
467
  ssrHeadComponents += SrrComponent({
468
- base64BackgroundImage: `data:image/${backgroundImage.split('.').pop()};base64,${fs
469
- .readFileSync(backgroundImage)
470
- .toString('base64')}`,
468
+ backgroundImage: (path === '/' ? path : `${path}/`) + backgroundImage,
471
469
  });
470
+ // `data:image/${backgroundImage.split('.').pop()};base64,${fs
471
+ // .readFileSync()
472
+ // .toString('base64')}`,
472
473
  break;
473
474
  } else {
474
475
  ssrHeadComponents += SrrComponent({ metadata });
@@ -480,7 +481,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }
480
481
  bgColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
481
482
  });
482
483
  ssrHeadComponents += SrrComponent({
483
- base64BackgroundImage: `data:image/png;base64,${bufferBackgroundImage.toString('base64')}`,
484
+ backgroundImage: `data:image/png;base64,${bufferBackgroundImage.toString('base64')}`,
484
485
  });
485
486
  }
486
487
 
@@ -700,28 +701,7 @@ root file where the route starts, such as index.js, app.js, routes.js, etc ... *
700
701
 
701
702
  await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
702
703
  }
703
- if (!enableLiveRebuild && process.argv.includes('zip')) {
704
- logger.warn('build zip', rootClientPath);
705
-
706
- if (!fs.existsSync('./build')) fs.mkdirSync('./build');
707
-
708
- const zip = new AdmZip();
709
- const files = await fs.readdir(rootClientPath, { recursive: true });
710
-
711
- for (const relativePath of files) {
712
- const filePath = dir.resolve(`${rootClientPath}/${relativePath}`);
713
- if (!fs.lstatSync(filePath).isDirectory()) {
714
- const folder = dir.relative(`public/${host}${path}`, dir.dirname(filePath));
715
- zip.addLocalFile(filePath, folder);
716
- }
717
- }
718
-
719
- const buildId = `${host}-${path.replaceAll('/', '')}`;
720
704
 
721
- logger.warn('write zip', `./build/${buildId}.zip`);
722
-
723
- zip.writeZip(`./build/${buildId}.zip`);
724
- }
725
705
  if (client) {
726
706
  let PRE_CACHED_RESOURCES = [];
727
707
 
@@ -792,6 +772,28 @@ root file where the route starts, such as index.js, app.js, routes.js, etc ... *
792
772
  );
793
773
  }
794
774
  }
775
+ if (!enableLiveRebuild && process.argv.includes('zip')) {
776
+ logger.warn('build zip', rootClientPath);
777
+
778
+ if (!fs.existsSync('./build')) fs.mkdirSync('./build');
779
+
780
+ const zip = new AdmZip();
781
+ const files = await fs.readdir(rootClientPath, { recursive: true });
782
+
783
+ for (const relativePath of files) {
784
+ const filePath = dir.resolve(`${rootClientPath}/${relativePath}`);
785
+ if (!fs.lstatSync(filePath).isDirectory()) {
786
+ const folder = dir.relative(`public/${host}${path}`, dir.dirname(filePath));
787
+ zip.addLocalFile(filePath, folder);
788
+ }
789
+ }
790
+
791
+ const buildId = `${host}-${path.replaceAll('/', '')}`;
792
+
793
+ logger.warn('write zip', `./build/${buildId}.zip`);
794
+
795
+ zip.writeZip(`./build/${buildId}.zip`);
796
+ }
795
797
  }
796
798
  }
797
799
  };
@@ -3,8 +3,9 @@ import { favicons } from 'favicons';
3
3
  // import textToImage from 'text-to-image';
4
4
  import { loggerFactory } from './logger.js';
5
5
  import fs from 'fs-extra';
6
- import { png3x } from 'font-awesome-assets';
7
6
  import { getCapVariableName, s4 } from '../client/components/core/CommonJs.js';
7
+ import { FileFactory } from '../api/file/file.service.js';
8
+ import { svg, png, png3x } from 'font-awesome-assets';
8
9
 
9
10
  const logger = loggerFactory(import.meta);
10
11
 
@@ -140,7 +141,7 @@ const buildIcons = async ({
140
141
  for (const file of response.files)
141
142
  fs.writeFileSync(`./src/client/public/${publicClientId}/${file.name}`, file.contents, 'utf8');
142
143
 
143
- const ssrPath = `./src/client/ssr/components/head/Pwa${getCapVariableName(publicClientId)}.js`;
144
+ const ssrPath = `./src/client/ssr/head/Pwa${getCapVariableName(publicClientId)}.js`;
144
145
  if (!fs.existsSync(ssrPath))
145
146
  fs.writeFileSync(ssrPath, 'SrrComponent = () => html`' + response.html.join(`\n`) + '`;', 'utf8');
146
147
  } catch (error) {
@@ -148,4 +149,13 @@ const buildIcons = async ({
148
149
  }
149
150
  };
150
151
 
151
- export { buildIcons, buildTextImg, defaultBaseTextImgOptions, faBase64Png, getBufferPngText };
152
+ const getDefaultProfileImageId = async (File) => {
153
+ const faId = 'user';
154
+ const tmpFilePath = `./tmp/${faId}-${s4() + s4()}.svg`;
155
+ fs.writeFileSync(tmpFilePath, svg(faId, '#f5f5f5d1'), 'utf8');
156
+ const file = await new File(FileFactory.svg(fs.readFileSync(tmpFilePath), `${faId}.svg`)).save();
157
+ fs.removeSync(tmpFilePath);
158
+ return file._id;
159
+ };
160
+
161
+ export { buildIcons, buildTextImg, defaultBaseTextImgOptions, faBase64Png, getBufferPngText, getDefaultProfileImageId };
@@ -253,8 +253,8 @@ const buildClientSrc = async (
253
253
  }
254
254
 
255
255
  fs.writeFileSync(
256
- `./src/client/ssr/components/head/${toClientVariableName}Scripts.js`,
257
- formattedSrc(fs.readFileSync(`./src/client/ssr/components/head/${fromClientVariableName}Scripts.js`, 'utf8')),
256
+ `./src/client/ssr/head/${toClientVariableName}Scripts.js`,
257
+ formattedSrc(fs.readFileSync(`./src/client/ssr/head/${fromClientVariableName}Scripts.js`, 'utf8')),
258
258
  'utf8',
259
259
  );
260
260
 
@@ -473,7 +473,7 @@ const buildProxyRouter = () => {
473
473
  title: 'Site in maintenance',
474
474
  ssrPath: '/',
475
475
  ssrHeadComponents: '',
476
- ssrBodyComponents: (await ssrFactory(`./src/client/ssr/body/Maintenance.js`))(),
476
+ ssrBodyComponents: (await ssrFactory(`./src/client/ssr/offline/Maintenance.js`))(),
477
477
  });
478
478
  })();
479
479
 
@@ -583,7 +583,7 @@ const validateTemplatePath = (absolutePath = '') => {
583
583
  const confServer = DefaultConf.server[host][path];
584
584
  const confClient = DefaultConf.client[client];
585
585
  const confSsr = DefaultConf.ssr[ssr];
586
- const clients = Object.keys(confClient).concat(['core', 'test', 'default']);
586
+ const clients = Object.keys(confClient).concat(['core', 'test', 'default', 'user']);
587
587
 
588
588
  if (absolutePath.match('src/api') && !confServer.apis.find((p) => absolutePath.match(`src/api/${p}/`))) {
589
589
  return false;
@@ -935,6 +935,7 @@ const maintenanceMiddleware = (req, res, port, proxyRouter) => {
935
935
 
936
936
  const setUpProxyMaintenanceServer = ({ deployGroupId }) => {
937
937
  shellExec(`pm2 kill`);
938
+ shellExec(`node bin/deploy valkey-service`);
938
939
  const proxyDeployId = fs.readFileSync(`./engine-private/deploy/${deployGroupId}.proxy`, 'utf8').trim();
939
940
  shellExec(`node bin/deploy conf ${proxyDeployId} production`);
940
941
  shellExec(`node bin/deploy run ${proxyDeployId} maintenance`);
@@ -86,6 +86,7 @@ const format = (meta) =>
86
86
  * @memberof Logger
87
87
  */
88
88
  const setUpInfo = async (logger = new winston.Logger()) => {
89
+ logger.info('npm_package_version', process.env.npm_package_version);
89
90
  logger.info('argv', process.argv);
90
91
  logger.info('platform', process.platform);
91
92
  logger.info('env', process.env.NODE_ENV);
@@ -179,12 +180,13 @@ const loggerMiddleware = (meta = { url: '' }) => {
179
180
  };
180
181
 
181
182
  const underpostASCI = () => `
182
- ▗▖ ▗▖▗▖ ▗▖▗▄▄▄ ▗▄▄▄▖▗▄▄▖ ▄▄▄▄ ▄▄▄ ▄▄▄ ■
183
- ▐▌ ▐▌▐▛▚▖▐▌▐▌ █ ▐▌ ▐▌ ▐▌█ █ █ █ ▀▄▄▗▄▟▙▄▖
184
- ▐▌ ▐▌▐▌ ▝▜▌▐▌ █ ▐▛▀▀▘▐▛▀▚▖█▄▄▄▀ ▀▄▄▄▀ ▄▄▄▀ ▐▌
185
- ▝▚▄▞▘▐▌ ▐▌▐▙▄▄▀ ▐▙▄▄▖▐▌ ▐▌█ ▐▌
186
- ▀ ▐▌
187
-
183
+
184
+ ██╗░░░██╗███╗░░██╗██████╗░███████╗██████╗░██████╗░░█████╗░░██████╗████████╗
185
+ ██║░░░██║████╗░██║██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝
186
+ ██║░░░██║██╔██╗██║██║░░██║█████╗░░██████╔╝██████╔╝██║░░██║╚█████╗░░░░██║░░░
187
+ ██║░░░██║██║╚████║██║░░██║██╔══╝░░██╔══██╗██╔═══╝░██║░░██║░╚═══██╗░░░██║░░░
188
+ ╚██████╔╝██║░╚███║██████╔╝███████╗██║░░██║██║░░░░░╚█████╔╝██████╔╝░░░██║░░░
189
+ ░╚═════╝░╚═╝░░╚══╝╚═════╝░╚══════╝╚═╝░░╚═╝╚═╝░░░░░░╚════╝░╚═════╝░░░░╚═╝░░░
188
190
  `;
189
191
 
190
192
  export { loggerFactory, loggerMiddleware, setUpInfo, underpostASCI };
@@ -74,6 +74,10 @@ const saveRuntimeRouter = async () => {
74
74
  const host = process.env.DEFAULT_DEPLOY_HOST;
75
75
  const path = process.env.DEFAULT_DEPLOY_PATH;
76
76
  const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
77
+ if (!deployId || !host || !path) {
78
+ logger.warn('default deploy instance not found');
79
+ return;
80
+ }
77
81
  const confServer = JSON.parse(fs.readFileSync(confServerPath, 'utf8'));
78
82
  const { db } = confServer[host][path];
79
83
 
package/src/server/ssl.js CHANGED
@@ -50,8 +50,8 @@ const buildSSL = async (host) => {
50
50
 
51
51
  fs.writeFileSync(`./engine-private/ssl/${host}/_ca_bundle.crt`, ca, 'utf8');
52
52
  fs.writeFileSync(`./engine-private/ssl/${host}/_ca_full_bundle.crt`, caFull, 'utf8');
53
-
54
- fs.removeSync(`${sslPath}/${folderHost}`);
53
+ // TODO: no repeat folderHost match
54
+ // fs.removeSync(`${sslPath}/${folderHost}`);
55
55
  return true;
56
56
  }
57
57
  }
@@ -0,0 +1,126 @@
1
+ import Valkey from 'iovalkey';
2
+ import mongoose from 'mongoose';
3
+ import { hashPassword } from './auth.js';
4
+ import { loggerFactory } from './logger.js';
5
+
6
+ const logger = loggerFactory(import.meta);
7
+
8
+ let valkeyEnabled = true;
9
+
10
+ const disableValkeyErrorMessage = 'valkey is not enabled';
11
+
12
+ const isValkeyEnable = () => valkeyEnabled;
13
+
14
+ const selectDtoFactory = (payload, select) => {
15
+ const result = {};
16
+ for (const key of Object.keys(select)) {
17
+ if (select[key] === 1 && key in payload) result[key] = payload[key];
18
+ }
19
+ return result;
20
+ };
21
+
22
+ const valkeyClientFactory = async () => {
23
+ const valkey = new Valkey({
24
+ retryStrategy: (attempt) => {
25
+ if (attempt === 1) {
26
+ valkey.disconnect();
27
+ valkeyEnabled = false;
28
+ logger.warn('Valkey service not enabled', { valkeyEnabled });
29
+ return;
30
+ }
31
+ return 1000; // 1 second interval attempt
32
+ },
33
+ }); // Connect to 127.0.0.1:6379
34
+ // new Valkey(6380); // 127.0.0.1:6380
35
+ // new Valkey(6379, '192.168.1.1'); // 192.168.1.1:6379
36
+ // new Valkey('/tmp/redis.sock');
37
+ // new Valkey({
38
+ // port: 6379, // Valkey port
39
+ // host: '127.0.0.1', // Valkey host
40
+ // username: 'default', // needs Valkey >= 6
41
+ // password: 'my-top-secret',
42
+ // db: 0, // Defaults to 0
43
+ // });
44
+ return valkey;
45
+ };
46
+
47
+ const getValkeyObject = async (key = '') => {
48
+ if (!valkeyEnabled) {
49
+ logger.warn(disableValkeyErrorMessage + ' get', key);
50
+ return null;
51
+ }
52
+ const object = await valkey.get(key);
53
+ try {
54
+ return JSON.parse(object);
55
+ } catch (error) {
56
+ logger.error(error);
57
+ return object;
58
+ }
59
+ };
60
+
61
+ const setValkeyObject = async (key = '', payload = {}) => {
62
+ if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
63
+ return await valkey.set(key, JSON.stringify(payload));
64
+ };
65
+
66
+ const updateValkeyObject = async (key = '', payload = {}) => {
67
+ if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
68
+ const object = await getValkeyObject(key, valkey);
69
+ object.updatedAt = new Date().toISOString();
70
+ return await valkey.set(key, JSON.stringify({ ...object, ...payload }));
71
+ };
72
+
73
+ const valkeyObjectFactory = async (module = '', options = { host: 'localhost', object: {} }) => {
74
+ if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
75
+ const idoDate = new Date().toISOString();
76
+ options.object = options.object || {};
77
+ const { object } = options;
78
+ const _id = new mongoose.Types.ObjectId().toString();
79
+ object._id = _id;
80
+ object.createdAt = idoDate;
81
+ object.updatedAt = idoDate;
82
+ switch (module) {
83
+ case 'user': {
84
+ const role = 'guest';
85
+ object._id = `${role}${_id}`;
86
+ return {
87
+ ...object,
88
+ username: `${role}${_id.slice(-5)}`,
89
+ email: `${_id}@${options.host}`,
90
+ password: hashPassword(process.env.JWT_SECRET),
91
+ role,
92
+ failedLoginAttempts: 0,
93
+ phoneNumbers: [],
94
+ publicKey: [],
95
+ profileImageId: null,
96
+ emailConfirmed: false,
97
+ recoverTimeOut: null,
98
+ lastLoginDate: null,
99
+ };
100
+ }
101
+ default:
102
+ throw new Error(`module schema not found: ${module}`);
103
+ }
104
+ };
105
+
106
+ const ValkeyAPI = {
107
+ valkeyClientFactory,
108
+ selectDtoFactory,
109
+ getValkeyObject,
110
+ setValkeyObject,
111
+ valkeyObjectFactory,
112
+ updateValkeyObject,
113
+ };
114
+
115
+ const valkey = await ValkeyAPI.valkeyClientFactory();
116
+
117
+ export {
118
+ valkeyClientFactory,
119
+ selectDtoFactory,
120
+ getValkeyObject,
121
+ setValkeyObject,
122
+ valkeyObjectFactory,
123
+ updateValkeyObject,
124
+ isValkeyEnable,
125
+ ValkeyAPI,
126
+ };