@flusys/nestjs-storage 1.1.0-beta → 1.1.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 (118) hide show
  1. package/README.md +153 -8
  2. package/cjs/config/index.js +0 -1
  3. package/cjs/config/storage.constants.js +0 -17
  4. package/cjs/controllers/file-manager.controller.js +44 -1
  5. package/cjs/controllers/folder.controller.js +44 -1
  6. package/cjs/controllers/storage-config.controller.js +44 -1
  7. package/cjs/controllers/upload.controller.js +18 -29
  8. package/cjs/docs/storage-swagger.config.js +24 -136
  9. package/cjs/dtos/file-manager.dto.js +71 -35
  10. package/cjs/dtos/folder.dto.js +15 -9
  11. package/cjs/dtos/storage-config.dto.js +5 -86
  12. package/cjs/dtos/upload.dto.js +24 -17
  13. package/cjs/entities/file-manager-with-company.entity.js +3 -4
  14. package/cjs/entities/file-manager.entity.js +71 -3
  15. package/cjs/entities/folder-with-company.entity.js +3 -4
  16. package/cjs/entities/folder.entity.js +19 -3
  17. package/cjs/entities/index.js +9 -10
  18. package/cjs/entities/storage-config-with-company.entity.js +3 -4
  19. package/cjs/entities/storage-config.entity.js +73 -3
  20. package/cjs/interfaces/index.js +0 -1
  21. package/cjs/middlewares/file-serve.middleware.js +113 -100
  22. package/cjs/modules/storage.module.js +82 -136
  23. package/cjs/providers/azure-provider.optional.js +10 -38
  24. package/cjs/providers/local-provider.js +38 -31
  25. package/cjs/providers/s3-provider.optional.js +19 -40
  26. package/cjs/providers/storage-factory.service.js +54 -99
  27. package/cjs/providers/storage-provider.registry.js +8 -18
  28. package/cjs/services/file-manager.service.js +238 -323
  29. package/cjs/services/folder.service.js +8 -11
  30. package/cjs/services/index.js +1 -0
  31. package/cjs/{config → services}/storage-config.service.js +32 -76
  32. package/cjs/services/storage-datasource.provider.js +16 -26
  33. package/cjs/services/storage-provider-config.service.js +15 -37
  34. package/cjs/services/upload.service.js +72 -88
  35. package/cjs/utils/file-validator.util.js +458 -0
  36. package/cjs/utils/image-compressor.util.js +3 -8
  37. package/config/index.d.ts +0 -1
  38. package/config/storage.constants.d.ts +0 -8
  39. package/controllers/upload.controller.d.ts +3 -6
  40. package/dtos/file-manager.dto.d.ts +13 -7
  41. package/dtos/folder.dto.d.ts +5 -5
  42. package/dtos/storage-config.dto.d.ts +9 -16
  43. package/entities/file-manager-with-company.entity.d.ts +2 -2
  44. package/entities/file-manager.entity.d.ts +11 -2
  45. package/entities/folder-with-company.entity.d.ts +2 -2
  46. package/entities/folder.entity.d.ts +4 -2
  47. package/entities/index.d.ts +3 -4
  48. package/entities/storage-config-with-company.entity.d.ts +2 -2
  49. package/entities/storage-config.entity.d.ts +7 -2
  50. package/fesm/config/index.js +0 -1
  51. package/fesm/config/storage.constants.js +0 -8
  52. package/fesm/controllers/file-manager.controller.js +45 -2
  53. package/fesm/controllers/folder.controller.js +45 -2
  54. package/fesm/controllers/storage-config.controller.js +45 -2
  55. package/fesm/controllers/upload.controller.js +19 -30
  56. package/fesm/docs/storage-swagger.config.js +27 -142
  57. package/fesm/dtos/file-manager.dto.js +72 -36
  58. package/fesm/dtos/folder.dto.js +16 -10
  59. package/fesm/dtos/storage-config.dto.js +9 -96
  60. package/fesm/dtos/upload.dto.js +25 -19
  61. package/fesm/entities/file-manager-with-company.entity.js +3 -4
  62. package/fesm/entities/file-manager.entity.js +72 -4
  63. package/fesm/entities/folder-with-company.entity.js +3 -4
  64. package/fesm/entities/folder.entity.js +20 -4
  65. package/fesm/entities/index.js +5 -13
  66. package/fesm/entities/storage-config-with-company.entity.js +3 -4
  67. package/fesm/entities/storage-config.entity.js +74 -4
  68. package/fesm/interfaces/index.js +0 -1
  69. package/fesm/interfaces/storage-config.interface.js +1 -3
  70. package/fesm/middlewares/file-serve.middleware.js +114 -101
  71. package/fesm/modules/storage.module.js +83 -136
  72. package/fesm/providers/azure-provider.optional.js +14 -45
  73. package/fesm/providers/local-provider.js +38 -31
  74. package/fesm/providers/s3-provider.optional.js +23 -47
  75. package/fesm/providers/storage-factory.service.js +52 -97
  76. package/fesm/providers/storage-provider.registry.js +10 -20
  77. package/fesm/services/file-manager.service.js +237 -322
  78. package/fesm/services/folder.service.js +6 -9
  79. package/fesm/services/index.js +1 -0
  80. package/fesm/{config → services}/storage-config.service.js +32 -76
  81. package/fesm/services/storage-datasource.provider.js +16 -26
  82. package/fesm/services/storage-provider-config.service.js +13 -35
  83. package/fesm/services/upload.service.js +71 -87
  84. package/fesm/utils/file-validator.util.js +451 -0
  85. package/fesm/utils/image-compressor.util.js +3 -8
  86. package/interfaces/file-manager.interface.d.ts +7 -4
  87. package/interfaces/index.d.ts +0 -1
  88. package/interfaces/storage-config.interface.d.ts +1 -22
  89. package/interfaces/storage-module-options.interface.d.ts +0 -5
  90. package/middlewares/file-serve.middleware.d.ts +9 -1
  91. package/modules/storage.module.d.ts +1 -2
  92. package/package.json +6 -6
  93. package/providers/azure-provider.optional.d.ts +8 -6
  94. package/providers/local-provider.d.ts +2 -7
  95. package/providers/s3-provider.optional.d.ts +9 -7
  96. package/providers/storage-factory.service.d.ts +8 -9
  97. package/providers/storage-provider.registry.d.ts +4 -4
  98. package/services/file-manager.service.d.ts +23 -16
  99. package/services/folder.service.d.ts +4 -4
  100. package/services/index.d.ts +1 -0
  101. package/services/storage-config.service.d.ts +24 -0
  102. package/services/storage-datasource.provider.d.ts +3 -4
  103. package/services/storage-provider-config.service.d.ts +5 -6
  104. package/services/upload.service.d.ts +5 -5
  105. package/utils/file-validator.util.d.ts +19 -0
  106. package/cjs/entities/file-manager-base.entity.js +0 -115
  107. package/cjs/entities/folder-base.entity.js +0 -55
  108. package/cjs/entities/storage-config-base.entity.js +0 -93
  109. package/cjs/interfaces/file-upload-response.interface.js +0 -4
  110. package/config/storage-config.service.d.ts +0 -22
  111. package/entities/file-manager-base.entity.d.ts +0 -13
  112. package/entities/folder-base.entity.d.ts +0 -5
  113. package/entities/storage-config-base.entity.d.ts +0 -9
  114. package/fesm/entities/file-manager-base.entity.js +0 -108
  115. package/fesm/entities/folder-base.entity.js +0 -48
  116. package/fesm/entities/storage-config-base.entity.js +0 -83
  117. package/fesm/interfaces/file-upload-response.interface.js +0 -1
  118. package/interfaces/file-upload-response.interface.d.ts +0 -6
@@ -20,13 +20,29 @@ function _ts_decorate(decorators, target, key, desc) {
20
20
  function _ts_metadata(k, v) {
21
21
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
22
  }
23
- import { Entity, OneToMany } from 'typeorm';
24
- import { FolderBase } from './folder-base.entity';
25
- export class Folder extends FolderBase {
23
+ import { Identity } from '@flusys/nestjs-shared/entities';
24
+ import { Column, Entity, OneToMany } from 'typeorm';
25
+ export class Folder extends Identity {
26
26
  constructor(...args){
27
- super(...args), _define_property(this, "fileManager", void 0);
27
+ super(...args), _define_property(this, "name", void 0), _define_property(this, "slug", void 0), _define_property(this, "fileManager", void 0);
28
28
  }
29
29
  }
30
+ _ts_decorate([
31
+ Column({
32
+ type: 'varchar',
33
+ length: 255,
34
+ nullable: false
35
+ }),
36
+ _ts_metadata("design:type", String)
37
+ ], Folder.prototype, "name", void 0);
38
+ _ts_decorate([
39
+ Column({
40
+ type: 'varchar',
41
+ length: 255,
42
+ nullable: false
43
+ }),
44
+ _ts_metadata("design:type", String)
45
+ ], Folder.prototype, "slug", void 0);
30
46
  _ts_decorate([
31
47
  OneToMany('FileManager', (fileManager)=>fileManager.folder),
32
48
  _ts_metadata("design:type", Array)
@@ -1,7 +1,3 @@
1
- // Base entities
2
- export * from './file-manager-base.entity';
3
- export * from './folder-base.entity';
4
- export * from './storage-config-base.entity';
5
1
  // Entities without company feature
6
2
  export * from './file-manager.entity';
7
3
  export * from './folder.entity';
@@ -28,14 +24,10 @@ export const StorageCompanyEntities = [
28
24
  FolderWithCompany,
29
25
  StorageConfigWithCompany
30
26
  ];
31
- // All entities
32
- export const StorageAllEntities = [
33
- ...StorageCoreEntities,
34
- ...StorageCompanyEntities
35
- ];
36
- /**
37
- * Get Storage entities based on configuration
38
- * @param enableCompanyFeature - Whether company feature is enabled
39
- */ export function getStorageEntitiesByConfig(enableCompanyFeature) {
27
+ export function getStorageEntitiesByConfig(enableCompanyFeature) {
40
28
  return enableCompanyFeature ? StorageCompanyEntities : StorageCoreEntities;
41
29
  }
30
+ // Base type aliases for backwards compatibility with services
31
+ export { FileManager as FileManagerBase } from './file-manager.entity';
32
+ export { Folder as FolderBase } from './folder.entity';
33
+ export { StorageConfig as StorageConfigBase } from './storage-config.entity';
@@ -21,11 +21,10 @@ function _ts_metadata(k, v) {
21
21
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
22
  }
23
23
  import { Column, Entity } from 'typeorm';
24
- import { StorageConfigBase } from './storage-config-base.entity';
25
- export class StorageConfigWithCompany extends StorageConfigBase {
24
+ import { StorageConfig } from './storage-config.entity';
25
+ export class StorageConfigWithCompany extends StorageConfig {
26
26
  constructor(...args){
27
- super(...args), // Company ID - used when company feature is enabled
28
- _define_property(this, "companyId", void 0);
27
+ super(...args), _define_property(this, "companyId", void 0);
29
28
  }
30
29
  }
31
30
  _ts_decorate([
@@ -1,15 +1,85 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
1
14
  function _ts_decorate(decorators, target, key, desc) {
2
15
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
16
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
17
  else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
18
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
19
  }
7
- import { Entity } from 'typeorm';
8
- import { StorageConfigBase } from './storage-config-base.entity';
9
- export class StorageConfig extends StorageConfigBase {
20
+ function _ts_metadata(k, v) {
21
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
+ }
23
+ import { Identity } from '@flusys/nestjs-shared/entities';
24
+ import { Column, Entity, Index } from 'typeorm';
25
+ export class StorageConfig extends Identity {
26
+ constructor(...args){
27
+ super(...args), _define_property(this, "name", void 0), _define_property(this, "storage", void 0), _define_property(this, "config", void 0), _define_property(this, "isActive", void 0), _define_property(this, "isDefault", void 0);
28
+ }
10
29
  }
30
+ _ts_decorate([
31
+ Column({
32
+ type: 'varchar',
33
+ length: 255,
34
+ nullable: false
35
+ }),
36
+ _ts_metadata("design:type", String)
37
+ ], StorageConfig.prototype, "name", void 0);
38
+ _ts_decorate([
39
+ Column({
40
+ type: 'varchar',
41
+ length: 50,
42
+ nullable: false
43
+ }),
44
+ _ts_metadata("design:type", String)
45
+ ], StorageConfig.prototype, "storage", void 0);
46
+ _ts_decorate([
47
+ Column({
48
+ type: 'json',
49
+ nullable: false
50
+ }),
51
+ _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
52
+ ], StorageConfig.prototype, "config", void 0);
53
+ _ts_decorate([
54
+ Column({
55
+ type: 'boolean',
56
+ default: true,
57
+ name: 'is_active'
58
+ }),
59
+ _ts_metadata("design:type", Boolean)
60
+ ], StorageConfig.prototype, "isActive", void 0);
61
+ _ts_decorate([
62
+ Column({
63
+ type: 'boolean',
64
+ default: false,
65
+ name: 'is_default'
66
+ }),
67
+ _ts_metadata("design:type", Boolean)
68
+ ], StorageConfig.prototype, "isDefault", void 0);
11
69
  StorageConfig = _ts_decorate([
12
70
  Entity({
13
71
  name: 'storage_config'
14
- })
72
+ }),
73
+ Index([
74
+ 'name'
75
+ ]),
76
+ Index([
77
+ 'storage'
78
+ ]),
79
+ Index([
80
+ 'isActive'
81
+ ]),
82
+ Index([
83
+ 'isDefault'
84
+ ])
15
85
  ], StorageConfig);
@@ -1,5 +1,4 @@
1
1
  export * from './file-manager.interface';
2
- export * from './file-upload-response.interface';
3
2
  export * from './folder.interface';
4
3
  export * from './storage-config.interface';
5
4
  export * from './storage-module-options.interface';
@@ -1,3 +1 @@
1
- /**
2
- * SFTP Configuration
3
- */ export { };
1
+ export { };
@@ -20,132 +20,145 @@ function _ts_decorate(decorators, target, key, desc) {
20
20
  function _ts_metadata(k, v) {
21
21
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
22
  }
23
- import { Injectable, Logger, NotFoundException } from '@nestjs/common';
23
+ function _ts_param(paramIndex, decorator) {
24
+ return function(target, key) {
25
+ decorator(target, key, paramIndex);
26
+ };
27
+ }
28
+ import { Injectable, Logger, NotFoundException, Inject } from '@nestjs/common';
24
29
  import { createReadStream, existsSync, statSync } from 'fs';
25
30
  import { join, resolve } from 'path';
26
31
  import * as mime from 'mime-types';
27
32
  import { UploadService } from '../services/upload.service';
33
+ /** MIME type prefixes that can be displayed inline in browser */ const VIEWABLE_TYPE_PREFIXES = [
34
+ 'image/',
35
+ 'video/',
36
+ 'audio/',
37
+ 'text/',
38
+ 'application/pdf',
39
+ 'application/json',
40
+ 'application/xml'
41
+ ];
28
42
  export class FileServeMiddleware {
29
43
  async use(req, res, next) {
30
44
  try {
31
- const uploadDir = process.cwd();
32
- // Extract path after /storage/upload/file/
33
- const urlPath = req.path || req.url;
34
- const match = urlPath.match(/\/storage\/upload\/file\/(.+)/);
35
- const normalizedPath = match ? match[1] : '';
36
- if (!normalizedPath) {
37
- throw new NotFoundException('File path is required');
38
- }
39
- this.logger.debug(`Attempting to serve file, normalizedPath: ${normalizedPath}`);
40
- // Get the local storage basePath for fallback lookups
41
- const basePath = await this.uploadService.getLocalStorageBasePath();
42
- const normalizedBasePath = basePath ? basePath.replace(/^\.\//, '').replace(/\/$/, '') : null;
43
- // Strategy 1: Try path relative to CWD (new format: key includes basePath)
44
- let fullPath = join(uploadDir, normalizedPath);
45
- let resolvedPath = resolve(fullPath);
46
- this.logger.debug(`Strategy 1 - CWD relative: ${fullPath}`);
47
- // Validate path is inside the project directory (prevent path traversal)
48
- if (!resolvedPath.startsWith(uploadDir)) {
49
- throw new NotFoundException('Invalid file path');
50
- }
51
- // Check if file exists
52
- if (!existsSync(fullPath)) {
53
- // Strategy 2: If key already starts with basePath, try using basePath directly
54
- if (basePath && normalizedBasePath) {
55
- const pathStartsWithBase = normalizedPath.startsWith(normalizedBasePath + '/') || normalizedPath.startsWith(normalizedBasePath);
56
- if (pathStartsWithBase) {
57
- const remainingPath = normalizedPath.substring(normalizedBasePath.length).replace(/^\//, '');
58
- const fallbackPath = join(basePath, remainingPath);
59
- this.logger.debug(`Strategy 2 - basePath + remaining: ${fallbackPath}`);
60
- if (existsSync(fallbackPath)) {
61
- fullPath = fallbackPath;
62
- resolvedPath = resolve(fullPath);
63
- }
64
- } else {
65
- // Old format: key doesn't include basePath, prepend it
66
- const fallbackPath = join(basePath, normalizedPath);
67
- this.logger.debug(`Strategy 3 - basePath + full key (old format): ${fallbackPath}`);
68
- if (existsSync(fallbackPath)) {
69
- fullPath = fallbackPath;
70
- resolvedPath = resolve(fullPath);
71
- }
72
- }
73
- }
74
- // Final check if file was found
75
- if (!existsSync(fullPath)) {
76
- throw new NotFoundException(`File not found: ${normalizedPath}`);
77
- }
78
- }
45
+ const normalizedPath = this.extractFilePath(req);
46
+ const fullPath = await this.resolveFilePath(normalizedPath);
79
47
  const stats = statSync(fullPath);
80
48
  if (!stats.isFile()) {
81
49
  throw new NotFoundException(`Not a file: ${normalizedPath}`);
82
50
  }
83
- // Enhanced MIME type detection
84
- let mimeType = mime.lookup(fullPath) || 'application/octet-stream';
85
- // Special handling for SVG files
86
- if (fullPath.toLowerCase().endsWith('.svg')) {
87
- mimeType = 'image/svg+xml';
88
- }
89
- // Determine if file should be displayed inline or downloaded
90
- const viewableTypes = [
91
- 'image/',
92
- 'video/',
93
- 'audio/',
94
- 'text/',
95
- 'application/pdf',
96
- 'application/json',
97
- 'application/xml'
98
- ];
99
- const isViewable = viewableTypes.some((type)=>mimeType.startsWith(type));
100
- const contentDisposition = isViewable ? 'inline' : `attachment; filename="${encodeURIComponent(normalizedPath.split('/').pop() || 'download')}"`;
101
- // Set headers
102
- res.set({
103
- 'Content-Type': mimeType,
104
- 'Content-Length': stats.size,
105
- 'Content-Disposition': contentDisposition,
106
- 'Cache-Control': 'public, max-age=3600',
107
- 'Accept-Ranges': 'bytes',
108
- 'Cross-Origin-Resource-Policy': 'cross-origin',
109
- 'Access-Control-Allow-Origin': '*',
110
- 'X-Content-Type-Options': 'nosniff'
111
- });
112
- // Stream the file
113
- const stream = createReadStream(fullPath);
114
- stream.pipe(res);
115
- stream.on('error', (err)=>{
116
- this.logger.error('File stream error', err);
117
- if (!res.headersSent) {
118
- res.status(404).json({
119
- message: `Failed to serve file: ${normalizedPath}`,
120
- error: 'Not Found',
121
- statusCode: 404
122
- });
123
- }
124
- });
125
- // Handle client disconnection
126
- res.on('close', ()=>{
127
- stream.destroy();
128
- });
51
+ const mimeType = this.getMimeType(fullPath);
52
+ this.setResponseHeaders(res, stats.size, mimeType, normalizedPath);
53
+ this.streamFile(res, fullPath, normalizedPath);
129
54
  } catch (error) {
130
- this.logger.error('File retrieval error:', error);
55
+ this.sendErrorResponse(res, error);
56
+ }
57
+ }
58
+ /** Extract and validate file path from request URL */ extractFilePath(req) {
59
+ const urlPath = req.path || req.url;
60
+ const match = urlPath.match(/\/storage\/upload\/file\/(.+)/);
61
+ const normalizedPath = match ? match[1] : '';
62
+ if (!normalizedPath) {
63
+ throw new NotFoundException('File path is required');
64
+ }
65
+ this.logger.debug(`Attempting to serve file, normalizedPath: ${normalizedPath}`);
66
+ return normalizedPath;
67
+ }
68
+ /** Resolve file path using multiple fallback strategies */ async resolveFilePath(normalizedPath) {
69
+ // Strategy 1: Path relative to CWD (new format: key includes basePath)
70
+ let fullPath = join(this.uploadDir, normalizedPath);
71
+ const resolvedPath = resolve(fullPath);
72
+ this.logger.debug(`Strategy 1 - CWD relative: ${fullPath}`);
73
+ // Prevent path traversal attacks
74
+ if (!resolvedPath.startsWith(this.uploadDir)) {
75
+ throw new NotFoundException('Invalid file path');
76
+ }
77
+ if (existsSync(fullPath)) {
78
+ return fullPath;
79
+ }
80
+ // Try fallback strategies with basePath
81
+ const basePath = await this.uploadService.getLocalStorageBasePath();
82
+ if (basePath) {
83
+ const fallbackPath = this.tryFallbackPaths(normalizedPath, basePath);
84
+ if (fallbackPath) {
85
+ return fallbackPath;
86
+ }
87
+ }
88
+ throw new NotFoundException(`File not found: ${normalizedPath}`);
89
+ }
90
+ /** Try basePath-based fallback strategies */ tryFallbackPaths(normalizedPath, basePath) {
91
+ const normalizedBasePath = basePath.replace(/^\.\//, '').replace(/\/$/, '');
92
+ const pathStartsWithBase = normalizedPath.startsWith(normalizedBasePath + '/') || normalizedPath.startsWith(normalizedBasePath);
93
+ if (pathStartsWithBase) {
94
+ // Strategy 2: basePath + remaining path
95
+ const remainingPath = normalizedPath.substring(normalizedBasePath.length).replace(/^\//, '');
96
+ const fallbackPath = join(basePath, remainingPath);
97
+ this.logger.debug(`Strategy 2 - basePath + remaining: ${fallbackPath}`);
98
+ if (existsSync(fallbackPath)) return fallbackPath;
99
+ } else {
100
+ // Strategy 3: Old format - prepend basePath
101
+ const fallbackPath = join(basePath, normalizedPath);
102
+ this.logger.debug(`Strategy 3 - basePath + full key (old format): ${fallbackPath}`);
103
+ if (existsSync(fallbackPath)) return fallbackPath;
104
+ }
105
+ return null;
106
+ }
107
+ /** Get MIME type with SVG special handling */ getMimeType(fullPath) {
108
+ if (fullPath.toLowerCase().endsWith('.svg')) {
109
+ return 'image/svg+xml';
110
+ }
111
+ return mime.lookup(fullPath) || 'application/octet-stream';
112
+ }
113
+ /** Set response headers for file serving */ setResponseHeaders(res, size, mimeType, normalizedPath) {
114
+ const isViewable = VIEWABLE_TYPE_PREFIXES.some((type)=>mimeType.startsWith(type));
115
+ const filename = normalizedPath.split('/').pop() || 'download';
116
+ const contentDisposition = isViewable ? 'inline' : `attachment; filename="${encodeURIComponent(filename)}"`;
117
+ res.set({
118
+ 'Content-Type': mimeType,
119
+ 'Content-Length': size,
120
+ 'Content-Disposition': contentDisposition,
121
+ 'Cache-Control': 'public, max-age=3600',
122
+ 'Accept-Ranges': 'bytes',
123
+ 'Cross-Origin-Resource-Policy': 'cross-origin',
124
+ 'Access-Control-Allow-Origin': '*',
125
+ 'X-Content-Type-Options': 'nosniff'
126
+ });
127
+ }
128
+ /** Stream file to response with error handling */ streamFile(res, fullPath, normalizedPath) {
129
+ const stream = createReadStream(fullPath);
130
+ stream.pipe(res);
131
+ stream.on('error', (err)=>{
132
+ this.logger.error('File stream error', err);
131
133
  if (!res.headersSent) {
132
- res.status(404).json({
133
- message: error instanceof NotFoundException ? error.message : `File not found`,
134
- error: 'Not Found',
135
- statusCode: 404
136
- });
134
+ this.sendErrorResponse(res, new NotFoundException(`Failed to serve file: ${normalizedPath}`));
137
135
  }
136
+ });
137
+ res.on('close', ()=>stream.destroy());
138
+ }
139
+ /** Send consistent error response */ sendErrorResponse(res, error) {
140
+ this.logger.error('File retrieval error:', error);
141
+ if (!res.headersSent) {
142
+ const message = error instanceof NotFoundException ? error.message : 'File not found';
143
+ res.status(404).json({
144
+ message,
145
+ error: 'Not Found',
146
+ statusCode: 404
147
+ });
138
148
  }
139
149
  }
140
150
  constructor(uploadService){
141
151
  _define_property(this, "uploadService", void 0);
142
152
  _define_property(this, "logger", void 0);
153
+ _define_property(this, "uploadDir", void 0);
143
154
  this.uploadService = uploadService;
144
155
  this.logger = new Logger(FileServeMiddleware.name);
156
+ this.uploadDir = process.cwd();
145
157
  }
146
158
  }
147
159
  FileServeMiddleware = _ts_decorate([
148
160
  Injectable(),
161
+ _ts_param(0, Inject(UploadService)),
149
162
  _ts_metadata("design:type", Function),
150
163
  _ts_metadata("design:paramtypes", [
151
164
  typeof UploadService === "undefined" ? Object : UploadService