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,5 +1,14 @@
1
+ /**
2
+ * Document service module for handling document CRUD operations.
3
+ * Provides REST API handlers for document management with security-aware
4
+ * public/private document access control, search, and file cleanup integration.
5
+ *
6
+ * @module src/api/document/document.service.js
7
+ * @namespace DocumentService
8
+ */
9
+
1
10
  import { loggerFactory } from '../../server/logger.js';
2
- import { DataBaseProvider } from '../../db/DataBaseProvider.js';
11
+ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
3
12
  import { DocumentDto } from './document.model.js';
4
13
  import { uniqueArray } from '../../client/components/core/CommonJs.js';
5
14
  import { getBearerToken, verifyJWT } from '../../server/auth.js';
@@ -8,10 +17,26 @@ import { FileCleanup } from '../file/file.service.js';
8
17
 
9
18
  const logger = loggerFactory(import.meta);
10
19
 
11
- const DocumentService = {
12
- post: async (req, res, options) => {
20
+ /**
21
+ * Document Service for handling REST API document operations.
22
+ * Implements a security model for public/private document access control
23
+ * with support for high-query typeahead search, tag filtering, and pagination.
24
+ * @namespace DocumentService
25
+ */
26
+ class DocumentService {
27
+ /**
28
+ * POST - Create a new document.
29
+ * @async
30
+ * @function post
31
+ * @memberof DocumentService
32
+ * @param {Object} req - Express request object.
33
+ * @param {Object} res - Express response object.
34
+ * @param {Object} options - Request options containing host and path.
35
+ * @returns {Promise<Object>} Created document object.
36
+ */
37
+ static post = async (req, res, options) => {
13
38
  /** @type {import('./document.model.js').DocumentModel} */
14
- const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
39
+ const Document = DataBaseProviderService.getModel("Document", options);
15
40
 
16
41
  switch (req.params.id) {
17
42
  default:
@@ -25,14 +50,28 @@ const DocumentService = {
25
50
 
26
51
  return await new Document(req.body).save();
27
52
  }
28
- },
29
- get: async (req, res, options) => {
53
+ };
54
+
55
+ /**
56
+ * GET - Retrieve documents.
57
+ * Supports public high-query search, public tag-filtered listing,
58
+ * and authenticated user document retrieval with pagination.
59
+ * @async
60
+ * @function get
61
+ * @memberof DocumentService
62
+ * @param {Object} req - Express request object.
63
+ * @param {Object} res - Express response object.
64
+ * @param {Object} options - Request options containing host and path.
65
+ * @returns {Promise<Object>} Document data with optional pagination metadata.
66
+ * @throws {Error} If search query is invalid or document not found.
67
+ */
68
+ static get = async (req, res, options) => {
30
69
  /** @type {import('./document.model.js').DocumentModel} */
31
- const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
70
+ const Document = DataBaseProviderService.getModel("Document", options);
32
71
  /** @type {import('../user/user.model.js').UserModel} */
33
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
72
+ const User = DataBaseProviderService.getModel("User", options);
34
73
  /** @type {import('../file/file.model.js').FileModel} */
35
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
74
+ const File = DataBaseProviderService.getModel("File", options);
36
75
 
37
76
  // High-query endpoint for typeahead search
38
77
  //
@@ -436,12 +475,24 @@ const DocumentService = {
436
475
  };
437
476
  }
438
477
  }
439
- },
440
- delete: async (req, res, options) => {
478
+ };
479
+
480
+ /**
481
+ * DELETE - Remove a document and its associated files.
482
+ * @async
483
+ * @function delete
484
+ * @memberof DocumentService
485
+ * @param {Object} req - Express request object.
486
+ * @param {Object} res - Express response object.
487
+ * @param {Object} options - Request options containing host and path.
488
+ * @returns {Promise<Object>} Deleted document object.
489
+ * @throws {Error} If document not found or user not authorized.
490
+ */
491
+ static delete = async (req, res, options) => {
441
492
  /** @type {import('./document.model.js').DocumentModel} */
442
- const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
493
+ const Document = DataBaseProviderService.getModel("Document", options);
443
494
  /** @type {import('../file/file.model.js').FileModel} */
444
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
495
+ const File = DataBaseProviderService.getModel("File", options);
445
496
 
446
497
  switch (req.params.id) {
447
498
  default: {
@@ -460,12 +511,25 @@ const DocumentService = {
460
511
  return await Document.findByIdAndDelete(req.params.id);
461
512
  }
462
513
  }
463
- },
464
- put: async (req, res, options) => {
514
+ };
515
+
516
+ /**
517
+ * PUT - Update a document.
518
+ * Cleans up replaced files and handles tag-based isPublic extraction.
519
+ * @async
520
+ * @function put
521
+ * @memberof DocumentService
522
+ * @param {Object} req - Express request object.
523
+ * @param {Object} res - Express response object.
524
+ * @param {Object} options - Request options containing host and path.
525
+ * @returns {Promise<Object>} Updated document object.
526
+ * @throws {Error} If document not found.
527
+ */
528
+ static put = async (req, res, options) => {
465
529
  /** @type {import('./document.model.js').DocumentModel} */
466
- const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
530
+ const Document = DataBaseProviderService.getModel("Document", options);
467
531
  /** @type {import('../file/file.model.js').FileModel} */
468
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
532
+ const File = DataBaseProviderService.getModel("File", options);
469
533
 
470
534
  switch (req.params.id) {
471
535
  default: {
@@ -500,10 +564,23 @@ const DocumentService = {
500
564
  return await Document.findByIdAndUpdate(req.params.id, req.body, { returnDocument: 'after' });
501
565
  }
502
566
  }
503
- },
504
- patch: async (req, res, options) => {
567
+ };
568
+
569
+ /**
570
+ * PATCH - Partially update a document.
571
+ * Supports toggle-public and copy-share-link operations.
572
+ * @async
573
+ * @function patch
574
+ * @memberof DocumentService
575
+ * @param {Object} req - Express request object.
576
+ * @param {Object} res - Express response object.
577
+ * @param {Object} options - Request options containing host and path.
578
+ * @returns {Promise<Object>} Updated document or result object.
579
+ * @throws {Error} If document not found or invalid patch endpoint.
580
+ */
581
+ static patch = async (req, res, options) => {
505
582
  /** @type {import('./document.model.js').DocumentModel} */
506
- const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
583
+ const Document = DataBaseProviderService.getModel("Document", options);
507
584
 
508
585
  if (req.path.includes('/toggle-public')) {
509
586
  const document = await Document.findById(req.params.id);
@@ -550,7 +627,7 @@ const DocumentService = {
550
627
  }
551
628
 
552
629
  throw new Error('Invalid patch endpoint');
553
- },
554
- };
630
+ };
631
+ }
555
632
 
556
- export { DocumentService };
633
+ export { DocumentService };
@@ -2,21 +2,27 @@ import { adminGuard } from '../../server/auth.js';
2
2
  import { loggerFactory } from '../../server/logger.js';
3
3
  import { FileController } from './file.controller.js';
4
4
  import express from 'express';
5
+
5
6
  const logger = loggerFactory(import.meta);
6
7
 
7
- const FileRouter = (options) => {
8
- const router = express.Router();
9
- const authMiddleware = options.authMiddleware;
10
- router.post(`/:id`, authMiddleware, async (req, res) => await FileController.post(req, res, options));
11
- router.post(`/`, authMiddleware, async (req, res) => await FileController.post(req, res, options));
12
- router.get(`/blob/:id`, async (req, res) => await FileController.get(req, res, options));
13
- router.get(`/:id`, async (req, res) => await FileController.get(req, res, options));
14
- router.get(`/`, async (req, res) => await FileController.get(req, res, options));
15
- router.delete(`/:id`, authMiddleware, adminGuard, async (req, res) => await FileController.delete(req, res, options));
16
- router.delete(`/`, authMiddleware, adminGuard, async (req, res) => await FileController.delete(req, res, options));
17
- return router;
18
- };
8
+ class FileRouter {
9
+ /**
10
+ * @param {import('../types.js').RouterOptions} options
11
+ * @returns {import('express').Router}
12
+ */
13
+ static router(options) {
14
+ const router = express.Router();
15
+ router.post(`/:id`, options.authMiddleware, async (req, res) => await FileController.post(req, res, options));
16
+ router.post(`/`, options.authMiddleware, async (req, res) => await FileController.post(req, res, options));
17
+ router.get(`/blob/:id`, async (req, res) => await FileController.get(req, res, options));
18
+ router.get(`/:id`, async (req, res) => await FileController.get(req, res, options));
19
+ router.get(`/`, async (req, res) => await FileController.get(req, res, options));
20
+ router.delete(`/:id`, options.authMiddleware, adminGuard, async (req, res) => await FileController.delete(req, res, options));
21
+ router.delete(`/`, options.authMiddleware, adminGuard, async (req, res) => await FileController.delete(req, res, options));
22
+ return router;
23
+ }
24
+ }
19
25
 
20
- const ApiRouter = FileRouter;
26
+ const ApiRouter = (options) => FileRouter.router(options);
21
27
 
22
28
  export { ApiRouter, FileRouter };
@@ -6,7 +6,7 @@
6
6
  * @namespace FileServiceServer
7
7
  */
8
8
 
9
- import { DataBaseProvider } from '../../db/DataBaseProvider.js';
9
+ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
10
10
  import { getBearerToken, jwtVerify } from '../../server/auth.js';
11
11
  import { loggerFactory } from '../../server/logger.js';
12
12
  import crypto from 'crypto';
@@ -266,7 +266,9 @@ class FileCleanup {
266
266
  const newFileId = newData[field];
267
267
 
268
268
  // If field has old file and new data changes or removes it
269
- if (oldFileId && newFileId !== undefined && String(oldFileId) !== String(newFileId)) {
269
+ // newFileId === null means client explicitly wants to remove the file
270
+ // newFileId === undefined means field was not included in request (no change)
271
+ if (oldFileId && newFileId !== undefined && (newFileId === null || String(oldFileId) !== String(newFileId))) {
270
272
  try {
271
273
  const file = await File.findOne({ _id: oldFileId });
272
274
  if (file) {
@@ -345,7 +347,7 @@ class FileService {
345
347
  }
346
348
 
347
349
  /** @type {import('./file.model.js').FileModel} */
348
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
350
+ const File = DataBaseProviderService.getModel("File", options);
349
351
 
350
352
  const uploadedFiles = await FileFactory.upload(req, File);
351
353
  return FileServiceDto.toMetadataArray(uploadedFiles);
@@ -367,11 +369,11 @@ class FileService {
367
369
  */
368
370
  static get = async (req, res, options) => {
369
371
  /** @type {import('./file.model.js').FileModel} */
370
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
372
+ const File = DataBaseProviderService.getModel("File", options);
371
373
  /** @type {import('../document/document.model.js').DocumentModel} */
372
- const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
374
+ const Document = DataBaseProviderService.getModel("Document", options);
373
375
  /** @type {import('../user/user.model.js').User} */
374
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
376
+ const User = DataBaseProviderService.getModel("User", options);
375
377
 
376
378
  const isFileAuthorized = async (fileId) => {
377
379
  try {
@@ -482,7 +484,7 @@ class FileService {
482
484
  */
483
485
  static delete = async (req, res, options) => {
484
486
  /** @type {import('./file.model.js').FileModel} */
485
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
487
+ const File = DataBaseProviderService.getModel("File", options);
486
488
 
487
489
  const result = await File.findByIdAndDelete(req.params.id);
488
490
 
@@ -4,18 +4,23 @@ import express from 'express';
4
4
 
5
5
  const logger = loggerFactory(import.meta);
6
6
 
7
- const TestRouter = (options) => {
8
- const router = express.Router();
9
- const authMiddleware = options.authMiddleware;
10
- router.post(`/:id`, async (req, res) => await TestController.post(req, res, options));
11
- router.post(`/`, authMiddleware, async (req, res) => await TestController.post(req, res, options));
12
- router.get(`/:id`, async (req, res) => await TestController.get(req, res, options));
13
- router.get(`/`, async (req, res) => await TestController.get(req, res, options));
14
- router.delete(`/:id`, async (req, res) => await TestController.delete(req, res, options));
15
- router.delete(`/`, async (req, res) => await TestController.delete(req, res, options));
16
- return router;
17
- };
7
+ class TestRouter {
8
+ /**
9
+ * @param {import('../types.js').RouterOptions} options
10
+ * @returns {import('express').Router}
11
+ */
12
+ static router(options) {
13
+ const router = express.Router();
14
+ router.post(`/:id`, async (req, res) => await TestController.post(req, res, options));
15
+ router.post(`/`, options.authMiddleware, async (req, res) => await TestController.post(req, res, options));
16
+ router.get(`/:id`, async (req, res) => await TestController.get(req, res, options));
17
+ router.get(`/`, async (req, res) => await TestController.get(req, res, options));
18
+ router.delete(`/:id`, async (req, res) => await TestController.delete(req, res, options));
19
+ router.delete(`/`, async (req, res) => await TestController.delete(req, res, options));
20
+ return router;
21
+ }
22
+ }
18
23
 
19
- const ApiRouter = TestRouter;
24
+ const ApiRouter = (options) => TestRouter.router(options);
20
25
 
21
26
  export { ApiRouter, TestRouter };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @module src/api/types
3
+ * @description Shared type definitions for the Express router layer.
4
+ */
5
+
6
+ /**
7
+ * Injected dependencies and runtime configuration for every API router instance.
8
+ * Keyed per-host/path by the multi-tenant engine (see src/runtime/express/Express.js).
9
+ *
10
+ * @typedef {Object} RouterOptions
11
+ * @property {import('express').Application} app - Express application instance.
12
+ * @property {string} host - Per-runtime host identifier (e.g. "nexodev.org").
13
+ * @property {string} path - Per-runtime path prefix (e.g. "/" or "/cyberia").
14
+ * @property {string} apiPath - Computed base URL for the API layer.
15
+ * @property {string[]} origins - Allowed origins for CORS validation.
16
+ * @property {import('express').RequestHandler} authMiddleware - Dynamically generated JWT auth middleware (keyed by host+path).
17
+ * @property {Object} [db] - DataBaseProviderService configuration or instance reference.
18
+ * @property {Object} [mailer] - MailerProvider configuration or instance reference.
19
+ * @property {Object} [png] - Cached mailer image buffers (populated by UserRouter on first load).
20
+ * @property {Record<string, Buffer>} [png.buffer] - Map of image key to raw PNG buffer.
21
+ * @property {Function} [png.header] - Sets CORS/Content-Type response headers for PNG responses.
22
+ */
23
+
24
+ export { };
@@ -22,12 +22,13 @@ const _guestTtlMs = () => {
22
22
  */
23
23
  const buildGuestUser = (options) => {
24
24
  const now = new Date().toISOString();
25
- const _id = new mongoose.Types.ObjectId().toString();
25
+ const objectId = new mongoose.Types.ObjectId().toString();
26
26
  const role = 'guest';
27
+ const username = `${role}${objectId.slice(-5)}`;
27
28
  return {
28
- _id: `${role}${_id}`,
29
- username: `${role}${_id.slice(-5)}`,
30
- email: `${_id}@${options.host || 'localhost'}`,
29
+ _id: `${role}${objectId}`,
30
+ username,
31
+ email: `${username}@${options.host || 'localhost'}`,
31
32
  password: hashPassword(process.env.JWT_SECRET),
32
33
  role,
33
34
  emailConfirmed: false,