@flusys/nestjs-storage 9.1.1 → 9.2.0

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 (38) hide show
  1. package/README.md +13 -0
  2. package/controllers/file-manager.controller.d.ts +1 -0
  3. package/controllers/folder.controller.d.ts +1 -0
  4. package/controllers/storage-config.controller.d.ts +1 -0
  5. package/fesm/493.js +416 -251
  6. package/fesm/537.js +25 -21
  7. package/fesm/562.js +184 -172
  8. package/fesm/chunks/190.js +6 -6
  9. package/fesm/chunks/252.js +1 -1
  10. package/fesm/chunks/327.js +1 -1
  11. package/fesm/chunks/385.js +1 -1
  12. package/fesm/chunks/591.js +11 -11
  13. package/fesm/chunks/746.js +1 -1
  14. package/fesm/chunks/798.js +1 -1
  15. package/fesm/chunks/918.js +19 -7
  16. package/fesm/config/index.js +8 -8
  17. package/fesm/controllers/index.js +1 -1
  18. package/fesm/docs/index.js +11 -15
  19. package/fesm/dtos/index.js +24 -20
  20. package/fesm/entities/index.js +6 -6
  21. package/fesm/enums/index.js +4 -4
  22. package/fesm/index.js +30 -34
  23. package/fesm/interfaces/index.js +2 -2
  24. package/fesm/modules/index.js +1 -1
  25. package/fesm/providers/index.js +123 -129
  26. package/fesm/services/index.js +1 -1
  27. package/fesm/utils/index.js +88 -84
  28. package/middlewares/file-serve.middleware.d.ts +3 -1
  29. package/package.json +3 -3
  30. package/providers/local-provider.d.ts +2 -0
  31. package/providers/sftp-provider.optional.d.ts +1 -0
  32. package/providers/storage-factory.service.d.ts +4 -2
  33. package/services/file-manager.service.d.ts +5 -6
  34. package/services/folder.service.d.ts +5 -5
  35. package/services/storage-provider-config.service.d.ts +5 -2
  36. package/services/upload.service.d.ts +3 -1
  37. package/utils/company-ownership.util.d.ts +3 -0
  38. package/utils/file-validator.util.d.ts +1 -0
package/fesm/562.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as __rspack_external_sharp from "sharp";
2
- import * as __rspack_external_path from "path";
3
2
  import * as __rspack_external_uuid from "uuid";
4
3
  import * as __rspack_external_fs from "fs";
5
4
  import * as __rspack_external_mime_types_1e2a4730 from "mime-types";
@@ -7,7 +6,7 @@ import * as __rspack_external_fs_promises_e1851c9c from "fs/promises";
7
6
  export const __rspack_esm_id = 562;
8
7
  export const __rspack_esm_ids = [562];
9
8
  export const __webpack_modules__ = {
10
- 968(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
9
+ 5968(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
11
10
 
12
11
  // EXPORTS
13
12
  __webpack_require__.d(__webpack_exports__, {
@@ -15,17 +14,17 @@ __webpack_require__.d(__webpack_exports__, {
15
14
  });
16
15
 
17
16
  // EXTERNAL MODULE: external "@nestjs/common"
18
- var common_ = __webpack_require__(157);
17
+ var common_ = __webpack_require__(6157);
19
18
  ;// CONCATENATED MODULE: external "fs"
20
19
 
21
20
  // EXTERNAL MODULE: external "path"
22
- var external_path_ = __webpack_require__(521);
21
+ var external_path_ = __webpack_require__(2521);
23
22
  ;// CONCATENATED MODULE: external "mime-types"
24
23
 
25
24
  // EXTERNAL MODULE: ./projects/nestjs-storage/src/config/message-keys.ts
26
- var message_keys = __webpack_require__(450);
25
+ var message_keys = __webpack_require__(5450);
27
26
  // EXTERNAL MODULE: ./projects/nestjs-storage/src/services/upload.service.ts + 1 modules
28
- var upload_service = __webpack_require__(18);
27
+ var upload_service = __webpack_require__(5018);
29
28
  ;// CONCATENATED MODULE: ./projects/nestjs-storage/src/middlewares/file-serve.middleware.ts
30
29
  function _ts_decorate(decorators, target, key, desc) {
31
30
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -65,6 +64,15 @@ function _ts_param(paramIndex, decorator) {
65
64
  'application/json',
66
65
  'application/xml'
67
66
  ];
67
+ /** Types a browser would run script from if rendered inline on the API origin */ const ACTIVE_CONTENT_TYPES = [
68
+ 'text/html',
69
+ 'application/xhtml+xml',
70
+ 'image/svg+xml',
71
+ 'text/xml',
72
+ 'application/xml',
73
+ 'text/javascript',
74
+ 'application/javascript'
75
+ ];
68
76
  class FileServeMiddleware {
69
77
  uploadService;
70
78
  uploadDir = process.cwd();
@@ -107,27 +115,23 @@ class FileServeMiddleware {
107
115
  }
108
116
  return normalizedPath;
109
117
  }
110
- /** Resolve file path using multiple fallback strategies */ async resolveFilePath(normalizedPath) {
118
+ /**
119
+ * Resolve file path using multiple fallback strategies. Only files inside the local storage
120
+ * base directory are served - never the rest of the working directory (.env, sources, dist).
121
+ */ async resolveFilePath(normalizedPath) {
111
122
  // Strategy 1: Path relative to CWD (new format: key includes basePath)
112
- let fullPath = (0,external_path_.join)(this.uploadDir, normalizedPath);
123
+ const fullPath = (0,external_path_.join)(this.uploadDir, normalizedPath);
113
124
  const resolvedPath = (0,external_path_.resolve)(fullPath);
114
125
  // Prevent path traversal attacks (boundary-safe: avoids matching sibling dirs like uploadDir + '-evil')
115
- if (resolvedPath !== this.uploadDir && !resolvedPath.startsWith(this.uploadDir + external_path_.sep)) {
126
+ if (!this.isWithin(resolvedPath, this.uploadDir)) {
116
127
  throw new common_.BadRequestException({
117
128
  message: 'Invalid file path',
118
129
  messageKey: message_keys/* .UPLOAD_MESSAGES.INVALID_FILE_PATH */.dS.INVALID_FILE_PATH
119
130
  });
120
131
  }
121
- if ((0,__rspack_external_fs.existsSync)(fullPath)) {
122
- return fullPath;
123
- }
124
- // Try fallback strategies with basePath
125
- const basePath = await this.uploadService.getLocalStorageBasePath();
126
- if (basePath) {
127
- const fallbackPath = this.tryFallbackPaths(normalizedPath, basePath);
128
- if (fallbackPath) {
129
- return fallbackPath;
130
- }
132
+ for (const basePath of (await this.uploadService.getLocalStorageBasePaths())){
133
+ const found = this.findInStorageRoot(normalizedPath, fullPath, basePath);
134
+ if (found) return found;
131
135
  }
132
136
  throw new common_.NotFoundException({
133
137
  message: `File not found: ${normalizedPath}`,
@@ -137,20 +141,32 @@ class FileServeMiddleware {
137
141
  }
138
142
  });
139
143
  }
140
- /** Try basePath-based fallback strategies */ tryFallbackPaths(normalizedPath, basePath) {
141
- const normalizedBasePath = basePath.replace(/^\.\//, '').replace(/\/$/, '');
142
- const pathStartsWithBase = normalizedPath.startsWith(normalizedBasePath + '/') || normalizedPath.startsWith(normalizedBasePath);
143
- if (pathStartsWithBase) {
144
+ findInStorageRoot(normalizedPath, fullPath, basePath) {
145
+ const storageRoot = (0,external_path_.resolve)(basePath);
146
+ // A base path at or above the working directory would expose the whole application
147
+ if (this.isWithin(this.uploadDir, storageRoot)) return null;
148
+ const candidates = [
149
+ fullPath,
150
+ ...this.fallbackPaths(normalizedPath, basePath)
151
+ ];
152
+ return candidates.find((candidate)=>this.isWithin((0,external_path_.resolve)(candidate), storageRoot) && (0,__rspack_external_fs.existsSync)(candidate)) ?? null;
153
+ }
154
+ isWithin(target, root) {
155
+ return target === root || target.startsWith(root + external_path_.sep);
156
+ }
157
+ /** basePath-based fallback strategies */ fallbackPaths(normalizedPath, basePath) {
158
+ const normalizedBasePath = (0,external_path_.relative)(this.uploadDir, (0,external_path_.resolve)(basePath)).split(external_path_.sep).join('/');
159
+ if (normalizedPath.startsWith(normalizedBasePath)) {
144
160
  // Strategy 2: basePath + remaining path
145
161
  const remainingPath = normalizedPath.substring(normalizedBasePath.length).replace(/^\//, '');
146
- const fallbackPath = (0,external_path_.join)(basePath, remainingPath);
147
- if ((0,__rspack_external_fs.existsSync)(fallbackPath)) return fallbackPath;
148
- } else {
149
- // Strategy 3: Old format - prepend basePath
150
- const fallbackPath = (0,external_path_.join)(basePath, normalizedPath);
151
- if ((0,__rspack_external_fs.existsSync)(fallbackPath)) return fallbackPath;
162
+ return [
163
+ (0,external_path_.join)(basePath, remainingPath)
164
+ ];
152
165
  }
153
- return null;
166
+ // Strategy 3: Old format - prepend basePath
167
+ return [
168
+ (0,external_path_.join)(basePath, normalizedPath)
169
+ ];
154
170
  }
155
171
  /** Get MIME type with SVG special handling */ getMimeType(fullPath) {
156
172
  if (fullPath.toLowerCase().endsWith('.svg')) {
@@ -159,14 +175,15 @@ class FileServeMiddleware {
159
175
  return __rspack_external_mime_types_1e2a4730.lookup(fullPath) || 'application/octet-stream';
160
176
  }
161
177
  /** Set response headers for file serving */ setResponseHeaders(res, size, mimeType, normalizedPath) {
162
- const isViewable = VIEWABLE_TYPE_PREFIXES.some((type)=>mimeType.startsWith(type));
178
+ const isViewable = !ACTIVE_CONTENT_TYPES.includes(mimeType) && VIEWABLE_TYPE_PREFIXES.some((type)=>mimeType.startsWith(type));
163
179
  const filename = normalizedPath.split('/').pop() || 'download';
164
180
  const contentDisposition = isViewable ? 'inline' : `attachment; filename="${encodeURIComponent(filename)}"`;
165
181
  res.set({
166
182
  'Content-Type': mimeType,
167
183
  'Content-Length': size,
168
184
  'Content-Disposition': contentDisposition,
169
- 'Cache-Control': 'public, max-age=3600',
185
+ // The route cannot tell private files apart, so no shared proxy/CDN may keep a copy
186
+ 'Cache-Control': 'private, max-age=3600',
170
187
  'Accept-Ranges': 'bytes',
171
188
  'Cross-Origin-Resource-Policy': 'cross-origin',
172
189
  'Access-Control-Allow-Origin': '*',
@@ -223,7 +240,7 @@ FileServeMiddleware = _ts_decorate([
223
240
 
224
241
 
225
242
  },
226
- 329(module, __webpack_exports__, __webpack_require__) {
243
+ 2329(module, __webpack_exports__, __webpack_require__) {
227
244
  __webpack_require__.a(module, async function (__rspack_load_async_deps, __rspack_async_done) { try {
228
245
  /* import */ var _storage_module_js__rspack_import_0 = __webpack_require__(624);
229
246
  var __rspack_async_deps = __rspack_load_async_deps([_storage_module_js__rspack_import_0]);
@@ -239,16 +256,16 @@ __rspack_async_done();
239
256
  },
240
257
  624(module, __webpack_exports__, __webpack_require__) {
241
258
  __webpack_require__.a(module, async function (__rspack_load_async_deps, __rspack_async_done) { try {
242
- /* import */ var _flusys_nestjs_shared_modules__rspack_import_0 = __webpack_require__(236);
243
- /* import */ var _nestjs_common__rspack_import_1 = __webpack_require__(157);
244
- /* import */ var _middlewares_index_js__rspack_import_2 = __webpack_require__(968);
245
- /* import */ var _services_index_js__rspack_import_3 = __webpack_require__(148);
246
- /* import */ var _config_storage_constants_js__rspack_import_9 = __webpack_require__(188);
247
- /* import */ var _controllers_index_js__rspack_import_4 = __webpack_require__(488);
248
- /* import */ var _enums_file_location_enum_js__rspack_import_8 = __webpack_require__(733);
249
- /* import */ var _providers_index_js__rspack_import_5 = __webpack_require__(922);
259
+ /* import */ var _flusys_nestjs_shared_modules__rspack_import_0 = __webpack_require__(1236);
260
+ /* import */ var _nestjs_common__rspack_import_1 = __webpack_require__(6157);
261
+ /* import */ var _middlewares_index_js__rspack_import_2 = __webpack_require__(5968);
262
+ /* import */ var _services_index_js__rspack_import_3 = __webpack_require__(4148);
263
+ /* import */ var _config_storage_constants_js__rspack_import_9 = __webpack_require__(9188);
264
+ /* import */ var _controllers_index_js__rspack_import_4 = __webpack_require__(5488);
265
+ /* import */ var _enums_file_location_enum_js__rspack_import_8 = __webpack_require__(2733);
266
+ /* import */ var _providers_index_js__rspack_import_5 = __webpack_require__(1922);
250
267
  /* import */ var _providers_local_provider_js__rspack_import_6 = __webpack_require__(632);
251
- /* import */ var _services_storage_provider_config_service_js__rspack_import_7 = __webpack_require__(623);
268
+ /* import */ var _services_storage_provider_config_service_js__rspack_import_7 = __webpack_require__(8623);
252
269
  function _ts_decorate(decorators, target, key, desc) {
253
270
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
254
271
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -306,9 +323,9 @@ async function registerOptionalProvider(location, name, dep, loader) {
306
323
  logger.debug(`${name} not available (install ${dep} to enable)`);
307
324
  }
308
325
  }
309
- await registerOptionalProvider(_enums_file_location_enum_js__rspack_import_8/* .FileLocationEnum.AWS */.N.AWS, 'S3Provider', '@aws-sdk/client-s3', ()=>__webpack_require__.e(/* import() */ 591).then(__webpack_require__.bind(__webpack_require__, 970)));
310
- await registerOptionalProvider(_enums_file_location_enum_js__rspack_import_8/* .FileLocationEnum.AZURE */.N.AZURE, 'AzureProvider', '@azure/storage-blob', ()=>__webpack_require__.e(/* import() */ 190).then(__webpack_require__.bind(__webpack_require__, 269)));
311
- await registerOptionalProvider(_enums_file_location_enum_js__rspack_import_8/* .FileLocationEnum.SFTP */.N.SFTP, 'SftpProvider', 'ssh2-sftp-client', ()=>__webpack_require__.e(/* import() */ 918).then(__webpack_require__.bind(__webpack_require__, 5)));
326
+ await registerOptionalProvider(_enums_file_location_enum_js__rspack_import_8/* .FileLocationEnum.AWS */.N.AWS, 'S3Provider', '@aws-sdk/client-s3', ()=>__webpack_require__.e(/* import() */ 591).then(__webpack_require__.bind(__webpack_require__, 9970)));
327
+ await registerOptionalProvider(_enums_file_location_enum_js__rspack_import_8/* .FileLocationEnum.AZURE */.N.AZURE, 'AzureProvider', '@azure/storage-blob', ()=>__webpack_require__.e(/* import() */ 190).then(__webpack_require__.bind(__webpack_require__, 7269)));
328
+ await registerOptionalProvider(_enums_file_location_enum_js__rspack_import_8/* .FileLocationEnum.SFTP */.N.SFTP, 'SftpProvider', 'ssh2-sftp-client', ()=>__webpack_require__.e(/* import() */ 918).then(__webpack_require__.bind(__webpack_require__, 1005)));
312
329
  class StorageModule {
313
330
  configure(consumer) {
314
331
  consumer.apply(_middlewares_index_js__rspack_import_2/* .FileServeMiddleware */.u).forRoutes({
@@ -409,9 +426,9 @@ __rspack_async_done();
409
426
  } catch(e) { __rspack_async_done(e); } }, 1);
410
427
 
411
428
  },
412
- 922(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
413
- /* import */ var _storage_factory_service_js__rspack_import_0 = __webpack_require__(192);
414
- /* import */ var _storage_provider_registry_js__rspack_import_1 = __webpack_require__(264);
429
+ 1922(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
430
+ /* import */ var _storage_factory_service_js__rspack_import_0 = __webpack_require__(2192);
431
+ /* import */ var _storage_provider_registry_js__rspack_import_1 = __webpack_require__(9264);
415
432
  /* import */ var _local_provider_js__rspack_import_2 = __webpack_require__(632);
416
433
  // Core exports
417
434
 
@@ -439,15 +456,15 @@ __webpack_require__.d(__webpack_exports__, {
439
456
  });
440
457
 
441
458
  // EXTERNAL MODULE: external "@nestjs/common"
442
- var common_ = __webpack_require__(157);
459
+ var common_ = __webpack_require__(6157);
443
460
  // EXTERNAL MODULE: external "@flusys/nestjs-shared/constants"
444
- var constants_ = __webpack_require__(140);
461
+ var constants_ = __webpack_require__(9140);
445
462
  // EXTERNAL MODULE: ./projects/nestjs-storage/src/utils/image-compressor.util.ts + 1 modules
446
- var image_compressor_util = __webpack_require__(872);
463
+ var image_compressor_util = __webpack_require__(1872);
447
464
  ;// CONCATENATED MODULE: external "fs/promises"
448
465
 
449
466
  // EXTERNAL MODULE: external "path"
450
- var external_path_ = __webpack_require__(521);
467
+ var external_path_ = __webpack_require__(2521);
451
468
  // EXTERNAL MODULE: external "uuid"
452
469
  var external_uuid_ = __webpack_require__(37);
453
470
  ;// CONCATENATED MODULE: ./projects/nestjs-storage/src/providers/local-provider.ts
@@ -475,20 +492,26 @@ var external_uuid_ = __webpack_require__(37);
475
492
  basePath = '';
476
493
  baseUrl = '';
477
494
  relativeBasePath = '';
495
+ isWithinBase(targetPath) {
496
+ const normalizedBasePath = external_path_.resolve(this.basePath);
497
+ const normalizedTargetPath = external_path_.resolve(targetPath);
498
+ return normalizedTargetPath.startsWith(normalizedBasePath + external_path_.sep) || normalizedTargetPath === normalizedBasePath;
499
+ }
478
500
  /**
479
501
  * SECURITY: Validates that a target path does not escape the base directory
480
502
  * Prevents path traversal attacks using ../ sequences
481
503
  * @throws Error if path traversal is detected
482
504
  */ validatePathWithinBase(targetPath) {
483
- const normalizedBasePath = external_path_.resolve(this.basePath);
484
- const normalizedTargetPath = external_path_.resolve(targetPath);
485
- if (!normalizedTargetPath.startsWith(normalizedBasePath + external_path_.sep) && normalizedTargetPath !== normalizedBasePath) {
486
- throw new common_.BadRequestException({
487
- message: 'Invalid path: Path traversal attempt detected',
488
- messageKey: constants_.SYSTEM_MESSAGES.PATH_TRAVERSAL_DETECTED
489
- });
505
+ if (!this.isWithinBase(targetPath)) {
506
+ this.throwTraversal();
490
507
  }
491
508
  }
509
+ throwTraversal() {
510
+ throw new common_.BadRequestException({
511
+ message: 'Invalid path: Path traversal attempt detected',
512
+ messageKey: constants_.SYSTEM_MESSAGES.PATH_TRAVERSAL_DETECTED
513
+ });
514
+ }
492
515
  /**
493
516
  * SECURITY: Validates file key format to prevent malicious input
494
517
  * @throws Error if key contains suspicious patterns
@@ -563,36 +586,27 @@ var external_uuid_ = __webpack_require__(37);
563
586
  return Promise.all(files.map((file)=>this.uploadFile(file, options)));
564
587
  }
565
588
  async deleteFile(key) {
566
- try {
567
- // SECURITY: Validate key format first
568
- this.validateKeyFormat(key);
569
- // Key now includes the basePath, resolve from cwd
570
- let filePath = external_path_.resolve(key);
571
- // SECURITY: Validate resolved path is within base directory
572
- this.validatePathWithinBase(filePath);
573
- // Check if file exists at the resolved path
589
+ // SECURITY: Validate key format first
590
+ this.validateKeyFormat(key);
591
+ // New keys include the basePath and resolve from cwd; old keys are relative to basePath.
592
+ // Only candidates inside the base directory are ever touched.
593
+ const candidates = [
594
+ external_path_.resolve(key),
595
+ external_path_.join(this.basePath, key)
596
+ ].filter((candidate)=>this.isWithinBase(candidate) && external_path_.resolve(candidate) !== this.basePath);
597
+ if (!candidates.length) this.throwTraversal();
598
+ for (const candidate of candidates){
574
599
  try {
575
- await __rspack_external_fs_promises_e1851c9c.access(filePath);
600
+ await __rspack_external_fs_promises_e1851c9c.access(candidate);
576
601
  } catch {
577
- // Fallback: try with basePath prefix (for old keys without basePath)
578
- const fallbackPath = external_path_.join(this.basePath, key);
579
- // SECURITY: Validate fallback path as well
580
- this.validatePathWithinBase(fallbackPath);
581
- try {
582
- await __rspack_external_fs_promises_e1851c9c.access(fallbackPath);
583
- filePath = fallbackPath;
584
- } catch {
585
- // File doesn't exist at either location - silent return
586
- return;
587
- }
602
+ continue;
588
603
  }
589
- await __rspack_external_fs_promises_e1851c9c.unlink(filePath);
590
- } catch (error) {
591
- // Re-throw security errors
592
- if (error instanceof Error && error.message.includes('Invalid')) {
593
- throw error;
604
+ try {
605
+ await __rspack_external_fs_promises_e1851c9c.unlink(candidate);
606
+ } catch {
607
+ // Silent failure - the file may have been removed concurrently
594
608
  }
595
- // Silent failure for other errors
609
+ return;
596
610
  }
597
611
  }
598
612
  async deleteMultipleFiles(keys) {
@@ -621,7 +635,7 @@ var external_uuid_ = __webpack_require__(37);
621
635
 
622
636
 
623
637
  },
624
- 872(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
638
+ 1872(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
625
639
 
626
640
  // EXPORTS
627
641
  __webpack_require__.d(__webpack_exports__, {
@@ -634,6 +648,10 @@ __webpack_require__.d(__webpack_exports__, {
634
648
 
635
649
  // Handle ESM/CJS interop - sharp exports differently in each module system
636
650
  const sharp = __rspack_external_sharp["default"] || __rspack_external_sharp;
651
+ const MULTI_FRAME_FORMATS = [
652
+ 'gif',
653
+ 'webp'
654
+ ];
637
655
  /**
638
656
  * Image Compression Utility
639
657
  * Handles image compression and format conversion using sharp
@@ -651,87 +669,87 @@ const sharp = __rspack_external_sharp["default"] || __rspack_external_sharp;
651
669
  format: mimetype
652
670
  };
653
671
  }
654
- // Initialize sharp with improved error handling
655
- const image = sharp(buffer, {
656
- failOnError: false,
657
- density: 300
658
- });
659
- // Apply initial resize
660
- image.resize({
661
- width: maxWidth,
662
- height: maxHeight,
663
- fit: 'inside',
664
- withoutEnlargement: true
665
- });
666
- // Determine target format
667
- let targetFormat = format !== 'original' ? format : mimetype.split('/')[1];
668
- // Format-specific optimizations
669
- switch(targetFormat.toLowerCase()){
670
- case 'jpeg':
671
- case 'jpg':
672
- image.jpeg({
673
- quality,
674
- mozjpeg: true,
675
- chromaSubsampling: '4:4:4'
676
- });
677
- break;
678
- case 'png':
679
- image.png({
680
- compressionLevel: 9,
681
- adaptiveFiltering: true,
682
- palette: true,
683
- quality,
684
- colors: 256,
685
- dither: 1.0
686
- });
687
- break;
688
- case 'webp':
689
- image.webp({
690
- quality,
691
- smartSubsample: true,
692
- effort: 6,
693
- lossless: quality === 100
694
- });
695
- break;
696
- case 'avif':
697
- image.avif({
698
- quality,
699
- effort: 6,
700
- chromaSubsampling: '4:4:4'
701
- });
702
- break;
703
- case 'tiff':
704
- image.tiff({
705
- quality,
706
- compression: 'lzw',
707
- predictor: 'horizontal',
708
- pyramid: true
709
- });
710
- break;
711
- case 'gif':
712
- image.gif({
713
- colours: 256,
714
- effort: 10,
715
- dither: 1
716
- });
717
- break;
718
- case 'jp2':
719
- image.jp2({
720
- quality,
721
- lossless: quality === 100
722
- });
723
- break;
724
- default:
725
- // Fallback to WebP for best compression/quality ratio
726
- targetFormat = 'webp';
727
- image.webp({
728
- quality,
729
- smartSubsample: true,
730
- effort: 6
731
- });
732
- break;
733
- }
672
+ let targetFormat = (format !== 'original' ? format : mimetype.split('/')[1]).toLowerCase();
734
673
  try {
674
+ // Read every frame only when the output can hold them - a multi-page JPEG/PNG would be one tall strip
675
+ const image = sharp(buffer, {
676
+ failOnError: false,
677
+ density: 300,
678
+ animated: MULTI_FRAME_FORMATS.includes(targetFormat)
679
+ });
680
+ // Apply initial resize
681
+ image.resize({
682
+ width: maxWidth,
683
+ height: maxHeight,
684
+ fit: 'inside',
685
+ withoutEnlargement: true
686
+ });
687
+ // Format-specific optimizations
688
+ switch(targetFormat){
689
+ case 'jpeg':
690
+ case 'jpg':
691
+ image.jpeg({
692
+ quality,
693
+ mozjpeg: true,
694
+ chromaSubsampling: '4:4:4'
695
+ });
696
+ break;
697
+ case 'png':
698
+ image.png({
699
+ compressionLevel: 9,
700
+ adaptiveFiltering: true,
701
+ palette: true,
702
+ quality,
703
+ colors: 256,
704
+ dither: 1.0
705
+ });
706
+ break;
707
+ case 'webp':
708
+ image.webp({
709
+ quality,
710
+ smartSubsample: true,
711
+ effort: 6,
712
+ lossless: quality === 100
713
+ });
714
+ break;
715
+ case 'avif':
716
+ image.avif({
717
+ quality,
718
+ effort: 6,
719
+ chromaSubsampling: '4:4:4'
720
+ });
721
+ break;
722
+ case 'tiff':
723
+ image.tiff({
724
+ quality,
725
+ compression: 'lzw',
726
+ predictor: 'horizontal',
727
+ pyramid: true
728
+ });
729
+ break;
730
+ case 'gif':
731
+ image.gif({
732
+ colours: 256,
733
+ effort: 10,
734
+ dither: 1
735
+ });
736
+ break;
737
+ case 'jp2':
738
+ image.jp2({
739
+ quality,
740
+ lossless: quality === 100
741
+ });
742
+ break;
743
+ default:
744
+ // Fallback to WebP for best compression/quality ratio
745
+ targetFormat = 'webp';
746
+ image.webp({
747
+ quality,
748
+ smartSubsample: true,
749
+ effort: 6
750
+ });
751
+ break;
752
+ }
735
753
  const compressedBuffer = await image.toBuffer();
736
754
  return {
737
755
  buffer: compressedBuffer,
@@ -747,12 +765,6 @@ const sharp = __rspack_external_sharp["default"] || __rspack_external_sharp;
747
765
  }
748
766
 
749
767
 
750
- },
751
- 521(module) {
752
-
753
- module.exports = __rspack_external_path;
754
-
755
-
756
768
  },
757
769
  37(module) {
758
770
 
@@ -1,12 +1,12 @@
1
1
  export const __rspack_esm_id = 190;
2
2
  export const __rspack_esm_ids = [190];
3
3
  export const __webpack_modules__ = {
4
- 269(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
4
+ 7269(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
5
5
  __webpack_require__.r(__webpack_exports__);
6
- /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(157);
7
- /* import */ var _flusys_nestjs_shared_constants__rspack_import_1 = __webpack_require__(140);
6
+ /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
7
+ /* import */ var _flusys_nestjs_shared_constants__rspack_import_1 = __webpack_require__(9140);
8
8
  /* import */ var uuid__rspack_import_2 = __webpack_require__(37);
9
- /* import */ var _utils_image_compressor_util_js__rspack_import_3 = __webpack_require__(872);
9
+ /* import */ var _utils_image_compressor_util_js__rspack_import_3 = __webpack_require__(1872);
10
10
  /**
11
11
  * Optional Azure Blob Storage Provider
12
12
  * Requires: npm install @azure/storage-blob
@@ -21,7 +21,7 @@ class AzureProvider {
21
21
  accountName = '';
22
22
  async initialize(config) {
23
23
  try {
24
- const { BlobServiceClient, StorageSharedKeyCredential } = await __webpack_require__.e(/* import() */ 746).then(__webpack_require__.bind(__webpack_require__, 737));
24
+ const { BlobServiceClient, StorageSharedKeyCredential } = await __webpack_require__.e(/* import() */ 746).then(__webpack_require__.bind(__webpack_require__, 4737));
25
25
  this.accountName = config.accountName;
26
26
  this.containerName = config.containerName;
27
27
  this.blobServiceClient = config.connectionString ? BlobServiceClient.fromConnectionString(config.connectionString) : new BlobServiceClient(`https://${config.accountName}.blob.core.windows.net`, new StorageSharedKeyCredential(config.accountName, config.accountKey));
@@ -74,7 +74,7 @@ class AzureProvider {
74
74
  await Promise.all(keys.map((key)=>this.deleteFile(key)));
75
75
  }
76
76
  async generatePresignedUrl(key, expiresInSeconds = 3600) {
77
- const { BlobSASPermissions, generateBlobSASQueryParameters } = await __webpack_require__.e(/* import() */ 746).then(__webpack_require__.bind(__webpack_require__, 737));
77
+ const { BlobSASPermissions, generateBlobSASQueryParameters } = await __webpack_require__.e(/* import() */ 746).then(__webpack_require__.bind(__webpack_require__, 4737));
78
78
  const blockBlobClient = this.containerClient.getBlockBlobClient(key);
79
79
  const startsOn = new Date();
80
80
  const expiresOn = new Date(startsOn.getTime() + expiresInSeconds * 1000);
@@ -2,7 +2,7 @@ import * as __rspack_external__aws_sdk_s3_request_presigner_e09eabb8 from "@aws-
2
2
  export const __rspack_esm_id = 252;
3
3
  export const __rspack_esm_ids = [252];
4
4
  export const __webpack_modules__ = {
5
- 207(module) {
5
+ 5207(module) {
6
6
 
7
7
  module.exports = __rspack_external__aws_sdk_s3_request_presigner_e09eabb8;
8
8
 
@@ -2,7 +2,7 @@ import * as __rspack_external__aws_sdk_lib_storage_4348b131 from "@aws-sdk/lib-s
2
2
  export const __rspack_esm_id = 327;
3
3
  export const __rspack_esm_ids = [327];
4
4
  export const __webpack_modules__ = {
5
- 354(module) {
5
+ 5354(module) {
6
6
 
7
7
  module.exports = __rspack_external__aws_sdk_lib_storage_4348b131;
8
8
 
@@ -2,7 +2,7 @@ import * as __rspack_external_ssh2_sftp_client_29ee4624 from "ssh2-sftp-client";
2
2
  export const __rspack_esm_id = 385;
3
3
  export const __rspack_esm_ids = [385];
4
4
  export const __webpack_modules__ = {
5
- 240(module) {
5
+ 2240(module) {
6
6
 
7
7
  module.exports = __rspack_external_ssh2_sftp_client_29ee4624;
8
8
 
@@ -1,12 +1,12 @@
1
1
  export const __rspack_esm_id = 591;
2
2
  export const __rspack_esm_ids = [591];
3
3
  export const __webpack_modules__ = {
4
- 970(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
4
+ 9970(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
5
5
  __webpack_require__.r(__webpack_exports__);
6
- /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(157);
7
- /* import */ var _flusys_nestjs_shared_constants__rspack_import_1 = __webpack_require__(140);
6
+ /* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
7
+ /* import */ var _flusys_nestjs_shared_constants__rspack_import_1 = __webpack_require__(9140);
8
8
  /* import */ var uuid__rspack_import_2 = __webpack_require__(37);
9
- /* import */ var _utils_image_compressor_util_js__rspack_import_3 = __webpack_require__(872);
9
+ /* import */ var _utils_image_compressor_util_js__rspack_import_3 = __webpack_require__(1872);
10
10
  /**
11
11
  * Optional AWS S3 Storage Provider
12
12
  * Requires: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
@@ -20,7 +20,7 @@ class S3Provider {
20
20
  region = '';
21
21
  async initialize(config) {
22
22
  try {
23
- const { S3Client } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 965));
23
+ const { S3Client } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 2965));
24
24
  this.region = config.region;
25
25
  this.bucketName = config.bucket;
26
26
  this.s3 = new S3Client({
@@ -42,7 +42,7 @@ class S3Provider {
42
42
  }
43
43
  }
44
44
  async uploadFile(file, options) {
45
- const { Upload } = await __webpack_require__.e(/* import() */ 327).then(__webpack_require__.bind(__webpack_require__, 354));
45
+ const { Upload } = await __webpack_require__.e(/* import() */ 327).then(__webpack_require__.bind(__webpack_require__, 5354));
46
46
  let processedBuffer = file.buffer;
47
47
  let contentType = file.mimetype;
48
48
  const fileName = `${(0,uuid__rspack_import_2.v4)()}-${file.originalname}`;
@@ -77,14 +77,14 @@ class S3Provider {
77
77
  return Promise.all(files.map((file)=>this.uploadFile(file, options)));
78
78
  }
79
79
  async deleteFile(key) {
80
- const { DeleteObjectCommand } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 965));
80
+ const { DeleteObjectCommand } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 2965));
81
81
  await this.s3.send(new DeleteObjectCommand({
82
82
  Bucket: this.bucketName,
83
83
  Key: key
84
84
  }));
85
85
  }
86
86
  async deleteMultipleFiles(keys) {
87
- const { DeleteObjectsCommand } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 965));
87
+ const { DeleteObjectsCommand } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 2965));
88
88
  await this.s3.send(new DeleteObjectsCommand({
89
89
  Bucket: this.bucketName,
90
90
  Delete: {
@@ -95,8 +95,8 @@ class S3Provider {
95
95
  }));
96
96
  }
97
97
  async generatePresignedUrl(key, expiresInSeconds = 3600) {
98
- const { GetObjectCommand } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 965));
99
- const { getSignedUrl } = await __webpack_require__.e(/* import() */ 252).then(__webpack_require__.bind(__webpack_require__, 207));
98
+ const { GetObjectCommand } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 2965));
99
+ const { getSignedUrl } = await __webpack_require__.e(/* import() */ 252).then(__webpack_require__.bind(__webpack_require__, 5207));
100
100
  return getSignedUrl(this.s3, new GetObjectCommand({
101
101
  Bucket: this.bucketName,
102
102
  Key: key
@@ -106,7 +106,7 @@ class S3Provider {
106
106
  }
107
107
  async healthCheck() {
108
108
  try {
109
- const { HeadBucketCommand } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 965));
109
+ const { HeadBucketCommand } = await __webpack_require__.e(/* import() */ 798).then(__webpack_require__.bind(__webpack_require__, 2965));
110
110
  await this.s3.send(new HeadBucketCommand({
111
111
  Bucket: this.bucketName
112
112
  }));