@flusys/nestjs-storage 0.1.0-beta.2 → 1.0.0-beta
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 +3 -7
- package/cjs/config/storage-config.service.js +31 -0
- package/cjs/controllers/file-manager.controller.js +8 -6
- package/cjs/controllers/folder.controller.js +3 -4
- package/cjs/controllers/storage-config.controller.js +3 -4
- 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 +58 -14
- 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/folder.service.js +17 -48
- package/cjs/services/storage-datasource.provider.js +10 -16
- package/cjs/services/storage-provider-config.service.js +26 -32
- 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/file-manager.controller.d.ts +1 -1
- 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 +8 -6
- package/fesm/controllers/folder.controller.js +5 -6
- package/fesm/controllers/storage-config.controller.js +5 -6
- 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 +60 -16
- 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/folder.service.js +18 -49
- package/fesm/services/storage-datasource.provider.js +10 -16
- package/fesm/services/storage-provider-config.service.js +26 -32
- 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/folder.service.d.ts +1 -2
- package/services/storage-provider-config.service.d.ts +2 -2
- package/services/upload.service.d.ts +6 -2
|
@@ -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
|
+
}
|
|
@@ -10,10 +10,13 @@ Object.defineProperty(exports, "StorageModule", {
|
|
|
10
10
|
});
|
|
11
11
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
12
12
|
const _common = require("@nestjs/common");
|
|
13
|
+
const _middlewares = require("../middlewares");
|
|
13
14
|
const _config = require("../config");
|
|
14
15
|
const _storageconstants = require("../config/storage.constants");
|
|
15
16
|
const _controllers = require("../controllers");
|
|
17
|
+
const _filelocationenum = require("../enums/file-location.enum");
|
|
16
18
|
const _providers = require("../providers");
|
|
19
|
+
const _localprovider = require("../providers/local-provider");
|
|
17
20
|
const _services = require("../services");
|
|
18
21
|
const _storageproviderconfigservice = require("../services/storage-provider-config.service");
|
|
19
22
|
function _ts_decorate(decorators, target, key, desc) {
|
|
@@ -22,8 +25,44 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
22
25
|
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;
|
|
23
26
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
24
27
|
}
|
|
28
|
+
// Auto-register built-in storage providers
|
|
29
|
+
const logger = new _common.Logger('StorageModule');
|
|
30
|
+
// Register LocalProvider (always available - uses Node.js built-in fs)
|
|
31
|
+
_providers.StorageProviderRegistry.register(_filelocationenum.FileLocationEnum.LOCAL, _localprovider.LocalProvider);
|
|
32
|
+
logger.log('Registered LocalProvider');
|
|
33
|
+
// Try to register optional providers (only if dependencies are installed)
|
|
34
|
+
try {
|
|
35
|
+
const { S3Provider } = require('../providers/s3-provider.optional');
|
|
36
|
+
_providers.StorageProviderRegistry.register(_filelocationenum.FileLocationEnum.AWS, S3Provider);
|
|
37
|
+
logger.log('Registered S3Provider');
|
|
38
|
+
} catch {
|
|
39
|
+
logger.debug('S3Provider not available (install @aws-sdk/client-s3 to enable)');
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const { AzureProvider } = require('../providers/azure-provider.optional');
|
|
43
|
+
_providers.StorageProviderRegistry.register(_filelocationenum.FileLocationEnum.AZURE, AzureProvider);
|
|
44
|
+
logger.log('Registered AzureProvider');
|
|
45
|
+
} catch {
|
|
46
|
+
logger.debug('AzureProvider not available (install @azure/storage-blob to enable)');
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const { SftpProvider } = require('../providers/sftp-provider.optional');
|
|
50
|
+
_providers.StorageProviderRegistry.register(_filelocationenum.FileLocationEnum.SFTP, SftpProvider);
|
|
51
|
+
logger.log('Registered SftpProvider');
|
|
52
|
+
} catch {
|
|
53
|
+
logger.debug('SftpProvider not available (install ssh2-sftp-client to enable)');
|
|
54
|
+
}
|
|
25
55
|
let StorageModule = class StorageModule {
|
|
26
56
|
/**
|
|
57
|
+
* Configure middleware for file serving
|
|
58
|
+
* This bypasses path-to-regexp issues with wildcard routes
|
|
59
|
+
*/ configure(consumer) {
|
|
60
|
+
consumer.apply(_middlewares.FileServeMiddleware).forRoutes({
|
|
61
|
+
path: 'storage/upload/file/*',
|
|
62
|
+
method: _common.RequestMethod.GET
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
27
66
|
* Register StorageModule synchronously
|
|
28
67
|
*/ static forRoot(options) {
|
|
29
68
|
const controllers = this.getControllers(options);
|
|
@@ -62,9 +101,10 @@ let StorageModule = class StorageModule {
|
|
|
62
101
|
_modules.UtilsModule
|
|
63
102
|
],
|
|
64
103
|
controllers: options.includeController !== false ? controllers : [],
|
|
104
|
+
// Pass false to exclude STORAGE_MODULE_OPTIONS - it's already in asyncProviders
|
|
65
105
|
providers: [
|
|
66
106
|
...asyncProviders,
|
|
67
|
-
...this.getProviders(options)
|
|
107
|
+
...this.getProviders(options, false)
|
|
68
108
|
],
|
|
69
109
|
exports: [
|
|
70
110
|
_config.StorageConfigService,
|
|
@@ -77,10 +117,8 @@ let StorageModule = class StorageModule {
|
|
|
77
117
|
]
|
|
78
118
|
};
|
|
79
119
|
}
|
|
80
|
-
//
|
|
81
|
-
/**
|
|
82
|
-
* Get controllers (all controllers always loaded)
|
|
83
|
-
*/ static getControllers(options) {
|
|
120
|
+
// Private Helper Methods
|
|
121
|
+
/** Get controllers (all controllers always loaded) */ static getControllers(options) {
|
|
84
122
|
return [
|
|
85
123
|
_controllers.FileManagerController,
|
|
86
124
|
_controllers.FolderController,
|
|
@@ -93,22 +131,28 @@ let StorageModule = class StorageModule {
|
|
|
93
131
|
* This ensures dynamic entity loading based on runtime configuration
|
|
94
132
|
*/ /**
|
|
95
133
|
* Get providers (all providers always loaded)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
useValue: options
|
|
101
|
-
};
|
|
102
|
-
return [
|
|
103
|
-
optionsProvider,
|
|
134
|
+
* @param options Module options
|
|
135
|
+
* @param includeOptionsProvider Whether to include the STORAGE_MODULE_OPTIONS provider (false for async registration)
|
|
136
|
+
*/ static getProviders(options, includeOptionsProvider = true) {
|
|
137
|
+
const providers = [
|
|
104
138
|
_config.StorageConfigService,
|
|
105
139
|
_services.StorageDataSourceProvider,
|
|
106
140
|
_services.FileManagerService,
|
|
107
141
|
_services.FolderService,
|
|
108
142
|
_storageproviderconfigservice.StorageProviderConfigService,
|
|
109
143
|
_services.UploadService,
|
|
110
|
-
_providers.StorageFactoryService
|
|
144
|
+
_providers.StorageFactoryService,
|
|
145
|
+
_middlewares.FileServeMiddleware
|
|
111
146
|
];
|
|
147
|
+
// Only include options provider for sync registration
|
|
148
|
+
// For async registration, createAsyncProviders handles it
|
|
149
|
+
if (includeOptionsProvider) {
|
|
150
|
+
providers.unshift({
|
|
151
|
+
provide: _storageconstants.STORAGE_MODULE_OPTIONS,
|
|
152
|
+
useValue: options
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return providers;
|
|
112
156
|
}
|
|
113
157
|
/**
|
|
114
158
|
* Create async providers for forRootAsync
|
|
@@ -101,7 +101,7 @@ let AzureProvider = class AzureProvider {
|
|
|
101
101
|
// Ensure container exists
|
|
102
102
|
await this.containerClient.createIfNotExists();
|
|
103
103
|
this.logger.log(`Azure Provider initialized: account=${config.accountName}, container=${config.containerName}`);
|
|
104
|
-
} catch (
|
|
104
|
+
} catch (_error) {
|
|
105
105
|
this.logger.error('Failed to initialize AzureProvider. Make sure @azure/storage-blob is installed.');
|
|
106
106
|
throw new Error('Azure Storage SDK not found. Install it with: npm install @azure/storage-blob');
|
|
107
107
|
}
|
|
@@ -129,7 +129,6 @@ let AzureProvider = class AzureProvider {
|
|
|
129
129
|
blobContentType: contentType
|
|
130
130
|
}
|
|
131
131
|
});
|
|
132
|
-
const url = blockBlobClient.url;
|
|
133
132
|
return {
|
|
134
133
|
name: fileName,
|
|
135
134
|
key: blobName,
|
|
@@ -81,14 +81,19 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
81
81
|
let LocalProvider = class LocalProvider {
|
|
82
82
|
/**
|
|
83
83
|
* Initialize Local File System provider with configuration
|
|
84
|
+
* @param config.basePath - Base path for file storage (default: './uploads')
|
|
85
|
+
* @param config.baseUrl - Optional base URL for generating file URLs
|
|
84
86
|
*/ async initialize(config) {
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
+
// Store original relative path for key generation
|
|
88
|
+
this.relativeBasePath = config?.basePath || './uploads';
|
|
89
|
+
// Resolve to absolute path for file operations
|
|
90
|
+
this.basePath = _path.resolve(this.relativeBasePath);
|
|
91
|
+
this.baseUrl = config?.baseUrl || '';
|
|
87
92
|
// Ensure base directory exists
|
|
88
93
|
await _promises.mkdir(this.basePath, {
|
|
89
94
|
recursive: true
|
|
90
95
|
});
|
|
91
|
-
this.logger.log(`Local Provider initialized: ${this.basePath}`);
|
|
96
|
+
this.logger.log(`Local Provider initialized: ${this.basePath} (relative: ${this.relativeBasePath})`);
|
|
92
97
|
}
|
|
93
98
|
async uploadFile(file, options) {
|
|
94
99
|
let processedBuffer = file.buffer;
|
|
@@ -114,8 +119,9 @@ let LocalProvider = class LocalProvider {
|
|
|
114
119
|
});
|
|
115
120
|
// Write file to disk
|
|
116
121
|
await _promises.writeFile(filePath, processedBuffer);
|
|
117
|
-
// Generate
|
|
118
|
-
|
|
122
|
+
// Generate key that includes the base path (relative to cwd)
|
|
123
|
+
// This makes the key self-contained for serving files
|
|
124
|
+
const relativeKey = _path.join(this.relativeBasePath, options.folderPath || '', fileName).replace(/\\/g, '/'); // Normalize path separators
|
|
119
125
|
this.logger.log(`Uploaded file to local storage: ${relativeKey}`);
|
|
120
126
|
return {
|
|
121
127
|
name: fileName,
|
|
@@ -129,7 +135,24 @@ let LocalProvider = class LocalProvider {
|
|
|
129
135
|
}
|
|
130
136
|
async deleteFile(key) {
|
|
131
137
|
try {
|
|
132
|
-
|
|
138
|
+
// Key now includes the basePath, resolve from cwd
|
|
139
|
+
let filePath = _path.resolve(key);
|
|
140
|
+
// Check if file exists at the resolved path
|
|
141
|
+
try {
|
|
142
|
+
await _promises.access(filePath);
|
|
143
|
+
} catch {
|
|
144
|
+
// Fallback: try with basePath prefix (for old keys without basePath)
|
|
145
|
+
const fallbackPath = _path.join(this.basePath, key);
|
|
146
|
+
try {
|
|
147
|
+
await _promises.access(fallbackPath);
|
|
148
|
+
filePath = fallbackPath;
|
|
149
|
+
this.logger.debug(`Using fallback path for delete: ${fallbackPath}`);
|
|
150
|
+
} catch {
|
|
151
|
+
// File doesn't exist at either location
|
|
152
|
+
this.logger.warn(`File not found for deletion: ${key}`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
133
156
|
await _promises.unlink(filePath);
|
|
134
157
|
this.logger.log(`Deleted file from local storage: ${key}`);
|
|
135
158
|
} catch (_error) {
|
|
@@ -143,8 +166,13 @@ let LocalProvider = class LocalProvider {
|
|
|
143
166
|
async generatePresignedUrl(key, _expiresInSeconds) {
|
|
144
167
|
// Return public URL or relative path
|
|
145
168
|
// Note: Local storage doesn't support true presigned URLs
|
|
146
|
-
// If baseUrl is provided, return full URL, otherwise return relative path
|
|
147
|
-
|
|
169
|
+
// If baseUrl is provided, return full URL with file serving endpoint, otherwise return relative path
|
|
170
|
+
if (this.baseUrl) {
|
|
171
|
+
// Construct URL with the file serving endpoint
|
|
172
|
+
const baseUrlWithoutTrailingSlash = this.baseUrl.replace(/\/$/, '');
|
|
173
|
+
return `${baseUrlWithoutTrailingSlash}/storage/upload/file/${key}`;
|
|
174
|
+
}
|
|
175
|
+
return `/storage/upload/file/${key}`;
|
|
148
176
|
}
|
|
149
177
|
async healthCheck() {
|
|
150
178
|
try {
|
|
@@ -156,14 +184,16 @@ let LocalProvider = class LocalProvider {
|
|
|
156
184
|
}
|
|
157
185
|
/**
|
|
158
186
|
* Get the absolute path for a file key
|
|
187
|
+
* Key now includes basePath, so resolve from cwd
|
|
159
188
|
*/ getAbsolutePath(key) {
|
|
160
|
-
return _path.
|
|
189
|
+
return _path.resolve(key);
|
|
161
190
|
}
|
|
162
191
|
/**
|
|
163
192
|
* Check if a file exists
|
|
164
193
|
*/ async fileExists(key) {
|
|
165
194
|
try {
|
|
166
|
-
|
|
195
|
+
// Key now includes basePath, resolve from cwd
|
|
196
|
+
const filePath = _path.resolve(key);
|
|
167
197
|
await _promises.access(filePath);
|
|
168
198
|
return true;
|
|
169
199
|
} catch {
|
|
@@ -173,7 +203,8 @@ let LocalProvider = class LocalProvider {
|
|
|
173
203
|
/**
|
|
174
204
|
* Get file stats
|
|
175
205
|
*/ async getFileStats(key) {
|
|
176
|
-
|
|
206
|
+
// Key now includes basePath, resolve from cwd
|
|
207
|
+
const filePath = _path.resolve(key);
|
|
177
208
|
const stats = await _promises.stat(filePath);
|
|
178
209
|
return {
|
|
179
210
|
size: stats.size,
|
|
@@ -185,5 +216,6 @@ let LocalProvider = class LocalProvider {
|
|
|
185
216
|
_define_property(this, "logger", new _common.Logger(LocalProvider.name));
|
|
186
217
|
_define_property(this, "basePath", '');
|
|
187
218
|
_define_property(this, "baseUrl", '');
|
|
219
|
+
_define_property(this, "relativeBasePath", ''); // Path relative to cwd for key generation
|
|
188
220
|
}
|
|
189
221
|
};
|
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "StorageFactoryService", {
|
|
|
11
11
|
const _storageproviderregistry = require("./storage-provider.registry");
|
|
12
12
|
const _common = require("@nestjs/common");
|
|
13
13
|
const _crypto = /*#__PURE__*/ _interop_require_wildcard(require("crypto"));
|
|
14
|
+
const _config = require("../config");
|
|
14
15
|
function _define_property(obj, key, value) {
|
|
15
16
|
if (key in obj) {
|
|
16
17
|
Object.defineProperty(obj, key, {
|
|
@@ -71,6 +72,14 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
71
72
|
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;
|
|
72
73
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
73
74
|
}
|
|
75
|
+
function _ts_metadata(k, v) {
|
|
76
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
77
|
+
}
|
|
78
|
+
function _ts_param(paramIndex, decorator) {
|
|
79
|
+
return function(target, key) {
|
|
80
|
+
decorator(target, key, paramIndex);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
74
83
|
let StorageFactoryService = class StorageFactoryService {
|
|
75
84
|
/**
|
|
76
85
|
* Generate a stable cache key for provider configuration
|
|
@@ -100,7 +109,19 @@ let StorageFactoryService = class StorageFactoryService {
|
|
|
100
109
|
const instance = new ProviderClass();
|
|
101
110
|
// Initialize if method exists
|
|
102
111
|
if ('initialize' in instance && typeof instance.initialize === 'function') {
|
|
103
|
-
|
|
112
|
+
// For local provider, inject appUrl as fallback for baseUrl
|
|
113
|
+
let initConfig = config.config;
|
|
114
|
+
if (config.provider === 'local' && !config.config?.baseUrl) {
|
|
115
|
+
const appUrl = this.storageConfigService.getAppUrl();
|
|
116
|
+
if (appUrl) {
|
|
117
|
+
initConfig = {
|
|
118
|
+
...config.config,
|
|
119
|
+
baseUrl: appUrl
|
|
120
|
+
};
|
|
121
|
+
this.logger.debug(`Using appUrl from config as baseUrl: ${appUrl}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
await instance.initialize(initConfig);
|
|
104
125
|
}
|
|
105
126
|
// Cache the instance
|
|
106
127
|
this.providerCache.set(providerKey, instance);
|
|
@@ -142,6 +163,20 @@ let StorageFactoryService = class StorageFactoryService {
|
|
|
142
163
|
return _storageproviderregistry.StorageProviderRegistry.getAll();
|
|
143
164
|
}
|
|
144
165
|
/**
|
|
166
|
+
* Get the basePath from cached local provider
|
|
167
|
+
* Returns null if no local provider is cached
|
|
168
|
+
*/ getLocalProviderBasePath() {
|
|
169
|
+
// Find cached local provider
|
|
170
|
+
const localKey = Array.from(this.providerCache.keys()).find((key)=>key.startsWith('local'));
|
|
171
|
+
if (localKey) {
|
|
172
|
+
const provider = this.providerCache.get(localKey);
|
|
173
|
+
if (provider && 'basePath' in provider) {
|
|
174
|
+
return provider.basePath;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
145
180
|
* Cleanup all cached provider connections on module destroy
|
|
146
181
|
* Ensures SFTP connections and other resources are properly released
|
|
147
182
|
*/ async onModuleDestroy() {
|
|
@@ -157,11 +192,20 @@ let StorageFactoryService = class StorageFactoryService {
|
|
|
157
192
|
this.providerCache.clear();
|
|
158
193
|
this.logger.log('Storage provider cleanup complete');
|
|
159
194
|
}
|
|
160
|
-
constructor(){
|
|
161
|
-
_define_property(this, "
|
|
162
|
-
_define_property(this, "
|
|
195
|
+
constructor(storageConfigService){
|
|
196
|
+
_define_property(this, "storageConfigService", void 0);
|
|
197
|
+
_define_property(this, "logger", void 0);
|
|
198
|
+
_define_property(this, "providerCache", void 0);
|
|
199
|
+
this.storageConfigService = storageConfigService;
|
|
200
|
+
this.logger = new _common.Logger(StorageFactoryService.name);
|
|
201
|
+
this.providerCache = new Map();
|
|
163
202
|
}
|
|
164
203
|
};
|
|
165
204
|
StorageFactoryService = _ts_decorate([
|
|
166
|
-
(0, _common.Injectable)()
|
|
205
|
+
(0, _common.Injectable)(),
|
|
206
|
+
_ts_param(0, (0, _common.Inject)(_config.StorageConfigService)),
|
|
207
|
+
_ts_metadata("design:type", Function),
|
|
208
|
+
_ts_metadata("design:paramtypes", [
|
|
209
|
+
typeof _config.StorageConfigService === "undefined" ? Object : _config.StorageConfigService
|
|
210
|
+
])
|
|
167
211
|
], StorageFactoryService);
|