@flusys/nestjs-storage 0.1.0-beta.2 → 0.1.0-beta.3

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 (47) hide show
  1. package/README.md +2 -0
  2. package/cjs/config/storage-config.service.js +31 -0
  3. package/cjs/controllers/file-manager.controller.js +1 -1
  4. package/cjs/controllers/upload.controller.js +22 -169
  5. package/cjs/dtos/file-manager.dto.js +36 -2
  6. package/cjs/dtos/upload.dto.js +16 -0
  7. package/cjs/middlewares/file-serve.middleware.js +204 -0
  8. package/cjs/middlewares/index.js +18 -0
  9. package/cjs/modules/storage.module.js +56 -10
  10. package/cjs/providers/azure-provider.optional.js +1 -2
  11. package/cjs/providers/local-provider.js +43 -11
  12. package/cjs/providers/storage-factory.service.js +49 -5
  13. package/cjs/services/file-manager.service.js +134 -9
  14. package/cjs/services/storage-provider-config.service.js +12 -0
  15. package/cjs/services/upload.service.js +135 -24
  16. package/cjs/utils/image-compressor.util.js +43 -5
  17. package/config/storage-config.service.d.ts +2 -0
  18. package/controllers/upload.controller.d.ts +5 -4
  19. package/dtos/file-manager.dto.d.ts +4 -0
  20. package/dtos/upload.dto.d.ts +2 -0
  21. package/fesm/config/storage-config.service.js +31 -0
  22. package/fesm/controllers/file-manager.controller.js +1 -1
  23. package/fesm/controllers/upload.controller.js +25 -131
  24. package/fesm/dtos/file-manager.dto.js +36 -2
  25. package/fesm/dtos/upload.dto.js +16 -0
  26. package/fesm/middlewares/file-serve.middleware.js +153 -0
  27. package/fesm/middlewares/index.js +1 -0
  28. package/fesm/modules/storage.module.js +58 -12
  29. package/fesm/providers/azure-provider.optional.js +1 -2
  30. package/fesm/providers/local-provider.js +43 -11
  31. package/fesm/providers/storage-factory.service.js +50 -6
  32. package/fesm/services/file-manager.service.js +134 -9
  33. package/fesm/services/storage-provider-config.service.js +12 -0
  34. package/fesm/services/upload.service.js +135 -24
  35. package/fesm/utils/image-compressor.util.js +3 -1
  36. package/interfaces/file-manager.interface.d.ts +2 -0
  37. package/interfaces/storage-module-options.interface.d.ts +2 -0
  38. package/interfaces/storage-provider.interface.d.ts +2 -0
  39. package/middlewares/file-serve.middleware.d.ts +9 -0
  40. package/middlewares/index.d.ts +1 -0
  41. package/modules/storage.module.d.ts +3 -2
  42. package/package.json +26 -11
  43. package/providers/local-provider.d.ts +2 -1
  44. package/providers/storage-factory.service.d.ts +4 -0
  45. package/services/file-manager.service.d.ts +7 -1
  46. package/services/storage-provider-config.service.d.ts +1 -0
  47. package/services/upload.service.d.ts +6 -2
package/README.md CHANGED
@@ -161,6 +161,8 @@ import { StorageModule } from '@flusys/nestjs-storage';
161
161
  // File validation
162
162
  maxFileSize: 10 * 1024 * 1024, // 10MB
163
163
  allowedFileTypes: ['image/*', 'application/pdf', 'text/*'],
164
+ // URL generation (optional - falls back to APP_URL env or PORT)
165
+ appUrl: process.env.APP_URL || `http://localhost:${process.env.PORT || 3000}`,
164
166
  },
165
167
  }),
166
168
  ],
@@ -116,6 +116,37 @@ let StorageConfigService = class StorageConfigService {
116
116
  */ getOptions() {
117
117
  return this.options;
118
118
  }
119
+ /**
120
+ * Get default local storage base path
121
+ * Used for serving local files without database lookup
122
+ * Falls back to './uploads' if not configured
123
+ */ getDefaultLocalStoragePath() {
124
+ // Check if localStoragePath is configured in module options
125
+ const configuredPath = this.options.config?.localStoragePath;
126
+ if (configuredPath) {
127
+ return configuredPath;
128
+ }
129
+ // Default to ./uploads in the project root
130
+ return './uploads';
131
+ }
132
+ /**
133
+ * Get application base URL for generating file URLs
134
+ * - First tries module config
135
+ * - Falls back to APP_URL env var
136
+ * - Finally constructs from PORT env var
137
+ */ getAppUrl() {
138
+ // Try from module config first
139
+ if (this.options.config?.appUrl) {
140
+ return this.options.config.appUrl;
141
+ }
142
+ // Fallback: read directly from environment
143
+ if (process.env.APP_URL) {
144
+ return process.env.APP_URL;
145
+ }
146
+ // Last resort: construct from PORT
147
+ const port = process.env.PORT || '3000';
148
+ return `http://localhost:${port}`;
149
+ }
119
150
  constructor(options){
120
151
  _define_property(this, "options", void 0);
121
152
  this.options = options;
@@ -51,7 +51,7 @@ let FileManagerController = class FileManagerController extends (0, _classes.cre
51
51
  throw new _common.BadRequestException('No files provided');
52
52
  }
53
53
  // Fetch files from service
54
- const files = await this.fileService.getFiles(dto, req.protocol, req.host, user);
54
+ const files = await this.fileService.getFiles(dto, req.protocol, req.get('host') || req.hostname, user);
55
55
  return {
56
56
  success: true,
57
57
  message: 'Files retrieved successfully',
@@ -15,11 +15,9 @@ const _dtos = require("../dtos");
15
15
  const _common = require("@nestjs/common");
16
16
  const _platformexpress = require("@nestjs/platform-express");
17
17
  const _swagger = require("@nestjs/swagger");
18
- const _express = require("express");
19
- const _fs = require("fs");
20
- const _mimetypes = /*#__PURE__*/ _interop_require_wildcard(require("mime-types"));
21
- const _path = require("path");
22
18
  const _uploadservice = require("../services/upload.service");
19
+ const _config = require("../config");
20
+ const _storagefactoryservice = require("../providers/storage-factory.service");
23
21
  function _define_property(obj, key, value) {
24
22
  if (key in obj) {
25
23
  Object.defineProperty(obj, key, {
@@ -33,47 +31,6 @@ function _define_property(obj, key, value) {
33
31
  }
34
32
  return obj;
35
33
  }
36
- function _getRequireWildcardCache(nodeInterop) {
37
- if (typeof WeakMap !== "function") return null;
38
- var cacheBabelInterop = new WeakMap();
39
- var cacheNodeInterop = new WeakMap();
40
- return (_getRequireWildcardCache = function(nodeInterop) {
41
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
42
- })(nodeInterop);
43
- }
44
- function _interop_require_wildcard(obj, nodeInterop) {
45
- if (!nodeInterop && obj && obj.__esModule) {
46
- return obj;
47
- }
48
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
49
- return {
50
- default: obj
51
- };
52
- }
53
- var cache = _getRequireWildcardCache(nodeInterop);
54
- if (cache && cache.has(obj)) {
55
- return cache.get(obj);
56
- }
57
- var newObj = {
58
- __proto__: null
59
- };
60
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
61
- for(var key in obj){
62
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
63
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
64
- if (desc && (desc.get || desc.set)) {
65
- Object.defineProperty(newObj, key, desc);
66
- } else {
67
- newObj[key] = obj[key];
68
- }
69
- }
70
- }
71
- newObj.default = obj;
72
- if (cache) {
73
- cache.set(obj, newObj);
74
- }
75
- return newObj;
76
- }
77
34
  function _ts_decorate(decorators, target, key, desc) {
78
35
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
79
36
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -90,15 +47,17 @@ function _ts_param(paramIndex, decorator) {
90
47
  }
91
48
  let UploadController = class UploadController {
92
49
  async uploadSingleFile(file, options, user) {
93
- const url = await this.uploadService.uploadSingleFile(file, options, user);
50
+ const result = await this.uploadService.uploadSingleFile(file, options, user);
94
51
  return {
95
52
  success: true,
96
53
  message: 'File uploaded successfully',
97
54
  data: {
98
- size: this.uploadService.bytesToKb(url.size),
99
- name: url.name,
100
- key: url.key,
101
- contentType: url.contentType
55
+ size: this.uploadService.bytesToKb(result.size),
56
+ name: result.name,
57
+ key: result.key,
58
+ contentType: result.contentType,
59
+ location: result.location || 'local',
60
+ storageConfigId: result.storageConfigId || ''
102
61
  }
103
62
  };
104
63
  }
@@ -108,7 +67,9 @@ let UploadController = class UploadController {
108
67
  size: this.uploadService.bytesToKb(file.size),
109
68
  name: file.name,
110
69
  key: file.key,
111
- contentType: file.contentType
70
+ contentType: file.contentType,
71
+ location: file.location || 'local',
72
+ storageConfigId: file.storageConfigId || ''
112
73
  }));
113
74
  return {
114
75
  success: true,
@@ -132,82 +93,13 @@ let UploadController = class UploadController {
132
93
  data
133
94
  };
134
95
  }
135
- async seeUploadedFile(filePath, res) {
136
- try {
137
- const uploadDir = (0, _path.join)(process.cwd(), 'uploads');
138
- const normalizedPath = Array.isArray(filePath) ? filePath.join('/') : filePath;
139
- const fullPath = (0, _path.join)(uploadDir, normalizedPath);
140
- this.logger.debug(`Attempting to serve file from: ${fullPath}`);
141
- // Validate path is inside uploads directory
142
- if (!fullPath.startsWith(uploadDir)) {
143
- throw new _common.NotFoundException('Invalid file path');
144
- }
145
- // Check if file exists
146
- if (!(0, _fs.existsSync)(fullPath)) {
147
- throw new _common.NotFoundException(`File not found: ${normalizedPath}`);
148
- }
149
- const stats = (0, _fs.statSync)(fullPath);
150
- if (!stats.isFile()) {
151
- throw new _common.NotFoundException(`Not a file: ${normalizedPath}`);
152
- }
153
- // Enhanced MIME type detection
154
- let mimeType = _mimetypes.lookup(fullPath) || 'application/octet-stream';
155
- // Special handling for SVG files
156
- if (fullPath.toLowerCase().endsWith('.svg')) {
157
- mimeType = 'image/svg+xml';
158
- }
159
- // Determine if file should be displayed inline or downloaded
160
- // Inline: PDFs, images, videos, audio, text files
161
- // Download: Office docs, archives, executables, etc.
162
- const viewableTypes = [
163
- 'image/',
164
- 'video/',
165
- 'audio/',
166
- 'text/',
167
- 'application/pdf',
168
- 'application/json',
169
- 'application/xml'
170
- ];
171
- const isViewable = viewableTypes.some((type)=>mimeType.startsWith(type));
172
- const contentDisposition = isViewable ? 'inline' : `attachment; filename="${encodeURIComponent(normalizedPath.split('/').pop() || 'download')}"`;
173
- // Set headers optimized for different file types
174
- res.set({
175
- 'Content-Type': mimeType,
176
- 'Content-Length': stats.size,
177
- 'Content-Disposition': contentDisposition,
178
- 'Cache-Control': 'public, max-age=3600',
179
- 'Accept-Ranges': 'bytes',
180
- 'Cross-Origin-Resource-Policy': 'cross-origin',
181
- 'Access-Control-Allow-Origin': '*',
182
- 'X-Content-Type-Options': 'nosniff'
183
- });
184
- // Stream the file
185
- const stream = (0, _fs.createReadStream)(fullPath);
186
- stream.pipe(res);
187
- return new Promise((resolve, reject)=>{
188
- stream.on('end', ()=>{
189
- res.end();
190
- resolve();
191
- });
192
- stream.on('error', (err)=>{
193
- this.logger.error('File stream error', err);
194
- reject(new _common.NotFoundException(`Failed to serve file: ${normalizedPath}`));
195
- });
196
- // Handle client disconnection
197
- res.on('close', ()=>{
198
- stream.destroy();
199
- });
200
- });
201
- } catch (error) {
202
- this.logger.error('File retrieval error:', error);
203
- throw new _common.NotFoundException(`File not found: ${filePath}`);
204
- }
205
- }
206
- constructor(uploadService){
96
+ constructor(uploadService, storageConfigService, storageFactoryService){
207
97
  _define_property(this, "uploadService", void 0);
208
- _define_property(this, "logger", void 0);
98
+ _define_property(this, "storageConfigService", void 0);
99
+ _define_property(this, "storageFactoryService", void 0);
209
100
  this.uploadService = uploadService;
210
- this.logger = new _common.Logger(UploadController.name);
101
+ this.storageConfigService = storageConfigService;
102
+ this.storageFactoryService = storageFactoryService;
211
103
  }
212
104
  };
213
105
  _ts_decorate([
@@ -329,57 +221,18 @@ _ts_decorate([
329
221
  ]),
330
222
  _ts_metadata("design:returntype", Promise)
331
223
  ], UploadController.prototype, "deleteMultipleFile", null);
332
- _ts_decorate([
333
- (0, _decorators.Public)(),
334
- (0, _common.Get)('file/*filePath'),
335
- (0, _swagger.ApiOperation)({
336
- summary: 'Serve uploaded file by relative path (Public endpoint)',
337
- description: `
338
- This endpoint returns a stored file from the server.
339
-
340
- - The \`filePath\` is a **wildcard path parameter** that supports nested folders.
341
- - Example:
342
- \`GET /uploads/file/profile/user123/avatar.png\`
343
- → Will serve the file from \`./profile/user123/avatar.png\`
344
-
345
- **Use case:**
346
- Access uploaded files publicly through a structured path.
347
-
348
- **Note:** This endpoint is public and does not require authentication.
349
- `
350
- }),
351
- (0, _swagger.ApiParam)({
352
- name: 'filePath',
353
- required: true,
354
- description: 'Relative path to the uploaded file (supports nested directories, e.g. "profile/user123/avatar.png")',
355
- example: 'profile/user123/avatar.png'
356
- }),
357
- (0, _swagger.ApiProduces)('image/jpeg'),
358
- (0, _swagger.ApiResponse)({
359
- status: 200,
360
- description: 'Uploaded file successfully returned',
361
- schema: {
362
- type: 'string',
363
- format: 'binary'
364
- }
365
- }),
366
- _ts_param(0, (0, _common.Param)('filePath')),
367
- _ts_param(1, (0, _common.Res)()),
368
- _ts_metadata("design:type", Function),
369
- _ts_metadata("design:paramtypes", [
370
- Object,
371
- typeof _express.Response === "undefined" ? Object : _express.Response
372
- ]),
373
- _ts_metadata("design:returntype", Promise)
374
- ], UploadController.prototype, "seeUploadedFile", null);
375
224
  UploadController = _ts_decorate([
376
225
  (0, _swagger.ApiTags)('Upload'),
377
226
  (0, _swagger.ApiBearerAuth)(),
378
227
  (0, _common.Controller)('storage/upload'),
379
228
  (0, _common.UseGuards)(_guards.JwtAuthGuard),
380
229
  _ts_param(0, (0, _common.Inject)(_uploadservice.UploadService)),
230
+ _ts_param(1, (0, _common.Inject)(_config.StorageConfigService)),
231
+ _ts_param(2, (0, _common.Inject)(_storagefactoryservice.StorageFactoryService)),
381
232
  _ts_metadata("design:type", Function),
382
233
  _ts_metadata("design:paramtypes", [
383
- typeof _uploadservice.UploadService === "undefined" ? Object : _uploadservice.UploadService
234
+ typeof _uploadservice.UploadService === "undefined" ? Object : _uploadservice.UploadService,
235
+ typeof _config.StorageConfigService === "undefined" ? Object : _config.StorageConfigService,
236
+ typeof _storagefactoryservice.StorageFactoryService === "undefined" ? Object : _storagefactoryservice.StorageFactoryService
384
237
  ])
385
238
  ], UploadController);
@@ -149,7 +149,17 @@ _ts_decorate([
149
149
  _ts_metadata("design:type", String)
150
150
  ], UpdateFileManagerDto.prototype, "id", void 0);
151
151
  let FileManagerResponseDto = class FileManagerResponseDto extends UpdateFileManagerDto {
152
+ constructor(...args){
153
+ super(...args), _define_property(this, "providerName", void 0);
154
+ }
152
155
  };
156
+ _ts_decorate([
157
+ (0, _swagger.ApiPropertyOptional)({
158
+ example: 'My S3 Storage',
159
+ description: 'Name of the storage configuration/provider'
160
+ }),
161
+ _ts_metadata("design:type", String)
162
+ ], FileManagerResponseDto.prototype, "providerName", void 0);
153
163
  let GetFilesRequestDto = class GetFilesRequestDto {
154
164
  constructor(){
155
165
  _define_property(this, "id", void 0);
@@ -167,6 +177,9 @@ let FilesResponseDto = class FilesResponseDto {
167
177
  _define_property(this, "name", void 0);
168
178
  _define_property(this, "contentType", void 0);
169
179
  _define_property(this, "url", void 0);
180
+ _define_property(this, "location", void 0);
181
+ _define_property(this, "storageConfigId", void 0);
182
+ _define_property(this, "providerName", void 0);
170
183
  }
171
184
  };
172
185
  _ts_decorate([
@@ -183,13 +196,34 @@ _ts_decorate([
183
196
  ], FilesResponseDto.prototype, "name", void 0);
184
197
  _ts_decorate([
185
198
  (0, _swagger.ApiProperty)({
186
- example: 'file123.jpg'
199
+ example: 'image/jpeg'
187
200
  }),
188
201
  _ts_metadata("design:type", String)
189
202
  ], FilesResponseDto.prototype, "contentType", void 0);
190
203
  _ts_decorate([
191
204
  (0, _swagger.ApiProperty)({
192
- example: 'file123.jpg'
205
+ example: 'https://example.com/file123.jpg'
193
206
  }),
194
207
  _ts_metadata("design:type", String)
195
208
  ], FilesResponseDto.prototype, "url", void 0);
209
+ _ts_decorate([
210
+ (0, _swagger.ApiPropertyOptional)({
211
+ example: 'local',
212
+ description: 'Storage provider type (local, aws, azure, sftp)'
213
+ }),
214
+ _ts_metadata("design:type", String)
215
+ ], FilesResponseDto.prototype, "location", void 0);
216
+ _ts_decorate([
217
+ (0, _swagger.ApiPropertyOptional)({
218
+ example: '123e4567-e89b-12d3-a456-426614174000',
219
+ description: 'Storage configuration ID'
220
+ }),
221
+ _ts_metadata("design:type", String)
222
+ ], FilesResponseDto.prototype, "storageConfigId", void 0);
223
+ _ts_decorate([
224
+ (0, _swagger.ApiPropertyOptional)({
225
+ example: 'My S3 Storage',
226
+ description: 'Name of the storage configuration/provider'
227
+ }),
228
+ _ts_metadata("design:type", String)
229
+ ], FilesResponseDto.prototype, "providerName", void 0);
@@ -210,6 +210,8 @@ let FileUploadResponsePayloadDto = class FileUploadResponsePayloadDto {
210
210
  _define_property(this, "contentType", void 0);
211
211
  _define_property(this, "size", void 0);
212
212
  _define_property(this, "key", void 0);
213
+ _define_property(this, "location", void 0);
214
+ _define_property(this, "storageConfigId", void 0);
213
215
  }
214
216
  };
215
217
  _ts_decorate([
@@ -236,3 +238,17 @@ _ts_decorate([
236
238
  }),
237
239
  _ts_metadata("design:type", String)
238
240
  ], FileUploadResponsePayloadDto.prototype, "key", void 0);
241
+ _ts_decorate([
242
+ (0, _swagger.ApiProperty)({
243
+ example: 'local',
244
+ description: 'Storage provider type (local, aws, azure, sftp)'
245
+ }),
246
+ _ts_metadata("design:type", String)
247
+ ], FileUploadResponsePayloadDto.prototype, "location", void 0);
248
+ _ts_decorate([
249
+ (0, _swagger.ApiProperty)({
250
+ example: '123e4567-e89b-12d3-a456-426614174000',
251
+ description: 'Storage configuration ID used for this upload'
252
+ }),
253
+ _ts_metadata("design:type", String)
254
+ ], FileUploadResponsePayloadDto.prototype, "storageConfigId", void 0);
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "FileServeMiddleware", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return FileServeMiddleware;
9
+ }
10
+ });
11
+ const _common = require("@nestjs/common");
12
+ const _fs = require("fs");
13
+ const _path = require("path");
14
+ const _mimetypes = /*#__PURE__*/ _interop_require_wildcard(require("mime-types"));
15
+ const _uploadservice = require("../services/upload.service");
16
+ function _define_property(obj, key, value) {
17
+ if (key in obj) {
18
+ Object.defineProperty(obj, key, {
19
+ value: value,
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true
23
+ });
24
+ } else {
25
+ obj[key] = value;
26
+ }
27
+ return obj;
28
+ }
29
+ function _getRequireWildcardCache(nodeInterop) {
30
+ if (typeof WeakMap !== "function") return null;
31
+ var cacheBabelInterop = new WeakMap();
32
+ var cacheNodeInterop = new WeakMap();
33
+ return (_getRequireWildcardCache = function(nodeInterop) {
34
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
35
+ })(nodeInterop);
36
+ }
37
+ function _interop_require_wildcard(obj, nodeInterop) {
38
+ if (!nodeInterop && obj && obj.__esModule) {
39
+ return obj;
40
+ }
41
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
42
+ return {
43
+ default: obj
44
+ };
45
+ }
46
+ var cache = _getRequireWildcardCache(nodeInterop);
47
+ if (cache && cache.has(obj)) {
48
+ return cache.get(obj);
49
+ }
50
+ var newObj = {
51
+ __proto__: null
52
+ };
53
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
54
+ for(var key in obj){
55
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
56
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
57
+ if (desc && (desc.get || desc.set)) {
58
+ Object.defineProperty(newObj, key, desc);
59
+ } else {
60
+ newObj[key] = obj[key];
61
+ }
62
+ }
63
+ }
64
+ newObj.default = obj;
65
+ if (cache) {
66
+ cache.set(obj, newObj);
67
+ }
68
+ return newObj;
69
+ }
70
+ function _ts_decorate(decorators, target, key, desc) {
71
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
72
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
73
+ 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;
74
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
75
+ }
76
+ function _ts_metadata(k, v) {
77
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
78
+ }
79
+ let FileServeMiddleware = class FileServeMiddleware {
80
+ async use(req, res, next) {
81
+ try {
82
+ const uploadDir = process.cwd();
83
+ // Extract path after /storage/upload/file/
84
+ const urlPath = req.path || req.url;
85
+ const match = urlPath.match(/\/storage\/upload\/file\/(.+)/);
86
+ const normalizedPath = match ? match[1] : '';
87
+ if (!normalizedPath) {
88
+ throw new _common.NotFoundException('File path is required');
89
+ }
90
+ this.logger.debug(`Attempting to serve file, normalizedPath: ${normalizedPath}`);
91
+ // Get the local storage basePath for fallback lookups
92
+ const basePath = await this.uploadService.getLocalStorageBasePath();
93
+ const normalizedBasePath = basePath ? basePath.replace(/^\.\//, '').replace(/\/$/, '') : null;
94
+ // Strategy 1: Try path relative to CWD (new format: key includes basePath)
95
+ let fullPath = (0, _path.join)(uploadDir, normalizedPath);
96
+ let resolvedPath = (0, _path.resolve)(fullPath);
97
+ this.logger.debug(`Strategy 1 - CWD relative: ${fullPath}`);
98
+ // Validate path is inside the project directory (prevent path traversal)
99
+ if (!resolvedPath.startsWith(uploadDir)) {
100
+ throw new _common.NotFoundException('Invalid file path');
101
+ }
102
+ // Check if file exists
103
+ if (!(0, _fs.existsSync)(fullPath)) {
104
+ // Strategy 2: If key already starts with basePath, try using basePath directly
105
+ if (basePath && normalizedBasePath) {
106
+ const pathStartsWithBase = normalizedPath.startsWith(normalizedBasePath + '/') || normalizedPath.startsWith(normalizedBasePath);
107
+ if (pathStartsWithBase) {
108
+ const remainingPath = normalizedPath.substring(normalizedBasePath.length).replace(/^\//, '');
109
+ const fallbackPath = (0, _path.join)(basePath, remainingPath);
110
+ this.logger.debug(`Strategy 2 - basePath + remaining: ${fallbackPath}`);
111
+ if ((0, _fs.existsSync)(fallbackPath)) {
112
+ fullPath = fallbackPath;
113
+ resolvedPath = (0, _path.resolve)(fullPath);
114
+ }
115
+ } else {
116
+ // Old format: key doesn't include basePath, prepend it
117
+ const fallbackPath = (0, _path.join)(basePath, normalizedPath);
118
+ this.logger.debug(`Strategy 3 - basePath + full key (old format): ${fallbackPath}`);
119
+ if ((0, _fs.existsSync)(fallbackPath)) {
120
+ fullPath = fallbackPath;
121
+ resolvedPath = (0, _path.resolve)(fullPath);
122
+ }
123
+ }
124
+ }
125
+ // Final check if file was found
126
+ if (!(0, _fs.existsSync)(fullPath)) {
127
+ throw new _common.NotFoundException(`File not found: ${normalizedPath}`);
128
+ }
129
+ }
130
+ const stats = (0, _fs.statSync)(fullPath);
131
+ if (!stats.isFile()) {
132
+ throw new _common.NotFoundException(`Not a file: ${normalizedPath}`);
133
+ }
134
+ // Enhanced MIME type detection
135
+ let mimeType = _mimetypes.lookup(fullPath) || 'application/octet-stream';
136
+ // Special handling for SVG files
137
+ if (fullPath.toLowerCase().endsWith('.svg')) {
138
+ mimeType = 'image/svg+xml';
139
+ }
140
+ // Determine if file should be displayed inline or downloaded
141
+ const viewableTypes = [
142
+ 'image/',
143
+ 'video/',
144
+ 'audio/',
145
+ 'text/',
146
+ 'application/pdf',
147
+ 'application/json',
148
+ 'application/xml'
149
+ ];
150
+ const isViewable = viewableTypes.some((type)=>mimeType.startsWith(type));
151
+ const contentDisposition = isViewable ? 'inline' : `attachment; filename="${encodeURIComponent(normalizedPath.split('/').pop() || 'download')}"`;
152
+ // Set headers
153
+ res.set({
154
+ 'Content-Type': mimeType,
155
+ 'Content-Length': stats.size,
156
+ 'Content-Disposition': contentDisposition,
157
+ 'Cache-Control': 'public, max-age=3600',
158
+ 'Accept-Ranges': 'bytes',
159
+ 'Cross-Origin-Resource-Policy': 'cross-origin',
160
+ 'Access-Control-Allow-Origin': '*',
161
+ 'X-Content-Type-Options': 'nosniff'
162
+ });
163
+ // Stream the file
164
+ const stream = (0, _fs.createReadStream)(fullPath);
165
+ stream.pipe(res);
166
+ stream.on('error', (err)=>{
167
+ this.logger.error('File stream error', err);
168
+ if (!res.headersSent) {
169
+ res.status(404).json({
170
+ message: `Failed to serve file: ${normalizedPath}`,
171
+ error: 'Not Found',
172
+ statusCode: 404
173
+ });
174
+ }
175
+ });
176
+ // Handle client disconnection
177
+ res.on('close', ()=>{
178
+ stream.destroy();
179
+ });
180
+ } catch (error) {
181
+ this.logger.error('File retrieval error:', error);
182
+ if (!res.headersSent) {
183
+ res.status(404).json({
184
+ message: error instanceof _common.NotFoundException ? error.message : `File not found`,
185
+ error: 'Not Found',
186
+ statusCode: 404
187
+ });
188
+ }
189
+ }
190
+ }
191
+ constructor(uploadService){
192
+ _define_property(this, "uploadService", void 0);
193
+ _define_property(this, "logger", void 0);
194
+ this.uploadService = uploadService;
195
+ this.logger = new _common.Logger(FileServeMiddleware.name);
196
+ }
197
+ };
198
+ FileServeMiddleware = _ts_decorate([
199
+ (0, _common.Injectable)(),
200
+ _ts_metadata("design:type", Function),
201
+ _ts_metadata("design:paramtypes", [
202
+ typeof _uploadservice.UploadService === "undefined" ? Object : _uploadservice.UploadService
203
+ ])
204
+ ], FileServeMiddleware);
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ _export_star(require("./file-serve.middleware"), exports);
6
+ function _export_star(from, to) {
7
+ Object.keys(from).forEach(function(k) {
8
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
9
+ Object.defineProperty(to, k, {
10
+ enumerable: true,
11
+ get: function() {
12
+ return from[k];
13
+ }
14
+ });
15
+ }
16
+ });
17
+ return from;
18
+ }