underpost 3.2.8 → 3.2.10

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 (92) hide show
  1. package/.github/workflows/npmpkg.ci.yml +1 -0
  2. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  3. package/.github/workflows/release.cd.yml +1 -0
  4. package/.vscode/settings.json +10 -5
  5. package/CHANGELOG.md +223 -2
  6. package/CLI-HELP.md +36 -7
  7. package/README.md +38 -9
  8. package/bin/build.js +27 -11
  9. package/bin/deploy.js +20 -21
  10. package/bin/file.js +32 -13
  11. package/bin/index.js +2 -1
  12. package/bin/vs.js +1 -1
  13. package/bump.config.js +26 -0
  14. package/conf.js +20 -4
  15. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  16. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
  17. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  18. package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
  19. package/manifests/kind-config-dev.yaml +8 -0
  20. package/manifests/mongodb/pv-pvc.yaml +44 -8
  21. package/manifests/mongodb/statefulset.yaml +55 -68
  22. package/package.json +40 -25
  23. package/scripts/k3s-node-setup.sh +30 -11
  24. package/scripts/nat-iptables.sh +103 -18
  25. package/src/api/core/core.router.js +19 -14
  26. package/src/api/core/core.service.js +5 -5
  27. package/src/api/default/default.router.js +22 -18
  28. package/src/api/default/default.service.js +5 -5
  29. package/src/api/document/document.router.js +28 -23
  30. package/src/api/document/document.service.js +100 -23
  31. package/src/api/file/file.router.js +19 -13
  32. package/src/api/file/file.service.js +9 -7
  33. package/src/api/test/test.router.js +17 -12
  34. package/src/api/types.js +24 -0
  35. package/src/api/user/guest.service.js +5 -4
  36. package/src/api/user/user.router.js +297 -288
  37. package/src/api/user/user.service.js +100 -35
  38. package/src/cli/baremetal.js +20 -11
  39. package/src/cli/cluster.js +243 -55
  40. package/src/cli/db.js +106 -62
  41. package/src/cli/deploy.js +297 -154
  42. package/src/cli/fs.js +19 -3
  43. package/src/cli/index.js +37 -9
  44. package/src/cli/ipfs.js +4 -6
  45. package/src/cli/kubectl.js +4 -1
  46. package/src/cli/lxd.js +217 -135
  47. package/src/cli/release.js +289 -131
  48. package/src/cli/repository.js +91 -34
  49. package/src/cli/run.js +297 -56
  50. package/src/cli/test.js +9 -3
  51. package/src/client/Default.index.js +9 -3
  52. package/src/client/components/core/Auth.js +19 -5
  53. package/src/client/components/core/Docs.js +6 -34
  54. package/src/client/components/core/FileExplorer.js +6 -6
  55. package/src/client/components/core/Modal.js +65 -2
  56. package/src/client/components/core/PanelForm.js +56 -52
  57. package/src/client/components/core/Recover.js +4 -4
  58. package/src/client/components/core/Worker.js +170 -350
  59. package/src/client/services/default/default.management.js +20 -25
  60. package/src/client/services/user/guest.service.js +10 -3
  61. package/src/client/sw/core.sw.js +174 -112
  62. package/src/db/DataBaseProvider.js +120 -20
  63. package/src/db/mongo/MongoBootstrap.js +587 -0
  64. package/src/db/mongo/MongooseDB.js +126 -22
  65. package/src/index.js +1 -1
  66. package/src/runtime/express/Express.js +2 -2
  67. package/src/runtime/wp/Wp.js +8 -5
  68. package/src/server/auth.js +2 -2
  69. package/src/server/client-build-docs.js +1 -1
  70. package/src/server/client-build.js +94 -129
  71. package/src/server/conf.js +20 -65
  72. package/src/server/data-query.js +32 -20
  73. package/src/server/dns.js +22 -0
  74. package/src/server/process.js +180 -19
  75. package/src/server/runtime.js +1 -1
  76. package/src/server/start.js +26 -7
  77. package/src/server/valkey.js +9 -2
  78. package/src/ws/IoInterface.js +16 -16
  79. package/src/ws/core/channels/core.ws.chat.js +11 -11
  80. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  81. package/src/ws/core/channels/core.ws.stream.js +19 -19
  82. package/src/ws/core/core.ws.connection.js +8 -8
  83. package/src/ws/core/core.ws.server.js +6 -5
  84. package/src/ws/default/channels/default.ws.main.js +10 -10
  85. package/src/ws/default/default.ws.connection.js +4 -4
  86. package/src/ws/default/default.ws.server.js +4 -3
  87. package/typedoc.json +10 -1
  88. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  89. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  90. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  91. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  92. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -1,3 +1,12 @@
1
+ /**
2
+ * User service module for handling user account operations.
3
+ * Provides REST API handlers for authentication, registration, profile management,
4
+ * email verification, password recovery, and guest user lifecycle management.
5
+ *
6
+ * @module src/api/user/user.service.js
7
+ * @namespace UserService
8
+ */
9
+
1
10
  import { loggerFactory } from '../../server/logger.js';
2
11
  import { DataQuery } from '../../server/data-query.js';
3
12
  import {
@@ -15,21 +24,40 @@ import { MailerProvider } from '../../mailer/MailerProvider.js';
15
24
  import { CoreWsEmitter } from '../../ws/core/core.ws.emit.js';
16
25
  import { CoreWsMailerChannel } from '../../ws/core/channels/core.ws.mailer.js';
17
26
  import validator from 'validator';
18
- import { DataBaseProvider } from '../../db/DataBaseProvider.js';
27
+ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
19
28
  import { FileFactory, FileCleanup } from '../file/file.service.js';
20
29
  import { UserDto } from './user.model.js';
21
30
  import { timer } from '../../client/components/core/CommonJs.js';
22
31
  import { GuestService } from './guest.service.js';
32
+ import { resolveHostKeyContext } from '../../server/conf.js';
23
33
 
24
34
  const logger = loggerFactory(import.meta);
25
35
 
26
- const UserService = {
27
- post: async (req, res, options) => {
28
- /** @type {import('./user.model.js').UserModel} */
29
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
30
-
36
+ /**
37
+ * User Service for handling REST API user operations.
38
+ * Manages authentication, registration, profile CRUD, email verification,
39
+ * password recovery, session management, and guest user lifecycle.
40
+ * @namespace UserService
41
+ */
42
+ class UserService {
43
+ /**
44
+ * POST - Create or authenticate users.
45
+ * Supports authentication, guest account creation, email verification,
46
+ * and password recovery email sending.
47
+ * @async
48
+ * @function post
49
+ * @memberof UserService
50
+ * @param {Object} req - Express request object.
51
+ * @param {Object} res - Express response object.
52
+ * @param {Object} options - Request options containing host and path.
53
+ * @returns {Promise<Object>} User data with auth token, or status message.
54
+ * @throws {Error} If authentication fails, email is invalid, or email send error.
55
+ */
56
+ static post = async (req, res, options) => {
31
57
  /** @type {import('../file/file.model.js').FileModel} */
32
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
58
+ const File = DataBaseProviderService.getModel('File', options);
59
+ /** @type {import('./user.model.js').UserModel} */
60
+ const User = DataBaseProviderService.getModel('User', options);
33
61
 
34
62
  if (req.params.id === 'recover-verify-email') {
35
63
  const user = await User.findOne({
@@ -44,11 +72,10 @@ const UserService = {
44
72
 
45
73
  const token = jwtSign({ email: req.body.email }, options, 15);
46
74
  const payloadToken = jwtSign({ email: req.body.email }, options, 15);
47
- const id = `${options.host}${options.path}`;
75
+ const id = resolveHostKeyContext(options);
48
76
  const translate = MailerProvider.instance[id].translateTemplates.recoverEmail;
49
- const recoverUrl = `${process.env.NODE_ENV === 'development' ? 'http://' : 'https://'}${req.body.hostname}${
50
- req.body.proxyPath
51
- }recover?payload=${payloadToken}`;
77
+ const recoverUrl = `${process.env.NODE_ENV === 'development' ? 'http://' : 'https://'}${req.body.hostname}${req.body.proxyPath
78
+ }recover?payload=${payloadToken}`;
52
79
  const sendResult = await MailerProvider.send({
53
80
  id,
54
81
  sendOptions: {
@@ -81,7 +108,7 @@ const UserService = {
81
108
  if (!validator.isEmail(req.body.email)) throw { message: 'invalid email' };
82
109
 
83
110
  const token = jwtSign({ email: req.body.email }, options, 15);
84
- const id = `${options.host}${options.path}`;
111
+ const id = resolveHostKeyContext(options);
85
112
  const user = await User.findById(req.auth.user._id);
86
113
 
87
114
  if (user.emailConfirmed) throw new Error('email already confirmed');
@@ -130,10 +157,9 @@ const UserService = {
130
157
  });
131
158
  const getMinutesRemaining = () => (-1 * user.failedLoginAttempts - new Date().getTime()) / (1000 * 60);
132
159
  const accountLocketMessage = () =>
133
- `Account locked. Please try again in: ${
134
- getMinutesRemaining() < 1
135
- ? `${(getMinutesRemaining() * 60).toFixed(0)} s`
136
- : `${getMinutesRemaining().toFixed(0)} min`
160
+ `Account locked. Please try again in: ${getMinutesRemaining() < 1
161
+ ? `${(getMinutesRemaining() * 60).toFixed(0)} s`
162
+ : `${getMinutesRemaining().toFixed(0)} min`
137
163
  }.`;
138
164
 
139
165
  if (user) {
@@ -231,13 +257,27 @@ const UserService = {
231
257
  default:
232
258
  return await createUserAndSession(req, res, User, options);
233
259
  }
234
- },
235
- get: async (req, res, options) => {
236
- /** @type {import('./user.model.js').UserModel} */
237
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
238
-
260
+ };
261
+
262
+ /**
263
+ * GET - Retrieve user data.
264
+ * Supports public profile lookup by username, asset retrieval,
265
+ * email lookup, password recovery flow, email verification,
266
+ * admin user listing, authentication refresh, and profile retrieval.
267
+ * @async
268
+ * @function get
269
+ * @memberof UserService
270
+ * @param {Object} req - Express request object.
271
+ * @param {Object} res - Express response object.
272
+ * @param {Object} options - Request options containing host and path.
273
+ * @returns {Promise<Object>} User data with optional session token.
274
+ * @throws {Error} If user not found, profile is private, or token invalid.
275
+ */
276
+ static get = async (req, res, options) => {
239
277
  /** @type {import('../file/file.model.js').FileModel} */
240
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
278
+ const File = DataBaseProviderService.getModel('File', options);
279
+ /** @type {import('./user.model.js').UserModel} */
280
+ const User = DataBaseProviderService.getModel('User', options);
241
281
 
242
282
  if (req.path.startsWith('/u/')) {
243
283
  // First lookup user by username
@@ -309,7 +349,7 @@ const UserService = {
309
349
  {
310
350
  const user = await User.findByIdAndUpdate(_id, { emailConfirmed: true }, { runValidators: true });
311
351
  }
312
- const userWsId = CoreWsMailerChannel.getUserWsId(`${options.host}${options.path}`, user._id.toString());
352
+ const userWsId = CoreWsMailerChannel.getUserWsId(resolveHostKeyContext(options), user._id.toString());
313
353
  if (userWsId && CoreWsMailerChannel.client[userWsId]) {
314
354
  CoreWsEmitter.emit(CoreWsMailerChannel.channel, CoreWsMailerChannel.client[userWsId], {
315
355
  status: 'email-confirmed',
@@ -384,10 +424,23 @@ const UserService = {
384
424
  }
385
425
  }
386
426
  }
387
- },
388
- delete: async (req, res, options) => {
427
+ };
428
+
429
+ /**
430
+ * DELETE - Remove user accounts or log out sessions.
431
+ * Supports admin bulk deletion, user self-deletion, and session logout.
432
+ * @async
433
+ * @function delete
434
+ * @memberof UserService
435
+ * @param {Object} req - Express request object.
436
+ * @param {Object} res - Express response object.
437
+ * @param {Object} options - Request options containing host and path.
438
+ * @returns {Promise<Object>} Deleted user data or logout status message.
439
+ * @throws {Error} If logout fails, user not found, or token invalid.
440
+ */
441
+ static delete = async (req, res, options) => {
389
442
  /** @type {import('./user.model.js').UserModel} */
390
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
443
+ const User = DataBaseProviderService.getModel('User', options);
391
444
 
392
445
  if (req.params.id === 'logout') {
393
446
  const result = await logoutSession(User, req, res);
@@ -417,13 +470,25 @@ const UserService = {
417
470
  }
418
471
  }
419
472
  }
420
- },
421
- put: async (req, res, options) => {
422
- /** @type {import('./user.model.js').UserModel} */
423
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
424
-
473
+ };
474
+
475
+ /**
476
+ * PUT - Update user data.
477
+ * Supports profile image upload, password recovery, and profile field updates.
478
+ * @async
479
+ * @function put
480
+ * @memberof UserService
481
+ * @param {Object} req - Express request object.
482
+ * @param {Object} res - Express response object.
483
+ * @param {Object} options - Request options containing host and path.
484
+ * @returns {Promise<Object>} Updated user data.
485
+ * @throws {Error} If user not found, token invalid, or invalid file.
486
+ */
487
+ static put = async (req, res, options) => {
425
488
  /** @type {import('../file/file.model.js').FileModel} */
426
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
489
+ const File = DataBaseProviderService.getModel('File', options);
490
+ /** @type {import('./user.model.js').UserModel} */
491
+ const User = DataBaseProviderService.getModel('User', options);
427
492
 
428
493
  // req.path | req.baseUrl
429
494
 
@@ -511,7 +576,7 @@ const UserService = {
511
576
  }
512
577
  }
513
578
  }
514
- },
515
- };
579
+ };
580
+ }
516
581
 
517
- export { UserService };
582
+ export { UserService };
@@ -356,7 +356,7 @@ class UnderpostBaremetal {
356
356
 
357
357
  // Build phase (skip if upload-only mode)
358
358
  if (options.packerMaasImageBuild) {
359
- if (shellExec('packer version').code !== 0) {
359
+ if (shellExec('packer version', { silentOnError: true }).code !== 0) {
360
360
  throw new Error('Packer is not installed. Please install Packer to proceed.');
361
361
  }
362
362
 
@@ -424,7 +424,9 @@ rm -rf ${artifacts.join(' ')}`);
424
424
  const uploadCmd = `${uploadScript} ${process.env.MAAS_ADMIN_USERNAME} "${workflow.maas.name}" "${workflow.maas.title}" "${workflow.maas.architecture}" "${workflow.maas.base_image}" "${workflow.maas.filetype}" "${tarballPath}"`;
425
425
 
426
426
  logger.info(`Uploading to MAAS using: ${uploadScript}`);
427
- const uploadResult = shellExec(uploadCmd);
427
+ // silentOnError: caller logs stdout/stderr structure on failure
428
+ // before throwing its own, more informative error.
429
+ const uploadResult = shellExec(uploadCmd, { silentOnError: true });
428
430
  if (uploadResult.code !== 0) {
429
431
  logger.error(`Upload failed with exit code: ${uploadResult.code}`);
430
432
  if (uploadResult.stdout) {
@@ -2895,9 +2897,16 @@ EOF`);
2895
2897
  for (const mountPath of mounts[mountCmd]) {
2896
2898
  const hostMountPath = `${process.env.NFS_EXPORT_PATH}/${hostname}${mountPath}`;
2897
2899
  // Check if the path is already mounted using `mountpoint` command.
2898
- const isPathMounted = !shellExec(`mountpoint ${hostMountPath}`, { silent: true, stdout: true }).match(
2899
- 'not a mountpoint',
2900
- );
2900
+ // `mountpoint` exits 1 when the path is not a mountpoint — silentOnError
2901
+ // prevents ShellExecError so we can inspect stdout/stderr for the string.
2902
+ const mountpointOut = shellExec(`mountpoint ${hostMountPath}`, {
2903
+ silent: true,
2904
+ stdout: true,
2905
+ silentOnError: true,
2906
+ });
2907
+ const isPathMounted = typeof mountpointOut === 'string' && mountpointOut.length > 0
2908
+ ? !mountpointOut.match('not a mountpoint') && !mountpointOut.match('No such file')
2909
+ : false;
2901
2910
 
2902
2911
  if (isPathMounted) {
2903
2912
  logger.warn('Nfs path already mounted', mountPath);
@@ -3041,10 +3050,10 @@ udp-port = 32766
3041
3050
  // Check both /usr/local/bin (compiled) and system paths
3042
3051
  let qemuAarch64Path = null;
3043
3052
 
3044
- if (shellExec('test -x /usr/local/bin/qemu-system-aarch64').code === 0) {
3053
+ if (shellExec('test -x /usr/local/bin/qemu-system-aarch64', { silentOnError: true }).code === 0) {
3045
3054
  qemuAarch64Path = '/usr/local/bin/qemu-system-aarch64';
3046
- } else if (shellExec('which qemu-system-aarch64').code === 0) {
3047
- qemuAarch64Path = shellExec('which qemu-system-aarch64').stdout.trim();
3055
+ } else if (shellExec('which qemu-system-aarch64', { silentOnError: true }).code === 0) {
3056
+ qemuAarch64Path = shellExec('which qemu-system-aarch64', { stdout: true }).trim();
3048
3057
  }
3049
3058
 
3050
3059
  if (!qemuAarch64Path) {
@@ -3070,10 +3079,10 @@ udp-port = 32766
3070
3079
  // Check both /usr/local/bin (compiled) and system paths
3071
3080
  let qemuX86Path = null;
3072
3081
 
3073
- if (shellExec('test -x /usr/local/bin/qemu-system-x86_64').code === 0) {
3082
+ if (shellExec('test -x /usr/local/bin/qemu-system-x86_64', { silentOnError: true }).code === 0) {
3074
3083
  qemuX86Path = '/usr/local/bin/qemu-system-x86_64';
3075
- } else if (shellExec('which qemu-system-x86_64').code === 0) {
3076
- qemuX86Path = shellExec('which qemu-system-x86_64').stdout.trim();
3084
+ } else if (shellExec('which qemu-system-x86_64', { silentOnError: true }).code === 0) {
3085
+ qemuX86Path = shellExec('which qemu-system-x86_64', { stdout: true }).trim();
3077
3086
  }
3078
3087
 
3079
3088
  if (!qemuX86Path) {