@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
@@ -156,6 +156,15 @@ const FileExplorer = {
156
156
 
157
157
  EventsUI.onClick(`.btn-input-file-explorer`, async (e) => {
158
158
  e.preventDefault();
159
+
160
+ // Check authentication before upload
161
+ if (!Auth.getToken()) {
162
+ return NotificationManager.Push({
163
+ html: Translate.Render(`error-user-not-authenticated`),
164
+ status: 'error',
165
+ });
166
+ }
167
+
159
168
  const { errorMessage } = await validators();
160
169
  if (!formBodyFiles)
161
170
  return NotificationManager.Push({
@@ -166,6 +175,12 @@ const FileExplorer = {
166
175
  let fileData;
167
176
  {
168
177
  const { status, data } = await FileService.post({ body: formBodyFiles });
178
+ if (status === 'error' || !data) {
179
+ return NotificationManager.Push({
180
+ html: Translate.Render(`error-upload-file`),
181
+ status: 'error',
182
+ });
183
+ }
169
184
  fileData = data;
170
185
  }
171
186
  {
@@ -259,6 +274,8 @@ const FileExplorer = {
259
274
  // params.data._id
260
275
 
261
276
  this.eGui = document.createElement('div');
277
+ const isPublic = params.data.isPublic;
278
+ const toggleId = `toggle-public-${params.data._id}`;
262
279
  this.eGui.innerHTML = html`
263
280
  <div class="fl">
264
281
  ${await BtnIcon.Render({
@@ -281,6 +298,13 @@ const FileExplorer = {
281
298
  label: html`<i class="fas fa-copy"></i>`,
282
299
  type: 'button',
283
300
  })}
301
+ ${await BtnIcon.Render({
302
+ class: `in fll management-table-btn-mini ${toggleId}`,
303
+ label: isPublic
304
+ ? html`<i class="fas fa-globe" style="color: #4caf50;"></i>`
305
+ : html`<i class="fas fa-lock" style="color: #9e9e9e;"></i>`,
306
+ type: 'button',
307
+ })}
284
308
  </div>
285
309
  `;
286
310
 
@@ -315,13 +339,21 @@ const FileExplorer = {
315
339
 
316
340
  EventsUI.onClick(`.btn-file-download-${params.data._id}`, async (e) => {
317
341
  e.preventDefault();
318
- console.log(params);
319
- const {
320
- data: [file],
321
- status,
322
- } = await FileService.get({ id: params.data.fileId });
323
-
324
- downloadFile(new Blob([new Uint8Array(file.data.data)], { type: params.data.mimetype }), params.data.name);
342
+ try {
343
+ // Use FileService with blob/ prefix for centralized blob fetching
344
+ const { data: blobArray, status } = await FileService.get({ id: `blob/${params.data.fileId}` });
345
+ if (status === 'success' && blobArray && blobArray[0]) {
346
+ downloadFile(blobArray[0], params.data.name);
347
+ } else {
348
+ throw new Error('Failed to fetch file blob');
349
+ }
350
+ } catch (error) {
351
+ logger.error('Download failed:', error);
352
+ NotificationManager.Push({
353
+ html: 'Download failed',
354
+ status: 'error',
355
+ });
356
+ }
325
357
  });
326
358
  EventsUI.onClick(
327
359
  `.btn-file-delete-${params.data._id}`,
@@ -371,6 +403,80 @@ const FileExplorer = {
371
403
  },
372
404
  { context: 'modal' },
373
405
  );
406
+
407
+ // Toggle public/private status
408
+ EventsUI.onClick(
409
+ `.${toggleId}`,
410
+ async (e) => {
411
+ e.preventDefault();
412
+
413
+ // If document is currently private, show confirmation before making public
414
+ if (!params.data.isPublic) {
415
+ const confirmResult = await Modal.RenderConfirm({
416
+ html: async () => {
417
+ return html`
418
+ <div class="in section-mp" style="text-align: center">
419
+ ${Translate.Render('confirm-make-public')}
420
+ <br />
421
+ "${params.data.title}"
422
+ </div>
423
+ `;
424
+ },
425
+ id: `confirm-toggle-public-${params.data._id}`,
426
+ });
427
+ if (confirmResult.status !== 'confirm') return;
428
+ }
429
+
430
+ try {
431
+ const { data, status } = await DocumentService.patch({
432
+ id: params.data._id,
433
+ action: 'toggle-public',
434
+ });
435
+
436
+ if (status === 'success') {
437
+ // Update local data
438
+ params.data.isPublic = data.isPublic;
439
+
440
+ // Update documentInstance
441
+ const docIndex = documentInstance.findIndex((d) => d._id === params.data._id);
442
+ if (docIndex !== -1) {
443
+ documentInstance[docIndex].isPublic = data.isPublic;
444
+ }
445
+
446
+ // Update button icon
447
+ const btnElement = s(`.${toggleId}`);
448
+ if (btnElement) {
449
+ const iconElement = btnElement.querySelector('i');
450
+ if (iconElement) {
451
+ if (data.isPublic) {
452
+ iconElement.className = 'fas fa-globe';
453
+ iconElement.style.color = '#4caf50';
454
+ } else {
455
+ iconElement.className = 'fas fa-lock';
456
+ iconElement.style.color = '#9e9e9e';
457
+ }
458
+ }
459
+ }
460
+
461
+ NotificationManager.Push({
462
+ html: data.isPublic
463
+ ? Translate.Render('document-now-public')
464
+ : Translate.Render('document-now-private'),
465
+ status: 'success',
466
+ });
467
+ } else {
468
+ throw new Error('Failed to toggle public status');
469
+ }
470
+ } catch (error) {
471
+ logger.error('Toggle public failed:', error);
472
+ NotificationManager.Push({
473
+ html: Translate.Render('error-toggle-public'),
474
+ status: 'error',
475
+ });
476
+ }
477
+ },
478
+ { context: 'modal' },
479
+ );
374
480
  });
375
481
  }
376
482
 
@@ -618,7 +724,7 @@ const FileExplorer = {
618
724
  columnDefs: [
619
725
  { field: 'name', flex: 2, headerName: 'Name', cellRenderer: LoadFileNameRenderer },
620
726
  { field: 'mimetype', flex: 1, headerName: 'Type' },
621
- { headerName: '', width: 80, cellRenderer: LoadFileActionsRenderer },
727
+ { headerName: '', width: 120, cellRenderer: LoadFileActionsRenderer },
622
728
  ],
623
729
  },
624
730
  })}
@@ -680,6 +786,7 @@ const FileExplorer = {
680
786
  fileId: f.fileId._id,
681
787
  _id: f._id,
682
788
  title: f.title,
789
+ isPublic: f.isPublic || false,
683
790
  };
684
791
  });
685
792
  let documentId = document._id;
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Input component module for form controls and file handling utilities.
3
+ * Provides input rendering, file data conversion, and blob endpoint integration.
4
+ *
5
+ * @module src/client/components/core/Input.js
6
+ * @namespace InputClient
7
+ */
8
+
1
9
  import { AgGrid } from './AgGrid.js';
2
10
  import { BtnIcon } from './BtnIcon.js';
3
11
  import { isValidDate } from './CommonJs.js';
@@ -8,8 +16,26 @@ import { RichText } from './RichText.js';
8
16
  import { ToggleSwitch } from './ToggleSwitch.js';
9
17
  import { Translate } from './Translate.js';
10
18
  import { htmls, htmlStrSanitize, s } from './VanillaJs.js';
19
+ import { getApiBaseUrl } from '../../services/core/core.service.js';
20
+ import { FileService } from '../../services/file/file.service.js';
21
+
22
+ /**
23
+ * Logger instance for this module.
24
+ * @type {Function}
25
+ * @memberof InputClient
26
+ * @private
27
+ */
11
28
  const logger = loggerFactory(import.meta);
12
29
 
30
+ /**
31
+ * Creates a FormData object from file input event.
32
+ * Filters files by extension if provided.
33
+ * @function fileFormDataFactory
34
+ * @memberof InputClient
35
+ * @param {Event} e - The input change event containing files.
36
+ * @param {string[]} [extensions] - Optional array of allowed MIME types.
37
+ * @returns {FormData} FormData object containing the valid files.
38
+ */
13
39
  const fileFormDataFactory = (e, extensions) => {
14
40
  const form = new FormData();
15
41
  for (const keyFile of Object.keys(e.target.files)) {
@@ -22,17 +48,156 @@ const fileFormDataFactory = (e, extensions) => {
22
48
  return form;
23
49
  };
24
50
 
51
+ /**
52
+ * Convert file data to File object.
53
+ * Supports both legacy format (with buffer data) and new format (metadata only).
54
+ *
55
+ * Legacy format: `{ data: { data: [0, 1, 2, ...] }, mimetype: 'text/markdown', name: 'file.md' }`
56
+ * New format: `{ _id: '...', mimetype: 'text/markdown', name: 'file.md' }`
57
+ *
58
+ * @function getFileFromFileData
59
+ * @memberof InputClient
60
+ * @param {Object} fileData - File data object in legacy or new format.
61
+ * @param {Object} [fileData.data] - Legacy format data container.
62
+ * @param {Array<number>} [fileData.data.data] - Legacy format byte array.
63
+ * @param {string} [fileData._id] - New format file ID.
64
+ * @param {string} fileData.mimetype - MIME type of the file.
65
+ * @param {string} fileData.name - Name of the file.
66
+ * @returns {File|null} File object if legacy format, null if metadata-only or invalid.
67
+ */
25
68
  const getFileFromFileData = (fileData) => {
26
- const blob = new Blob([new Uint8Array(fileData.data.data)], { type: fileData.mimetype });
27
- return new File([blob], fileData.name, { type: fileData.mimetype });
69
+ if (!fileData) {
70
+ logger.error('getFileFromFileData: fileData is undefined');
71
+ return null;
72
+ }
73
+
74
+ // Check if this is legacy format with buffer data
75
+ if (fileData.data?.data) {
76
+ try {
77
+ const blob = new Blob([new Uint8Array(fileData.data.data)], { type: fileData.mimetype });
78
+ return new File([blob], fileData.name, { type: fileData.mimetype });
79
+ } catch (error) {
80
+ logger.error('Error creating File from legacy buffer data:', error);
81
+ return null;
82
+ }
83
+ }
84
+
85
+ // New format - metadata only, cannot create File without content
86
+ // Return null and let caller fetch from blob endpoint if needed
87
+ if (fileData._id && !fileData.data?.data) {
88
+ logger.warn(
89
+ 'getFileFromFileData: File is metadata-only, cannot create File object without content. File ID:',
90
+ fileData._id,
91
+ );
92
+ return null;
93
+ }
94
+
95
+ logger.error('getFileFromFileData: Invalid file data structure', fileData);
96
+ return null;
97
+ };
98
+
99
+ /**
100
+ * Fetch file content from blob endpoint and create File object.
101
+ * Used for metadata-only format files during edit mode.
102
+ * Uses FileService with blob/ prefix for centralized blob fetching.
103
+ *
104
+ * @async
105
+ * @function getFileFromBlobEndpoint
106
+ * @memberof InputClient
107
+ * @param {Object} fileData - File metadata object with _id.
108
+ * @param {string} fileData._id - File ID for blob endpoint lookup.
109
+ * @param {string} [fileData.name] - Optional file name.
110
+ * @param {string} [fileData.mimetype] - Optional MIME type.
111
+ * @returns {Promise<File|null>} File object from blob endpoint, or null on error.
112
+ */
113
+ const getFileFromBlobEndpoint = async (fileData) => {
114
+ if (!fileData || !fileData._id) {
115
+ return null;
116
+ }
117
+
118
+ try {
119
+ const { data: blobArray, status } = await FileService.get({ id: `blob/${fileData._id}` });
120
+ if (status !== 'success' || !blobArray || !blobArray[0]) {
121
+ logger.error('Failed to fetch file from blob endpoint');
122
+ return null;
123
+ }
124
+
125
+ const blob = blobArray[0];
126
+ return new File([blob], fileData.name || 'file', { type: fileData.mimetype || blob.type });
127
+ } catch (error) {
128
+ logger.error('Error fetching file from blob endpoint:', error);
129
+ return null;
130
+ }
28
131
  };
29
132
 
133
+ /**
134
+ * Get image/file source URL from file data.
135
+ * Supports both legacy format (with buffer) and new format (metadata only).
136
+ * For new format, returns blob endpoint URL.
137
+ *
138
+ * @function getSrcFromFileData
139
+ * @memberof InputClient
140
+ * @param {Object} fileData - File data object in legacy or new format.
141
+ * @param {Object} [fileData.data] - Legacy format data container.
142
+ * @param {Array<number>} [fileData.data.data] - Legacy format byte array.
143
+ * @param {string} [fileData._id] - New format file ID for blob endpoint.
144
+ * @param {string} fileData.mimetype - MIME type of the file.
145
+ * @returns {string|null} Object URL for legacy format, blob endpoint URL for new format, or null on error.
146
+ */
30
147
  const getSrcFromFileData = (fileData) => {
31
- const file = getFileFromFileData(fileData);
32
- return URL.createObjectURL(file);
148
+ if (!fileData) {
149
+ logger.error('getSrcFromFileData: fileData is undefined');
150
+ return null;
151
+ }
152
+
153
+ // Legacy format with buffer data - create object URL
154
+ if (fileData.data?.data) {
155
+ try {
156
+ const file = getFileFromFileData(fileData);
157
+ if (file) {
158
+ return URL.createObjectURL(file);
159
+ }
160
+ } catch (error) {
161
+ logger.error('Error getting src from legacy buffer data:', error);
162
+ }
163
+ }
164
+
165
+ // New format - use blob endpoint
166
+ if (fileData._id) {
167
+ try {
168
+ return getApiBaseUrl({ id: fileData._id, endpoint: 'file/blob' });
169
+ } catch (error) {
170
+ logger.error('Error generating blob URL:', error);
171
+ return null;
172
+ }
173
+ }
174
+
175
+ logger.error('getSrcFromFileData: Cannot generate src, invalid file data:', fileData);
176
+ return null;
33
177
  };
34
178
 
179
+ /**
180
+ * Input component for rendering various form input types.
181
+ * Supports text, password, file, color, date, dropdown, toggle, rich text, and grid inputs.
182
+ * @namespace InputClient.Input
183
+ * @memberof InputClient
184
+ */
35
185
  const Input = {
186
+ /**
187
+ * Renders an input element based on the provided options.
188
+ * @async
189
+ * @function Render
190
+ * @memberof InputClient.Input
191
+ * @param {Object} options - Input configuration options.
192
+ * @param {string} options.id - Unique identifier for the input.
193
+ * @param {string} [options.type] - Input type (text, password, file, color, datetime-local, etc.).
194
+ * @param {string} [options.placeholder] - Placeholder text.
195
+ * @param {string} [options.label] - Label text for the input.
196
+ * @param {string} [options.containerClass] - CSS class for the container.
197
+ * @param {string} [options.inputClass] - CSS class for the input element.
198
+ * @param {boolean} [options.disabled] - Whether the input is disabled.
199
+ * @returns {Promise<string>} HTML string for the input component.
200
+ */
36
201
  Render: async function (options) {
37
202
  const { id } = options;
38
203
  options?.placeholder
@@ -184,8 +349,8 @@ const Input = {
184
349
  }
185
350
  return obj;
186
351
  },
187
- setValues: function (formData, obj, originObj, fileObj) {
188
- setTimeout(() => {
352
+ setValues: async function (formData, obj, originObj, fileObj) {
353
+ setTimeout(async () => {
189
354
  for (const inputData of formData) {
190
355
  if (!s(`.${inputData.id}`)) continue;
191
356
 
@@ -194,11 +359,31 @@ const Input = {
194
359
  if (fileObj && fileObj[inputData.model] && s(`.${inputData.id}`)) {
195
360
  const dataTransfer = new DataTransfer();
196
361
 
197
- if (fileObj[inputData.model].fileBlob)
198
- dataTransfer.items.add(getFileFromFileData(fileObj[inputData.model].fileBlob));
362
+ if (fileObj[inputData.model].fileBlob) {
363
+ let fileBlobData = getFileFromFileData(fileObj[inputData.model].fileBlob);
364
+
365
+ // If fileBlob is metadata-only, try to fetch from blob endpoint
366
+ if (!fileBlobData && fileObj[inputData.model].fileBlob?._id) {
367
+ fileBlobData = await getFileFromBlobEndpoint(fileObj[inputData.model].fileBlob);
368
+ }
369
+
370
+ if (fileBlobData) {
371
+ dataTransfer.items.add(fileBlobData);
372
+ }
373
+ }
199
374
 
200
- if (fileObj[inputData.model].mdBlob)
201
- dataTransfer.items.add(getFileFromFileData(fileObj[inputData.model].mdBlob));
375
+ if (fileObj[inputData.model].mdBlob) {
376
+ let mdBlobData = getFileFromFileData(fileObj[inputData.model].mdBlob);
377
+
378
+ // If mdBlob is metadata-only, try to fetch from blob endpoint
379
+ if (!mdBlobData && fileObj[inputData.model].mdBlob?._id) {
380
+ mdBlobData = await getFileFromBlobEndpoint(fileObj[inputData.model].mdBlob);
381
+ }
382
+
383
+ if (mdBlobData) {
384
+ dataTransfer.items.add(mdBlobData);
385
+ }
386
+ }
202
387
 
203
388
  if (dataTransfer.files.length) {
204
389
  s(`.${inputData.id}`).files = dataTransfer.files;
@@ -384,4 +569,12 @@ function isTextInputFocused() {
384
569
  return active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA');
385
570
  }
386
571
 
387
- export { Input, InputFile, fileFormDataFactory, getSrcFromFileData, getFileFromFileData, isTextInputFocused };
572
+ export {
573
+ Input,
574
+ InputFile,
575
+ fileFormDataFactory,
576
+ getSrcFromFileData,
577
+ getFileFromFileData,
578
+ getFileFromBlobEndpoint,
579
+ isTextInputFocused,
580
+ };
@@ -1,16 +1,19 @@
1
- import { CoreService } from '../../services/core/core.service.js';
1
+ import { CoreService, getApiBaseUrl } from '../../services/core/core.service.js';
2
2
  import { FileService } from '../../services/file/file.service.js';
3
3
  import { UserService } from '../../services/user/user.service.js';
4
4
  import { Auth } from './Auth.js';
5
5
  import { BtnIcon } from './BtnIcon.js';
6
6
  import { EventsUI } from './EventsUI.js';
7
7
  import { Input } from './Input.js';
8
+ import { loggerFactory } from './Logger.js';
8
9
  import { NotificationManager } from './NotificationManager.js';
9
10
  import { Translate } from './Translate.js';
10
11
  import { Validator } from './Validator.js';
11
12
  import { htmls, s } from './VanillaJs.js';
12
13
  import { Webhook } from './Webhook.js';
13
14
 
15
+ const logger = loggerFactory(import.meta);
16
+
14
17
  const LogIn = {
15
18
  Scope: {
16
19
  user: {
@@ -24,6 +27,8 @@ const LogIn = {
24
27
  Event: {},
25
28
  Trigger: async function (options) {
26
29
  const { user } = options;
30
+ if (user) this.Scope.user.main.model.user = { ...this.Scope.user.main.model.user, ...user };
31
+
27
32
  for (const eventKey of Object.keys(this.Event)) await this.Event[eventKey](options);
28
33
  if (!user || user.role === 'guest') return;
29
34
  await Webhook.register({ user });
@@ -52,26 +57,40 @@ const LogIn = {
52
57
  </style>`,
53
58
  );
54
59
  if (!this.Scope.user.main.model.user.profileImage) {
55
- const resultFile = await FileService.get({ id: user.profileImageId });
56
- if (resultFile && resultFile.status === 'success' && resultFile.data[0]) {
57
- const imageData = resultFile.data[0];
58
-
59
- const imageBlob = new Blob([new Uint8Array(imageData.data.data)], { type: imageData.mimetype });
60
-
61
- const imageFile = new File([imageBlob], imageData.name, { type: imageData.mimetype });
60
+ // Try to load profile image only if profileImageId exists
61
+ if (!this.Scope.user.main.model.user.profileImage && user?.profileImageId) {
62
+ try {
63
+ const resultFile = await FileService.get({ id: user.profileImageId });
64
+ if (resultFile && resultFile.status === 'success' && resultFile.data[0]) {
65
+ const imageData = resultFile.data[0];
66
+ let imageSrc = null;
62
67
 
63
- const imageSrc = URL.createObjectURL(imageFile);
68
+ try {
69
+ // Handle new metadata-only format
70
+ if (!imageData.data?.data && imageData._id) {
71
+ imageSrc = getApiBaseUrl({ id: imageData._id, endpoint: 'file/blob' });
72
+ }
73
+ // Handle legacy format with buffer data
74
+ else if (imageData.data?.data) {
75
+ const imageBlob = new Blob([new Uint8Array(imageData.data.data)], { type: imageData.mimetype });
76
+ const imageFile = new File([imageBlob], imageData.name, { type: imageData.mimetype });
77
+ imageSrc = URL.createObjectURL(imageFile);
78
+ }
64
79
 
65
- // const rawSvg = await CoreService.getRaw({ url: imageSrc });
66
- // rawSvg = rawSvg.replace(`<svg`, `<svg class="abs account-profile-image" `).replace(`#5f5f5f`, `#ffffffc8`);
67
-
68
- this.Scope.user.main.model.user.profileImage = {
69
- resultFile,
70
- imageData,
71
- imageBlob,
72
- imageFile,
73
- imageSrc,
74
- };
80
+ if (imageSrc) {
81
+ this.Scope.user.main.model.user.profileImage = {
82
+ resultFile,
83
+ imageData,
84
+ imageSrc,
85
+ };
86
+ }
87
+ } catch (error) {
88
+ logger.warn('Error processing profile image:', error);
89
+ }
90
+ }
91
+ } catch (error) {
92
+ logger.warn('Error fetching profile image:', error);
93
+ }
75
94
  }
76
95
  htmls(
77
96
  `.action-btn-profile-log-in-render`,
@@ -80,7 +99,10 @@ const LogIn = {
80
99
  class="abs center top-box-profile-img"
81
100
  ${this.Scope.user.main.model.user.profileImage
82
101
  ? `src="${this.Scope.user.main.model.user.profileImage.imageSrc}"`
83
- : ``}
102
+ : `src="${getApiBaseUrl({
103
+ id: 'assets/avatar',
104
+ endpoint: 'user',
105
+ })}"`}
84
106
  />
85
107
  </div>`,
86
108
  );