@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.
- package/README.md +2 -0
- package/cjs/config/storage-config.service.js +31 -0
- package/cjs/controllers/file-manager.controller.js +1 -1
- package/cjs/controllers/upload.controller.js +22 -169
- package/cjs/dtos/file-manager.dto.js +36 -2
- package/cjs/dtos/upload.dto.js +16 -0
- package/cjs/middlewares/file-serve.middleware.js +204 -0
- package/cjs/middlewares/index.js +18 -0
- package/cjs/modules/storage.module.js +56 -10
- package/cjs/providers/azure-provider.optional.js +1 -2
- package/cjs/providers/local-provider.js +43 -11
- package/cjs/providers/storage-factory.service.js +49 -5
- package/cjs/services/file-manager.service.js +134 -9
- package/cjs/services/storage-provider-config.service.js +12 -0
- package/cjs/services/upload.service.js +135 -24
- package/cjs/utils/image-compressor.util.js +43 -5
- package/config/storage-config.service.d.ts +2 -0
- package/controllers/upload.controller.d.ts +5 -4
- package/dtos/file-manager.dto.d.ts +4 -0
- package/dtos/upload.dto.d.ts +2 -0
- package/fesm/config/storage-config.service.js +31 -0
- package/fesm/controllers/file-manager.controller.js +1 -1
- package/fesm/controllers/upload.controller.js +25 -131
- package/fesm/dtos/file-manager.dto.js +36 -2
- package/fesm/dtos/upload.dto.js +16 -0
- package/fesm/middlewares/file-serve.middleware.js +153 -0
- package/fesm/middlewares/index.js +1 -0
- package/fesm/modules/storage.module.js +58 -12
- package/fesm/providers/azure-provider.optional.js +1 -2
- package/fesm/providers/local-provider.js +43 -11
- package/fesm/providers/storage-factory.service.js +50 -6
- package/fesm/services/file-manager.service.js +134 -9
- package/fesm/services/storage-provider-config.service.js +12 -0
- package/fesm/services/upload.service.js +135 -24
- package/fesm/utils/image-compressor.util.js +3 -1
- package/interfaces/file-manager.interface.d.ts +2 -0
- package/interfaces/storage-module-options.interface.d.ts +2 -0
- package/interfaces/storage-provider.interface.d.ts +2 -0
- package/middlewares/file-serve.middleware.d.ts +9 -0
- package/middlewares/index.d.ts +1 -0
- package/modules/storage.module.d.ts +3 -2
- package/package.json +26 -11
- package/providers/local-provider.d.ts +2 -1
- package/providers/storage-factory.service.d.ts +4 -0
- package/services/file-manager.service.d.ts +7 -1
- package/services/storage-provider-config.service.d.ts +1 -0
- 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
|
|
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(
|
|
99
|
-
name:
|
|
100
|
-
key:
|
|
101
|
-
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
|
-
|
|
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, "
|
|
98
|
+
_define_property(this, "storageConfigService", void 0);
|
|
99
|
+
_define_property(this, "storageFactoryService", void 0);
|
|
209
100
|
this.uploadService = uploadService;
|
|
210
|
-
this.
|
|
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: '
|
|
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);
|
package/cjs/dtos/upload.dto.js
CHANGED
|
@@ -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
|
+
}
|