@underpostnet/underpost 2.97.0 → 2.97.5

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 (78) hide show
  1. package/README.md +2 -2
  2. package/baremetal/commission-workflows.json +33 -3
  3. package/bin/deploy.js +1 -1
  4. package/cli.md +7 -2
  5. package/conf.js +3 -0
  6. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  7. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  8. package/package.json +1 -1
  9. package/packer/scripts/fuse-tar-root +3 -3
  10. package/scripts/disk-clean.sh +23 -23
  11. package/scripts/gpu-diag.sh +2 -2
  12. package/scripts/ip-info.sh +11 -11
  13. package/scripts/maas-upload-boot-resource.sh +1 -1
  14. package/scripts/nvim.sh +1 -1
  15. package/scripts/packer-setup.sh +13 -13
  16. package/scripts/rocky-setup.sh +2 -2
  17. package/scripts/rpmfusion-ffmpeg-setup.sh +4 -4
  18. package/scripts/ssl.sh +7 -7
  19. package/src/api/core/core.service.js +0 -5
  20. package/src/api/default/default.service.js +7 -5
  21. package/src/api/document/document.model.js +30 -1
  22. package/src/api/document/document.router.js +6 -0
  23. package/src/api/document/document.service.js +423 -51
  24. package/src/api/file/file.model.js +112 -4
  25. package/src/api/file/file.ref.json +42 -0
  26. package/src/api/file/file.service.js +380 -32
  27. package/src/api/user/user.model.js +38 -1
  28. package/src/api/user/user.router.js +96 -63
  29. package/src/api/user/user.service.js +81 -48
  30. package/src/cli/baremetal.js +689 -329
  31. package/src/cli/cluster.js +50 -52
  32. package/src/cli/db.js +424 -166
  33. package/src/cli/deploy.js +1 -1
  34. package/src/cli/index.js +12 -1
  35. package/src/cli/lxd.js +3 -3
  36. package/src/cli/repository.js +1 -1
  37. package/src/cli/run.js +2 -1
  38. package/src/cli/ssh.js +10 -10
  39. package/src/client/components/core/Account.js +327 -36
  40. package/src/client/components/core/AgGrid.js +3 -0
  41. package/src/client/components/core/Auth.js +9 -3
  42. package/src/client/components/core/Chat.js +2 -2
  43. package/src/client/components/core/Content.js +159 -78
  44. package/src/client/components/core/Css.js +16 -2
  45. package/src/client/components/core/CssCore.js +16 -12
  46. package/src/client/components/core/FileExplorer.js +115 -8
  47. package/src/client/components/core/Input.js +204 -11
  48. package/src/client/components/core/LogIn.js +42 -20
  49. package/src/client/components/core/Modal.js +257 -177
  50. package/src/client/components/core/Panel.js +324 -27
  51. package/src/client/components/core/PanelForm.js +280 -73
  52. package/src/client/components/core/PublicProfile.js +888 -0
  53. package/src/client/components/core/Router.js +117 -15
  54. package/src/client/components/core/SearchBox.js +1117 -0
  55. package/src/client/components/core/SignUp.js +26 -7
  56. package/src/client/components/core/SocketIo.js +6 -3
  57. package/src/client/components/core/Translate.js +98 -0
  58. package/src/client/components/core/Validator.js +15 -0
  59. package/src/client/components/core/windowGetDimensions.js +6 -6
  60. package/src/client/components/default/MenuDefault.js +59 -12
  61. package/src/client/components/default/RoutesDefault.js +1 -0
  62. package/src/client/services/core/core.service.js +163 -1
  63. package/src/client/services/default/default.management.js +451 -64
  64. package/src/client/services/default/default.service.js +13 -6
  65. package/src/client/services/document/document.service.js +23 -0
  66. package/src/client/services/file/file.service.js +43 -16
  67. package/src/client/services/user/user.service.js +13 -9
  68. package/src/db/DataBaseProvider.js +1 -1
  69. package/src/db/mongo/MongooseDB.js +1 -1
  70. package/src/index.js +1 -1
  71. package/src/mailer/MailerProvider.js +4 -4
  72. package/src/runtime/express/Express.js +2 -1
  73. package/src/runtime/lampp/Lampp.js +2 -2
  74. package/src/server/auth.js +3 -6
  75. package/src/server/data-query.js +449 -0
  76. package/src/server/dns.js +4 -4
  77. package/src/server/object-layer.js +0 -3
  78. package/src/ws/IoInterface.js +2 -2
@@ -19,7 +19,7 @@ const SignUp = {
19
19
  {
20
20
  model: 'username',
21
21
  id: `sign-up-username`,
22
- rules: [{ type: 'isEmpty' }, { type: 'isLength', options: { min: 2, max: 20 } }],
22
+ rules: [{ type: 'isEmpty' }, { type: 'isLength', options: { min: 2, max: 20 } }, { type: 'isValidUsername' }],
23
23
  },
24
24
  { model: 'email', id: `sign-up-email`, rules: [{ type: 'isEmpty' }, { type: 'isEmail' }] },
25
25
  {
@@ -43,23 +43,42 @@ const SignUp = {
43
43
  if ('model' in inputData) body[inputData.model] = s(`.${inputData.id}`).value;
44
44
  }
45
45
  const result = await UserService.post({ body });
46
+ const handleSignUpError = (data) => {
47
+ let error = '';
48
+ if (data.message) {
49
+ if (data.message.match('duplicate')) {
50
+ if (data.message.match('username')) error += Translate.Render('error-username-taken');
51
+ if (data.message.match('email')) error += Translate.Render('error-email-taken');
52
+ } else {
53
+ if (data.message.match('username')) error += Translate.Render('error-username-invalid');
54
+ if (data.message.match('email')) error += Translate.Render('error-email-invalid');
55
+ if (data.message.match('password')) error += Translate.Render('error-password-invalid');
56
+ }
57
+ return error;
58
+ }
59
+ return Translate.Render('error-register-user');
60
+ };
46
61
  NotificationManager.Push({
47
62
  html:
48
63
  typeof result.data === 'string'
49
64
  ? result.data
50
65
  : result.status === 'success'
51
- ? Translate.Render(`success-register-user`)
52
- : Translate.Render(`no-valid-register`),
66
+ ? Translate.Render(`success-register-user`)
67
+ : handleSignUpError(result),
53
68
  status: result.status,
54
69
  });
55
70
  if (result.status === 'success') {
56
71
  await Auth.sessionIn(result);
57
- s(`.btn-close-${options.idModal}`).click();
72
+ setTimeout(() => {
73
+ if (s(`.btn-close-${options.idModal}`)) s(`.btn-close-${options.idModal}`).click();
74
+ });
58
75
  }
59
76
  });
60
- s(`.btn-sign-up-i-have-account`).onclick = () => {
61
- s(`.main-btn-log-in`).click();
62
- };
77
+ setTimeout(() => {
78
+ s(`.btn-sign-up-i-have-account`).onclick = () => {
79
+ s(`.main-btn-log-in`).click();
80
+ };
81
+ });
63
82
  });
64
83
  return html`
65
84
  ${await BtnIcon.Render({
@@ -21,9 +21,12 @@ const SocketIo = {
21
21
  },
22
22
  Init: async function (options) {
23
23
  if (this.socket) this.socket.disconnect();
24
- this.host = options.host ?? getWsBaseUrl({ wsBasePath: '' });
25
- logger.info(`ws host:`, this.host);
26
- const path = typeof options.path === 'string' ? options.path : getWsBasePath();
24
+ const path = getWsBasePath();
25
+ this.host = options.host ? options.host : getWsBaseUrl({ wsBasePath: '' });
26
+ logger.info(`ws host:`, {
27
+ host: this.host,
28
+ path,
29
+ });
27
30
  const connectOptions = {
28
31
  path: path === '/' ? undefined : path,
29
32
  // auth: {
@@ -111,6 +111,30 @@ const TranslateCore = {
111
111
  es: 'Este campo no cumple con los requisitos específicos',
112
112
  en: 'This field does not meet specific requirements',
113
113
  },
114
+ 'error-username-invalid': {
115
+ es: 'Nombre de usuario no válido. Debe contener solo letras, números y guiones bajos.',
116
+ en: 'Invalid username. Must contain only letters, numbers, and underscores.',
117
+ },
118
+ 'error-email-invalid': {
119
+ es: 'Dirección de correo electrónico no válida.',
120
+ en: 'Invalid email address.',
121
+ },
122
+ 'error-password-invalid': {
123
+ es: 'Contraseña no válida. Debe tener al menos 8 caracteres, incluir mayúsculas, minúsculas, números y símbolos.',
124
+ en: 'Invalid password. Must be at least 8 characters long and include uppercase, lowercase, numbers, and symbols.',
125
+ },
126
+ 'error-username-taken': {
127
+ es: 'Este nombre de usuario ya está en uso. Por favor, elige otro.',
128
+ en: 'This username is already taken. Please choose another one.',
129
+ },
130
+ 'error-email-taken': {
131
+ es: 'Esta dirección de correo electrónico ya está registrada.',
132
+ en: 'This email address is already registered.',
133
+ },
134
+ 'error-register-user': {
135
+ es: 'Error al registrar el usuario. Por favor, intenta nuevamente.',
136
+ en: 'Error registering user. Please try again.',
137
+ },
114
138
  };
115
139
  Translate.Data['isMobilePhone'] = {
116
140
  en: 'Invalid mobile phone number. Please check the format and try again.',
@@ -141,6 +165,16 @@ const TranslateCore = {
141
165
  Translate.Data['download'] = { en: 'download', es: 'Descargar' };
142
166
  Translate.Data['delete'] = { en: 'delete', es: 'Eliminar' };
143
167
  Translate.Data['success-delete'] = { en: 'success delete item', es: 'Item eliminado con exito' };
168
+ Translate.Data['document-now-public'] = { en: 'Document is now public', es: 'El documento ahora es público' };
169
+ Translate.Data['document-now-private'] = { en: 'Document is now private', es: 'El documento ahora es privado' };
170
+ Translate.Data['error-toggle-public'] = {
171
+ en: 'Failed to toggle public status',
172
+ es: 'Error al cambiar estado público',
173
+ };
174
+ Translate.Data['confirm-make-public'] = {
175
+ en: 'Are you sure you want to make this document public?',
176
+ es: '¿Estás seguro de que deseas hacer público este documento?',
177
+ };
144
178
  Translate.Data['invalid-data'] = { en: 'Invalid data', es: 'Datos invalidos' };
145
179
  Translate.Data['upload'] = { en: 'upload', es: 'Subir' };
146
180
  Translate.Data['load'] = { en: 'load', es: 'Cargar' };
@@ -179,6 +213,10 @@ const TranslateCore = {
179
213
  en: 'Error during user login. Please check your credentials and try again.',
180
214
  es: 'Error durante el inicio de sesión del usuario. Por favor, verifica tus credenciales e intenta nuevamente.',
181
215
  };
216
+ Translate.Data['error-user-not-authenticated'] = {
217
+ en: 'You must be logged in to perform this action.',
218
+ es: 'Debe iniciar sesión para realizar esta acción.',
219
+ };
182
220
  Translate.Data['confirm-logout'] = { en: 'Confirm Logout', es: 'Confirmar cierre de sesión' };
183
221
  Translate.Data['success-logout'] = { en: 'Successful session logout', es: 'Cierre de sesión exitoso' };
184
222
  Translate.Data['account'] = { en: 'Account', es: 'Cuenta' };
@@ -531,6 +569,66 @@ const TranslateCore = {
531
569
  en: 'Require title and content or file',
532
570
  es: 'Requiere título y contenido o archivo',
533
571
  };
572
+ Translate.Data['public-profile'] = {
573
+ en: 'Public Profile',
574
+ es: 'Perfil Público',
575
+ };
576
+ Translate.Data['private-profile'] = {
577
+ en: 'Private Profile',
578
+ es: 'Perfil Privado',
579
+ };
580
+ Translate.Data['brief-description'] = {
581
+ en: 'Brief Description',
582
+ es: 'Descripción Breve',
583
+ };
584
+ Translate.Data['brief-description-cannot-be-empty'] = {
585
+ en: 'Brief description cannot be empty',
586
+ es: 'La descripción breve no puede estar vacía',
587
+ };
588
+ Translate.Data['user-not-found'] = {
589
+ en: 'User not found',
590
+ es: 'Usuario no encontrado',
591
+ };
592
+ Translate.Data['error-loading-profile'] = {
593
+ en: 'Error loading profile',
594
+ es: 'Error al cargar el perfil',
595
+ };
596
+ Translate.Data['profile-is-private'] = {
597
+ en: 'This profile is private',
598
+ es: 'Este perfil es privado',
599
+ };
600
+ Translate.Data['no-description'] = {
601
+ en: 'No description provided',
602
+ es: 'Sin descripción',
603
+ };
604
+ Translate.Data['member-since'] = {
605
+ en: 'Member since',
606
+ es: 'Miembro desde',
607
+ };
608
+ Translate.Data['followers'] = {
609
+ en: 'Followers',
610
+ es: 'Seguidores',
611
+ };
612
+ Translate.Data['following'] = {
613
+ en: 'Following',
614
+ es: 'Siguiendo',
615
+ };
616
+ Translate.Data['invalid-username-format'] = {
617
+ en: 'Username can only contain letters, numbers, hyphens, and underscores',
618
+ es: 'El nombre de usuario solo puede contener letras, números, guiones e guiones bajos',
619
+ };
620
+ Translate.Data['go-home'] = {
621
+ en: 'Go Home',
622
+ es: 'Ir a Inicio',
623
+ };
624
+ Translate.Data['go-back'] = {
625
+ en: 'Go Back',
626
+ es: 'Volver',
627
+ };
628
+ Translate.Data['public-profile'] = {
629
+ en: 'Public Profile',
630
+ es: 'Perfil Público',
631
+ };
534
632
  },
535
633
  };
536
634
 
@@ -66,6 +66,21 @@ const Validator = {
66
66
 
67
67
  break;
68
68
  }
69
+ case 'isValidUsername': {
70
+ const username = s(`.${validatorData.id}`).value;
71
+ // Check if username is empty or only whitespace
72
+ if (!username || username.trim() === '') {
73
+ errorMessage += this.renderErrorMessage(undefined, Translate.Render('invalid-username-format'));
74
+ break;
75
+ }
76
+ // Check if username contains only valid characters (no spaces or special chars)
77
+ const usernameRegex = /^[a-zA-Z0-9_-]+$/;
78
+ if (!usernameRegex.test(username)) {
79
+ errorMessage += this.renderErrorMessage(undefined, Translate.Render('invalid-username-format'));
80
+ }
81
+
82
+ break;
83
+ }
69
84
  default:
70
85
  if (
71
86
  validator[rule.type] &&
@@ -53,7 +53,7 @@ export class PwaWindowDimensions {
53
53
  // --- Private Static Getters (Encapsulating browser APIs) ---
54
54
 
55
55
  /**
56
- * @private
56
+ * @method
57
57
  * @static
58
58
  * Try visualViewport values (most accurate for "what's actually visible").
59
59
  * @returns {{height: number|null, width: number|null}}
@@ -68,7 +68,7 @@ export class PwaWindowDimensions {
68
68
  }
69
69
 
70
70
  /**
71
- * @private
71
+ * @method
72
72
  * @static
73
73
  * Try layout viewport (doctype-root) measurements.
74
74
  * document.documentElement.clientHeight/clientWidth are stable and widely used.
@@ -84,7 +84,7 @@ export class PwaWindowDimensions {
84
84
  }
85
85
 
86
86
  /**
87
- * @private
87
+ * @method
88
88
  * @static
89
89
  * Try window.* measurements (innerHeight/innerWidth are widely supported).
90
90
  * @returns {{height: number|null, width: number|null}}
@@ -98,7 +98,7 @@ export class PwaWindowDimensions {
98
98
  }
99
99
 
100
100
  /**
101
- * @private
101
+ * @method
102
102
  * @static
103
103
  * Try body measurements (less reliable, used as a fallback).
104
104
  * @returns {{height: number|null, width: number|null}}
@@ -112,7 +112,7 @@ export class PwaWindowDimensions {
112
112
  }
113
113
 
114
114
  /**
115
- * @private
115
+ * @method
116
116
  * @static
117
117
  * Try screen measurements (physical screen/last-resort fallback).
118
118
  * @returns {{height: number|null, width: number|null}}
@@ -130,7 +130,7 @@ export class PwaWindowDimensions {
130
130
  }
131
131
 
132
132
  /**
133
- * @private
133
+ * @method
134
134
  * @static
135
135
  * Try outer dimensions (less reliable, sometimes available fallback).
136
136
  * @returns {{height: number|null, width: number|null}}
@@ -1,16 +1,7 @@
1
1
  import { Account } from '../core/Account.js';
2
2
  import { BtnIcon } from '../core/BtnIcon.js';
3
3
  import { getId, newInstance } from '../core/CommonJs.js';
4
- import {
5
- borderChar,
6
- boxShadow,
7
- Css,
8
- darkTheme,
9
- extractBackgroundImageUrl,
10
- renderCssAttr,
11
- ThemeEvents,
12
- Themes,
13
- } from '../core/Css.js';
4
+ import { Css, darkTheme, ThemeEvents, Themes } from '../core/Css.js';
14
5
  import { EventsUI } from '../core/EventsUI.js';
15
6
  import { LogIn } from '../core/LogIn.js';
16
7
  import { LogOut } from '../core/LogOut.js';
@@ -18,19 +9,19 @@ import { buildBadgeToolTipMenuOption, Modal, renderMenuLabel, renderViewTitle }
18
9
  import { SignUp } from '../core/SignUp.js';
19
10
  import { Translate } from '../core/Translate.js';
20
11
  import { htmls, s } from '../core/VanillaJs.js';
21
- import { getProxyPath } from '../core/Router.js';
12
+ import { extractUsernameFromPath, getProxyPath, getQueryParams } from '../core/Router.js';
22
13
  import { ElementsDefault } from './ElementsDefault.js';
23
14
  import Sortable from 'sortablejs';
24
15
  import { RouterDefault, BannerAppTemplate } from './RoutesDefault.js';
25
16
  import { SettingsDefault } from './SettingsDefault.js';
26
17
  import { Badge } from '../core/Badge.js';
27
- import { Docs } from '../core/Docs.js';
28
18
  import { Recover } from '../core/Recover.js';
29
19
  import { DefaultManagement } from '../../services/default/default.management.js';
30
20
  import { Page500 } from '../core/500.js';
31
21
  import { Page404 } from '../core/404.js';
32
22
  import { PanelForm } from '../core/PanelForm.js';
33
23
  import { Chat } from '../core/Chat.js';
24
+ import { PublicProfile } from '../core/PublicProfile.js';
34
25
 
35
26
  const MenuDefault = {
36
27
  Data: {},
@@ -111,6 +102,19 @@ const MenuDefault = {
111
102
  handleContainerClass: 'handle-btn-container',
112
103
  tooltipHtml: await Badge.Render(buildBadgeToolTipMenuOption('account')),
113
104
  })}
105
+ ${await BtnIcon.Render({
106
+ class: 'in wfa main-btn-menu main-btn-public-profile',
107
+ useMenuBtn: true,
108
+ label: renderMenuLabel({
109
+ icon: html`<i class="fas fa-user-tag"></i>`,
110
+ text: html`<span class="menu-label-text">${Translate.Render('public-profile')}</span>`,
111
+ }),
112
+ style: 'display: none',
113
+ attrs: `data-id="public-profile"`,
114
+ tabHref: `${getProxyPath()}u`,
115
+ handleContainerClass: 'handle-btn-container',
116
+ tooltipHtml: await Badge.Render(buildBadgeToolTipMenuOption('public-profile')),
117
+ })}
114
118
  ${await BtnIcon.Render({
115
119
  class: 'in wfa main-btn-menu main-btn-settings',
116
120
  useMenuBtn: true,
@@ -558,6 +562,49 @@ const MenuDefault = {
558
562
  });
559
563
  });
560
564
 
565
+ EventsUI.onClick(`.main-btn-public-profile`, async () => {
566
+ const { barConfig } = await Themes[Css.currentTheme]();
567
+ const idModal = 'modal-public-profile';
568
+ const user = ElementsDefault.Data.user.main.model.user;
569
+
570
+ // Check if modal already exists
571
+ const existingModal = s(`.${idModal}`);
572
+ if (existingModal) {
573
+ const usernameFromPath = extractUsernameFromPath();
574
+ const queryParams = getQueryParams();
575
+ const cid = usernameFromPath || queryParams.cid || user.username || null;
576
+ if (cid) {
577
+ await PublicProfile.Update({
578
+ idModal: 'modal-public-profile',
579
+ user: { username: cid },
580
+ });
581
+ return;
582
+ }
583
+ }
584
+
585
+ await Modal.Render({
586
+ id: idModal,
587
+ route: 'u',
588
+ barConfig,
589
+ title: '',
590
+ // renderViewTitle({
591
+ // icon: html`<i class="fas fa-user-circle"></i>`,
592
+ // text: Translate.Render('public-profile'),
593
+ // }),
594
+ html: async () =>
595
+ await PublicProfile.Render({
596
+ idModal,
597
+ user,
598
+ }),
599
+ handleType: 'bar',
600
+ maximize: true,
601
+ mode: 'view',
602
+ slideMenu: 'modal-menu',
603
+ RouterInstance,
604
+ observer: true,
605
+ });
606
+ });
607
+
561
608
  EventsUI.onClick(`.main-btn-settings`, async () => {
562
609
  const { barConfig } = await Themes[Css.currentTheme]();
563
610
  await Modal.Render({
@@ -34,6 +34,7 @@ const RoutesDefault = () => {
34
34
  title: 'default-management',
35
35
  render: () => s(`.main-btn-default-management`).click(),
36
36
  },
37
+ '/u': { title: 'public-profile', render: () => s(`.main-btn-public-profile`).click() },
37
38
  '/404': { title: '404 Not Found', render: () => s(`.main-btn-404`).click() },
38
39
  '/500': { title: '500 Server Error', render: () => s(`.main-btn-500`).click() },
39
40
  };
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Core Service Client
3
+ * Provides methods to interact and build URLs for the core API endpoints.
4
+ * @module src/client/services/core/core.service.js
5
+ * @namespace CoreServiceClient
6
+ */
7
+
1
8
  import { Auth } from '../../components/core/Auth.js';
2
9
  import { loggerFactory } from '../../components/core/Logger.js';
3
10
  import { getProxyPath } from '../../components/core/Router.js';
@@ -9,8 +16,23 @@ logger.info('Load service');
9
16
  const endpoint = 'core';
10
17
 
11
18
  // https://developer.mozilla.org/en-US/docs/Web/API/AbortController
19
+
20
+ /**
21
+ * Gets the base host for API requests.
22
+ * Uses the apiBaseHost from renderPayload if available, otherwise falls back to location.host.
23
+ * @memberof CoreServiceClient
24
+ * @return {string} The base host string.
25
+ */
12
26
  const getBaseHost = () => (window.renderPayload?.apiBaseHost ? window.renderPayload.apiBaseHost : location.host);
13
27
 
28
+ /**
29
+ * Gets the base path for API requests.
30
+ * Constructs the path using proxyPath and apiBasePath from renderPayload or defaults.
31
+ * @memberof CoreServiceClient
32
+ * @param {Object} [options] - Options for constructing the base path.
33
+ * @param {string} [options.proxyPath] - Custom proxy path to use.
34
+ * @return {string} The constructed API base path.
35
+ */
14
36
  const getApiBasePath = (options) =>
15
37
  `${
16
38
  options?.proxyPath
@@ -22,18 +44,51 @@ const getApiBasePath = (options) =>
22
44
  : getProxyPath()
23
45
  }${window.renderPayload?.apiBasePath ? window.renderPayload.apiBasePath : 'api'}/`;
24
46
 
47
+ /**
48
+ * Constructs the full API base URL for making requests.
49
+ * Combines protocol, host, base path, endpoint, and optional ID.
50
+ * @memberof CoreServiceClient
51
+ * @param {Object} [options={}] - Options for constructing the URL.
52
+ * @param {string} [options.id=''] - Optional resource ID to append to the URL.
53
+ * @param {string} [options.endpoint=''] - API endpoint name.
54
+ * @param {string} [options.proxyPath=''] - Custom proxy path to use.
55
+ * @return {string} The full API base URL.
56
+ */
25
57
  const getApiBaseUrl = (options = { id: '', endpoint: '', proxyPath: '' }) =>
26
58
  `${location.protocol}//${getBaseHost()}${getApiBasePath(options)}${options?.endpoint ? options.endpoint : ''}${
27
59
  options?.id ? `/${options.id}` : ''
28
60
  }`;
29
61
 
30
- const getWsBasePath = () => (getProxyPath() !== '/' ? `${getProxyPath()}socket.io/` : undefined);
62
+ /**
63
+ * Gets the base path for WebSocket connections.
64
+ * Constructs the socket.io path using the proxy path.
65
+ * @memberof CoreServiceClient
66
+ * @return {string} The WebSocket base path.
67
+ */
68
+ const getWsBasePath = () => (getProxyPath() !== '/' ? `${getProxyPath()}socket.io/` : '/socket.io/');
31
69
 
70
+ /**
71
+ * Constructs the full WebSocket base URL for connections.
72
+ * Uses wss: for HTTPS and ws: for HTTP protocols.
73
+ * @memberof CoreServiceClient
74
+ * @param {Object} [options={}] - Options for constructing the WebSocket URL.
75
+ * @param {string} [options.id=''] - Optional resource ID to append to the URL.
76
+ * @param {string} [options.endpoint=''] - WebSocket endpoint name.
77
+ * @param {string} [options.wsBasePath=''] - Custom WebSocket base path to use.
78
+ * @return {string} The full WebSocket base URL.
79
+ */
32
80
  const getWsBaseUrl = (options = { id: '', endpoint: '', wsBasePath: '' }) =>
33
81
  `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${getBaseHost()}${
34
82
  options?.wsBasePath !== undefined ? options.wsBasePath : getWsBasePath()
35
83
  }${options?.endpoint ? options.endpoint : ''}${options?.id ? `/${options.id}` : ''}`;
36
84
 
85
+ /**
86
+ * Creates HTTP headers for API requests.
87
+ * Includes Authorization header with JWT token and Content-Type based on headerId.
88
+ * @memberof CoreServiceClient
89
+ * @param {string} [headerId=''] - Header type identifier. Use 'file' for file uploads (no Content-Type).
90
+ * @return {Object} Headers object for fetch requests.
91
+ */
37
92
  const headersFactory = (headerId = '') => {
38
93
  const headers = {
39
94
  Authorization: Auth.getJWT(),
@@ -48,12 +103,77 @@ const headersFactory = (headerId = '') => {
48
103
  }
49
104
  };
50
105
 
106
+ /**
107
+ * Prepares the request body payload for API requests.
108
+ * Returns FormData as-is, otherwise stringifies the body as JSON.
109
+ * @memberof CoreServiceClient
110
+ * @param {Object|FormData} body - The request body to process.
111
+ * @return {string|FormData} The processed payload ready for fetch.
112
+ */
51
113
  const payloadFactory = (body) => {
52
114
  if (body instanceof FormData) return body;
53
115
  return JSON.stringify(body);
54
116
  };
55
117
 
118
+ /**
119
+ * Builds a URL with query parameters for pagination, filtering, and sorting.
120
+ * Supports AG Grid filterModel/sortModel as well as legacy simple sort params.
121
+ * @memberof CoreServiceClient
122
+ * @param {string} baseUrl - The base API URL.
123
+ * @param {Object} [options={}] - Query options.
124
+ * @param {number} [options.page] - Page number for pagination.
125
+ * @param {number} [options.limit] - Items per page for pagination.
126
+ * @param {Object|string} [options.filterModel] - AG Grid filterModel (object or JSON string).
127
+ * @param {Array|string} [options.sortModel] - AG Grid sortModel (array or JSON string).
128
+ * @param {string} [options.sort] - Simple sort field (legacy).
129
+ * @param {string|boolean} [options.asc] - Simple sort direction (legacy).
130
+ * @param {string} [options.order] - Order string, e.g. "field1:asc,field2:desc" (legacy).
131
+ * @return {URL} The URL object with query parameters set.
132
+ */
133
+ const buildQueryUrl = (baseUrl, options = {}) => {
134
+ const url = new URL(baseUrl);
135
+ const { page, limit, filterModel, sortModel, sort, asc, order } = options;
136
+
137
+ // Add pagination params
138
+ if (page !== undefined) url.searchParams.set('page', page);
139
+ if (limit !== undefined) url.searchParams.set('limit', limit);
140
+
141
+ // Add filter model (AG Grid format) - send as JSON string
142
+ if (filterModel) {
143
+ const filterStr = typeof filterModel === 'string' ? filterModel : JSON.stringify(filterModel);
144
+ if (filterStr && filterStr !== '{}' && filterStr !== 'null') {
145
+ url.searchParams.set('filterModel', filterStr);
146
+ }
147
+ }
148
+
149
+ // Add sort model (AG Grid format) - send as JSON string
150
+ if (sortModel) {
151
+ const sortStr = typeof sortModel === 'string' ? sortModel : JSON.stringify(sortModel);
152
+ if (sortStr && sortStr !== '[]' && sortStr !== 'null') {
153
+ url.searchParams.set('sortModel', sortStr);
154
+ }
155
+ }
156
+
157
+ // Add simple sort params for backwards compatibility
158
+ if (sort) url.searchParams.set('sort', sort);
159
+ if (asc !== undefined) url.searchParams.set('asc', asc);
160
+ if (order) url.searchParams.set('order', order);
161
+
162
+ return url;
163
+ };
164
+
165
+ /**
166
+ * Core Service object providing CRUD operations for the core API endpoint.
167
+ * @memberof CoreServiceClient
168
+ */
56
169
  const CoreService = {
170
+ /**
171
+ * Performs a raw GET request to fetch content as text.
172
+ * @memberof CoreServiceClient.CoreService
173
+ * @param {Object} [options={}] - Request options.
174
+ * @param {string} [options.url=''] - The full URL to fetch.
175
+ * @return {Promise<string>} A promise that resolves with the response text.
176
+ */
57
177
  getRaw: (options = { url: '' }) =>
58
178
  new Promise((resolve, reject) =>
59
179
  fetch(options.url, {
@@ -71,6 +191,15 @@ const CoreService = {
71
191
  return reject(error);
72
192
  }),
73
193
  ),
194
+
195
+ /**
196
+ * Performs a POST request to create a new resource.
197
+ * @memberof CoreServiceClient.CoreService
198
+ * @param {Object} [options={}] - Request options.
199
+ * @param {string} [options.id=''] - Optional resource ID to append to the URL.
200
+ * @param {Object} [options.body={}] - The request body payload.
201
+ * @return {Promise<Object>} A promise that resolves with the JSON response.
202
+ */
74
203
  post: (options = { id: '', body: {} }) =>
75
204
  new Promise((resolve, reject) =>
76
205
  fetch(getApiBaseUrl({ id: options.id, endpoint }), {
@@ -91,6 +220,15 @@ const CoreService = {
91
220
  return reject(error);
92
221
  }),
93
222
  ),
223
+
224
+ /**
225
+ * Performs a PUT request to update an existing resource.
226
+ * @memberof CoreServiceClient.CoreService
227
+ * @param {Object} [options={}] - Request options.
228
+ * @param {string} [options.id=''] - The resource ID to update.
229
+ * @param {Object} [options.body={}] - The request body payload with updated data.
230
+ * @return {Promise<Object>} A promise that resolves with the JSON response.
231
+ */
94
232
  put: (options = { id: '', body: {} }) =>
95
233
  new Promise((resolve, reject) =>
96
234
  fetch(getApiBaseUrl({ id: options.id, endpoint }), {
@@ -111,6 +249,15 @@ const CoreService = {
111
249
  return reject(error);
112
250
  }),
113
251
  ),
252
+
253
+ /**
254
+ * Performs a GET request to retrieve a resource.
255
+ * @memberof CoreServiceClient.CoreService
256
+ * @param {Object} [options={}] - Request options.
257
+ * @param {string} [options.id=''] - Optional resource ID to retrieve.
258
+ * @param {Object} [options.body={}] - Unused, kept for API consistency.
259
+ * @return {Promise<Object>} A promise that resolves with the JSON response.
260
+ */
114
261
  get: (options = { id: '', body: {} }) =>
115
262
  new Promise((resolve, reject) =>
116
263
  fetch(getApiBaseUrl({ id: options.id, endpoint }), {
@@ -130,6 +277,15 @@ const CoreService = {
130
277
  return reject(error);
131
278
  }),
132
279
  ),
280
+
281
+ /**
282
+ * Performs a DELETE request to remove a resource.
283
+ * @memberof CoreServiceClient.CoreService
284
+ * @param {Object} [options={}] - Request options.
285
+ * @param {string} [options.id=''] - The resource ID to delete.
286
+ * @param {Object} [options.body={}] - Optional request body payload.
287
+ * @return {Promise<Object>} A promise that resolves with the JSON response.
288
+ */
133
289
  delete: (options = { id: '', body: {} }) =>
134
290
  new Promise((resolve, reject) =>
135
291
  fetch(getApiBaseUrl({ id: options.id, endpoint }), {
@@ -152,12 +308,18 @@ const CoreService = {
152
308
  ),
153
309
  };
154
310
 
311
+ /**
312
+ * Alias for getApiBaseUrl function.
313
+ * @memberof CoreServiceClient
314
+ * @type {Function}
315
+ */
155
316
  const ApiBase = getApiBaseUrl;
156
317
 
157
318
  export {
158
319
  CoreService,
159
320
  headersFactory,
160
321
  payloadFactory,
322
+ buildQueryUrl,
161
323
  getBaseHost,
162
324
  getApiBasePath,
163
325
  getApiBaseUrl,