@flusys/nestjs-storage 1.0.0-rc → 2.0.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.
- package/README.md +44 -1
- package/cjs/config/index.js +0 -1
- package/cjs/config/storage.constants.js +0 -9
- package/cjs/controllers/upload.controller.js +12 -17
- package/cjs/docs/storage-swagger.config.js +24 -136
- package/cjs/dtos/file-manager.dto.js +64 -31
- package/cjs/dtos/folder.dto.js +15 -9
- package/cjs/dtos/storage-config.dto.js +4 -85
- package/cjs/dtos/upload.dto.js +17 -17
- package/cjs/entities/file-manager-with-company.entity.js +3 -4
- package/cjs/entities/file-manager.entity.js +71 -3
- package/cjs/entities/folder-with-company.entity.js +3 -4
- package/cjs/entities/folder.entity.js +19 -3
- package/cjs/entities/index.js +9 -10
- package/cjs/entities/storage-config-with-company.entity.js +3 -4
- package/cjs/entities/storage-config.entity.js +74 -3
- package/cjs/middlewares/file-serve.middleware.js +107 -100
- package/cjs/modules/storage.module.js +82 -136
- package/cjs/providers/azure-provider.optional.js +10 -38
- package/cjs/providers/local-provider.js +0 -43
- package/cjs/providers/s3-provider.optional.js +19 -40
- package/cjs/providers/storage-factory.service.js +54 -99
- package/cjs/providers/storage-provider.registry.js +8 -18
- package/cjs/services/file-manager.service.js +239 -337
- package/cjs/services/folder.service.js +3 -3
- package/cjs/services/index.js +1 -0
- package/cjs/{config → services}/storage-config.service.js +30 -79
- package/cjs/services/storage-datasource.provider.js +16 -26
- package/cjs/services/storage-provider-config.service.js +3 -3
- package/cjs/services/upload.service.js +33 -61
- package/cjs/utils/file-validator.util.js +54 -66
- package/cjs/utils/image-compressor.util.js +2 -5
- package/config/index.d.ts +0 -1
- package/config/storage.constants.d.ts +0 -6
- package/controllers/upload.controller.d.ts +1 -0
- package/dtos/file-manager.dto.d.ts +10 -1
- package/dtos/folder.dto.d.ts +3 -1
- package/dtos/storage-config.dto.d.ts +5 -8
- package/entities/file-manager-with-company.entity.d.ts +2 -2
- package/entities/file-manager.entity.d.ts +12 -2
- package/entities/folder-with-company.entity.d.ts +2 -2
- package/entities/folder.entity.d.ts +4 -2
- package/entities/index.d.ts +3 -4
- package/entities/storage-config-with-company.entity.d.ts +2 -2
- package/entities/storage-config.entity.d.ts +8 -2
- package/fesm/config/index.js +0 -1
- package/fesm/config/storage.constants.js +0 -6
- package/fesm/controllers/upload.controller.js +12 -17
- package/fesm/docs/storage-swagger.config.js +27 -142
- package/fesm/dtos/file-manager.dto.js +65 -32
- package/fesm/dtos/folder.dto.js +16 -10
- package/fesm/dtos/storage-config.dto.js +6 -87
- package/fesm/dtos/upload.dto.js +17 -18
- package/fesm/entities/file-manager-with-company.entity.js +3 -4
- package/fesm/entities/file-manager.entity.js +72 -4
- package/fesm/entities/folder-with-company.entity.js +3 -4
- package/fesm/entities/folder.entity.js +20 -4
- package/fesm/entities/index.js +4 -8
- package/fesm/entities/storage-config-with-company.entity.js +3 -4
- package/fesm/entities/storage-config.entity.js +75 -4
- package/fesm/middlewares/file-serve.middleware.js +107 -100
- package/fesm/modules/storage.module.js +83 -136
- package/fesm/providers/azure-provider.optional.js +11 -42
- package/fesm/providers/local-provider.js +0 -43
- package/fesm/providers/s3-provider.optional.js +20 -44
- package/fesm/providers/storage-factory.service.js +52 -97
- package/fesm/providers/storage-provider.registry.js +10 -20
- package/fesm/services/file-manager.service.js +237 -335
- package/fesm/services/folder.service.js +1 -1
- package/fesm/services/index.js +1 -0
- package/fesm/{config → services}/storage-config.service.js +30 -79
- package/fesm/services/storage-datasource.provider.js +16 -26
- package/fesm/services/storage-provider-config.service.js +1 -1
- package/fesm/services/upload.service.js +31 -59
- package/fesm/utils/file-validator.util.js +54 -66
- package/fesm/utils/image-compressor.util.js +2 -5
- package/interfaces/storage-module-options.interface.d.ts +0 -5
- package/middlewares/file-serve.middleware.d.ts +9 -1
- package/modules/storage.module.d.ts +1 -2
- package/package.json +3 -3
- package/providers/azure-provider.optional.d.ts +8 -6
- package/providers/local-provider.d.ts +0 -7
- package/providers/s3-provider.optional.d.ts +9 -7
- package/providers/storage-factory.service.d.ts +8 -9
- package/providers/storage-provider.registry.d.ts +4 -4
- package/services/file-manager.service.d.ts +21 -14
- package/services/folder.service.d.ts +4 -4
- package/services/index.d.ts +1 -0
- package/{config → services}/storage-config.service.d.ts +9 -10
- package/services/storage-datasource.provider.d.ts +3 -4
- package/services/storage-provider-config.service.d.ts +4 -4
- package/services/upload.service.d.ts +3 -2
- package/utils/file-validator.util.d.ts +3 -0
- package/cjs/entities/file-manager-base.entity.js +0 -115
- package/cjs/entities/folder-base.entity.js +0 -55
- package/cjs/entities/storage-config-base.entity.js +0 -93
- package/entities/file-manager-base.entity.d.ts +0 -13
- package/entities/folder-base.entity.d.ts +0 -5
- package/entities/storage-config-base.entity.d.ts +0 -9
- package/fesm/entities/file-manager-base.entity.js +0 -108
- package/fesm/entities/folder-base.entity.js +0 -48
- package/fesm/entities/storage-config-base.entity.js +0 -83
|
@@ -81,123 +81,130 @@ function _ts_param(paramIndex, decorator) {
|
|
|
81
81
|
decorator(target, key, paramIndex);
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
+
/** MIME type prefixes that can be displayed inline in browser */ const VIEWABLE_TYPE_PREFIXES = [
|
|
85
|
+
'image/',
|
|
86
|
+
'video/',
|
|
87
|
+
'audio/',
|
|
88
|
+
'text/',
|
|
89
|
+
'application/pdf',
|
|
90
|
+
'application/json',
|
|
91
|
+
'application/xml'
|
|
92
|
+
];
|
|
84
93
|
let FileServeMiddleware = class FileServeMiddleware {
|
|
85
94
|
async use(req, res, next) {
|
|
86
95
|
try {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const urlPath = req.path || req.url;
|
|
90
|
-
const match = urlPath.match(/\/storage\/upload\/file\/(.+)/);
|
|
91
|
-
const normalizedPath = match ? match[1] : '';
|
|
92
|
-
if (!normalizedPath) {
|
|
93
|
-
throw new _common.NotFoundException('File path is required');
|
|
94
|
-
}
|
|
95
|
-
this.logger.debug(`Attempting to serve file, normalizedPath: ${normalizedPath}`);
|
|
96
|
-
// Get the local storage basePath for fallback lookups
|
|
97
|
-
const basePath = await this.uploadService.getLocalStorageBasePath();
|
|
98
|
-
const normalizedBasePath = basePath ? basePath.replace(/^\.\//, '').replace(/\/$/, '') : null;
|
|
99
|
-
// Strategy 1: Try path relative to CWD (new format: key includes basePath)
|
|
100
|
-
let fullPath = (0, _path.join)(uploadDir, normalizedPath);
|
|
101
|
-
let resolvedPath = (0, _path.resolve)(fullPath);
|
|
102
|
-
this.logger.debug(`Strategy 1 - CWD relative: ${fullPath}`);
|
|
103
|
-
// Validate path is inside the project directory (prevent path traversal)
|
|
104
|
-
if (!resolvedPath.startsWith(uploadDir)) {
|
|
105
|
-
throw new _common.NotFoundException('Invalid file path');
|
|
106
|
-
}
|
|
107
|
-
// Check if file exists
|
|
108
|
-
if (!(0, _fs.existsSync)(fullPath)) {
|
|
109
|
-
// Strategy 2: If key already starts with basePath, try using basePath directly
|
|
110
|
-
if (basePath && normalizedBasePath) {
|
|
111
|
-
const pathStartsWithBase = normalizedPath.startsWith(normalizedBasePath + '/') || normalizedPath.startsWith(normalizedBasePath);
|
|
112
|
-
if (pathStartsWithBase) {
|
|
113
|
-
const remainingPath = normalizedPath.substring(normalizedBasePath.length).replace(/^\//, '');
|
|
114
|
-
const fallbackPath = (0, _path.join)(basePath, remainingPath);
|
|
115
|
-
this.logger.debug(`Strategy 2 - basePath + remaining: ${fallbackPath}`);
|
|
116
|
-
if ((0, _fs.existsSync)(fallbackPath)) {
|
|
117
|
-
fullPath = fallbackPath;
|
|
118
|
-
resolvedPath = (0, _path.resolve)(fullPath);
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
// Old format: key doesn't include basePath, prepend it
|
|
122
|
-
const fallbackPath = (0, _path.join)(basePath, normalizedPath);
|
|
123
|
-
this.logger.debug(`Strategy 3 - basePath + full key (old format): ${fallbackPath}`);
|
|
124
|
-
if ((0, _fs.existsSync)(fallbackPath)) {
|
|
125
|
-
fullPath = fallbackPath;
|
|
126
|
-
resolvedPath = (0, _path.resolve)(fullPath);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
// Final check if file was found
|
|
131
|
-
if (!(0, _fs.existsSync)(fullPath)) {
|
|
132
|
-
throw new _common.NotFoundException(`File not found: ${normalizedPath}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
96
|
+
const normalizedPath = this.extractFilePath(req);
|
|
97
|
+
const fullPath = await this.resolveFilePath(normalizedPath);
|
|
135
98
|
const stats = (0, _fs.statSync)(fullPath);
|
|
136
99
|
if (!stats.isFile()) {
|
|
137
100
|
throw new _common.NotFoundException(`Not a file: ${normalizedPath}`);
|
|
138
101
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (fullPath.toLowerCase().endsWith('.svg')) {
|
|
143
|
-
mimeType = 'image/svg+xml';
|
|
144
|
-
}
|
|
145
|
-
// Determine if file should be displayed inline or downloaded
|
|
146
|
-
const viewableTypes = [
|
|
147
|
-
'image/',
|
|
148
|
-
'video/',
|
|
149
|
-
'audio/',
|
|
150
|
-
'text/',
|
|
151
|
-
'application/pdf',
|
|
152
|
-
'application/json',
|
|
153
|
-
'application/xml'
|
|
154
|
-
];
|
|
155
|
-
const isViewable = viewableTypes.some((type)=>mimeType.startsWith(type));
|
|
156
|
-
const contentDisposition = isViewable ? 'inline' : `attachment; filename="${encodeURIComponent(normalizedPath.split('/').pop() || 'download')}"`;
|
|
157
|
-
// Set headers
|
|
158
|
-
res.set({
|
|
159
|
-
'Content-Type': mimeType,
|
|
160
|
-
'Content-Length': stats.size,
|
|
161
|
-
'Content-Disposition': contentDisposition,
|
|
162
|
-
'Cache-Control': 'public, max-age=3600',
|
|
163
|
-
'Accept-Ranges': 'bytes',
|
|
164
|
-
'Cross-Origin-Resource-Policy': 'cross-origin',
|
|
165
|
-
'Access-Control-Allow-Origin': '*',
|
|
166
|
-
'X-Content-Type-Options': 'nosniff'
|
|
167
|
-
});
|
|
168
|
-
// Stream the file
|
|
169
|
-
const stream = (0, _fs.createReadStream)(fullPath);
|
|
170
|
-
stream.pipe(res);
|
|
171
|
-
stream.on('error', (err)=>{
|
|
172
|
-
this.logger.error('File stream error', err);
|
|
173
|
-
if (!res.headersSent) {
|
|
174
|
-
res.status(404).json({
|
|
175
|
-
message: `Failed to serve file: ${normalizedPath}`,
|
|
176
|
-
error: 'Not Found',
|
|
177
|
-
statusCode: 404
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
// Handle client disconnection
|
|
182
|
-
res.on('close', ()=>{
|
|
183
|
-
stream.destroy();
|
|
184
|
-
});
|
|
102
|
+
const mimeType = this.getMimeType(fullPath);
|
|
103
|
+
this.setResponseHeaders(res, stats.size, mimeType, normalizedPath);
|
|
104
|
+
this.streamFile(res, fullPath, normalizedPath);
|
|
185
105
|
} catch (error) {
|
|
186
|
-
this.
|
|
106
|
+
this.sendErrorResponse(res, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Extract and validate file path from request URL */ extractFilePath(req) {
|
|
110
|
+
const urlPath = req.path || req.url;
|
|
111
|
+
const match = urlPath.match(/\/storage\/upload\/file\/(.+)/);
|
|
112
|
+
const normalizedPath = match ? match[1] : '';
|
|
113
|
+
if (!normalizedPath) {
|
|
114
|
+
throw new _common.NotFoundException('File path is required');
|
|
115
|
+
}
|
|
116
|
+
this.logger.debug(`Attempting to serve file, normalizedPath: ${normalizedPath}`);
|
|
117
|
+
return normalizedPath;
|
|
118
|
+
}
|
|
119
|
+
/** Resolve file path using multiple fallback strategies */ async resolveFilePath(normalizedPath) {
|
|
120
|
+
// Strategy 1: Path relative to CWD (new format: key includes basePath)
|
|
121
|
+
let fullPath = (0, _path.join)(this.uploadDir, normalizedPath);
|
|
122
|
+
const resolvedPath = (0, _path.resolve)(fullPath);
|
|
123
|
+
this.logger.debug(`Strategy 1 - CWD relative: ${fullPath}`);
|
|
124
|
+
// Prevent path traversal attacks
|
|
125
|
+
if (!resolvedPath.startsWith(this.uploadDir)) {
|
|
126
|
+
throw new _common.NotFoundException('Invalid file path');
|
|
127
|
+
}
|
|
128
|
+
if ((0, _fs.existsSync)(fullPath)) {
|
|
129
|
+
return fullPath;
|
|
130
|
+
}
|
|
131
|
+
// Try fallback strategies with basePath
|
|
132
|
+
const basePath = await this.uploadService.getLocalStorageBasePath();
|
|
133
|
+
if (basePath) {
|
|
134
|
+
const fallbackPath = this.tryFallbackPaths(normalizedPath, basePath);
|
|
135
|
+
if (fallbackPath) {
|
|
136
|
+
return fallbackPath;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw new _common.NotFoundException(`File not found: ${normalizedPath}`);
|
|
140
|
+
}
|
|
141
|
+
/** Try basePath-based fallback strategies */ tryFallbackPaths(normalizedPath, basePath) {
|
|
142
|
+
const normalizedBasePath = basePath.replace(/^\.\//, '').replace(/\/$/, '');
|
|
143
|
+
const pathStartsWithBase = normalizedPath.startsWith(normalizedBasePath + '/') || normalizedPath.startsWith(normalizedBasePath);
|
|
144
|
+
if (pathStartsWithBase) {
|
|
145
|
+
// Strategy 2: basePath + remaining path
|
|
146
|
+
const remainingPath = normalizedPath.substring(normalizedBasePath.length).replace(/^\//, '');
|
|
147
|
+
const fallbackPath = (0, _path.join)(basePath, remainingPath);
|
|
148
|
+
this.logger.debug(`Strategy 2 - basePath + remaining: ${fallbackPath}`);
|
|
149
|
+
if ((0, _fs.existsSync)(fallbackPath)) return fallbackPath;
|
|
150
|
+
} else {
|
|
151
|
+
// Strategy 3: Old format - prepend basePath
|
|
152
|
+
const fallbackPath = (0, _path.join)(basePath, normalizedPath);
|
|
153
|
+
this.logger.debug(`Strategy 3 - basePath + full key (old format): ${fallbackPath}`);
|
|
154
|
+
if ((0, _fs.existsSync)(fallbackPath)) return fallbackPath;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/** Get MIME type with SVG special handling */ getMimeType(fullPath) {
|
|
159
|
+
if (fullPath.toLowerCase().endsWith('.svg')) {
|
|
160
|
+
return 'image/svg+xml';
|
|
161
|
+
}
|
|
162
|
+
return _mimetypes.lookup(fullPath) || 'application/octet-stream';
|
|
163
|
+
}
|
|
164
|
+
/** Set response headers for file serving */ setResponseHeaders(res, size, mimeType, normalizedPath) {
|
|
165
|
+
const isViewable = VIEWABLE_TYPE_PREFIXES.some((type)=>mimeType.startsWith(type));
|
|
166
|
+
const filename = normalizedPath.split('/').pop() || 'download';
|
|
167
|
+
const contentDisposition = isViewable ? 'inline' : `attachment; filename="${encodeURIComponent(filename)}"`;
|
|
168
|
+
res.set({
|
|
169
|
+
'Content-Type': mimeType,
|
|
170
|
+
'Content-Length': size,
|
|
171
|
+
'Content-Disposition': contentDisposition,
|
|
172
|
+
'Cache-Control': 'public, max-age=3600',
|
|
173
|
+
'Accept-Ranges': 'bytes',
|
|
174
|
+
'Cross-Origin-Resource-Policy': 'cross-origin',
|
|
175
|
+
'Access-Control-Allow-Origin': '*',
|
|
176
|
+
'X-Content-Type-Options': 'nosniff'
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** Stream file to response with error handling */ streamFile(res, fullPath, normalizedPath) {
|
|
180
|
+
const stream = (0, _fs.createReadStream)(fullPath);
|
|
181
|
+
stream.pipe(res);
|
|
182
|
+
stream.on('error', (err)=>{
|
|
183
|
+
this.logger.error('File stream error', err);
|
|
187
184
|
if (!res.headersSent) {
|
|
188
|
-
|
|
189
|
-
message: error instanceof _common.NotFoundException ? error.message : `File not found`,
|
|
190
|
-
error: 'Not Found',
|
|
191
|
-
statusCode: 404
|
|
192
|
-
});
|
|
185
|
+
this.sendErrorResponse(res, new _common.NotFoundException(`Failed to serve file: ${normalizedPath}`));
|
|
193
186
|
}
|
|
187
|
+
});
|
|
188
|
+
res.on('close', ()=>stream.destroy());
|
|
189
|
+
}
|
|
190
|
+
/** Send consistent error response */ sendErrorResponse(res, error) {
|
|
191
|
+
this.logger.error('File retrieval error:', error);
|
|
192
|
+
if (!res.headersSent) {
|
|
193
|
+
const message = error instanceof _common.NotFoundException ? error.message : 'File not found';
|
|
194
|
+
res.status(404).json({
|
|
195
|
+
message,
|
|
196
|
+
error: 'Not Found',
|
|
197
|
+
statusCode: 404
|
|
198
|
+
});
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
constructor(uploadService){
|
|
197
202
|
_define_property(this, "uploadService", void 0);
|
|
198
203
|
_define_property(this, "logger", void 0);
|
|
204
|
+
_define_property(this, "uploadDir", void 0);
|
|
199
205
|
this.uploadService = uploadService;
|
|
200
206
|
this.logger = new _common.Logger(FileServeMiddleware.name);
|
|
207
|
+
this.uploadDir = process.cwd();
|
|
201
208
|
}
|
|
202
209
|
};
|
|
203
210
|
FileServeMiddleware = _ts_decorate([
|
|
@@ -11,13 +11,12 @@ Object.defineProperty(exports, "StorageModule", {
|
|
|
11
11
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
12
12
|
const _common = require("@nestjs/common");
|
|
13
13
|
const _middlewares = require("../middlewares");
|
|
14
|
-
const
|
|
14
|
+
const _services = require("../services");
|
|
15
15
|
const _storageconstants = require("../config/storage.constants");
|
|
16
16
|
const _controllers = require("../controllers");
|
|
17
17
|
const _filelocationenum = require("../enums/file-location.enum");
|
|
18
18
|
const _providers = require("../providers");
|
|
19
19
|
const _localprovider = require("../providers/local-provider");
|
|
20
|
-
const _services = require("../services");
|
|
21
20
|
const _storageproviderconfigservice = require("../services/storage-provider-config.service");
|
|
22
21
|
function _ts_decorate(decorators, target, key, desc) {
|
|
23
22
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -25,48 +24,62 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
25
24
|
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;
|
|
26
25
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
27
26
|
}
|
|
28
|
-
//
|
|
27
|
+
// ─── Module Constants ─────────────────────────────────────────────────────────
|
|
29
28
|
const logger = new _common.Logger('StorageModule');
|
|
30
|
-
|
|
29
|
+
const CONTROLLERS = [
|
|
30
|
+
_controllers.FileManagerController,
|
|
31
|
+
_controllers.FolderController,
|
|
32
|
+
_controllers.StorageConfigController,
|
|
33
|
+
_controllers.UploadController
|
|
34
|
+
];
|
|
35
|
+
const EXPORTED_SERVICES = [
|
|
36
|
+
_services.StorageConfigService,
|
|
37
|
+
_services.StorageDataSourceProvider,
|
|
38
|
+
_services.FileManagerService,
|
|
39
|
+
_services.FolderService,
|
|
40
|
+
_storageproviderconfigservice.StorageProviderConfigService,
|
|
41
|
+
_services.UploadService,
|
|
42
|
+
_providers.StorageFactoryService
|
|
43
|
+
];
|
|
44
|
+
// ─── Provider Registration ────────────────────────────────────────────────────
|
|
31
45
|
_providers.StorageProviderRegistry.register(_filelocationenum.FileLocationEnum.LOCAL, _localprovider.LocalProvider);
|
|
32
46
|
logger.log('Registered LocalProvider');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
const OPTIONAL_PROVIDERS = [
|
|
48
|
+
{
|
|
49
|
+
location: _filelocationenum.FileLocationEnum.AWS,
|
|
50
|
+
path: '../providers/s3-provider.optional',
|
|
51
|
+
name: 'S3Provider',
|
|
52
|
+
dep: '@aws-sdk/client-s3'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
location: _filelocationenum.FileLocationEnum.AZURE,
|
|
56
|
+
path: '../providers/azure-provider.optional',
|
|
57
|
+
name: 'AzureProvider',
|
|
58
|
+
dep: '@azure/storage-blob'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
location: _filelocationenum.FileLocationEnum.SFTP,
|
|
62
|
+
path: '../providers/sftp-provider.optional',
|
|
63
|
+
name: 'SftpProvider',
|
|
64
|
+
dep: 'ssh2-sftp-client'
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
for (const { location, path, name, dep } of OPTIONAL_PROVIDERS){
|
|
68
|
+
try {
|
|
69
|
+
_providers.StorageProviderRegistry.register(location, require(path)[name]);
|
|
70
|
+
logger.log(`Registered ${name}`);
|
|
71
|
+
} catch {
|
|
72
|
+
logger.debug(`${name} not available (install ${dep} to enable)`);
|
|
73
|
+
}
|
|
54
74
|
}
|
|
55
75
|
let StorageModule = class StorageModule {
|
|
56
|
-
|
|
57
|
-
* Configure middleware for file serving
|
|
58
|
-
* This bypasses path-to-regexp issues with wildcard routes
|
|
59
|
-
*/ configure(consumer) {
|
|
76
|
+
configure(consumer) {
|
|
60
77
|
consumer.apply(_middlewares.FileServeMiddleware).forRoutes({
|
|
61
78
|
path: 'storage/upload/file/*',
|
|
62
79
|
method: _common.RequestMethod.GET
|
|
63
80
|
});
|
|
64
81
|
}
|
|
65
|
-
|
|
66
|
-
* Register StorageModule synchronously
|
|
67
|
-
*/ static forRoot(options) {
|
|
68
|
-
const controllers = this.getControllers(options);
|
|
69
|
-
const providers = this.getProviders(options);
|
|
82
|
+
static forRoot(options) {
|
|
70
83
|
return {
|
|
71
84
|
module: StorageModule,
|
|
72
85
|
global: options.global ?? false,
|
|
@@ -74,24 +87,12 @@ let StorageModule = class StorageModule {
|
|
|
74
87
|
_modules.CacheModule,
|
|
75
88
|
_modules.UtilsModule
|
|
76
89
|
],
|
|
77
|
-
controllers: options.includeController !== false ?
|
|
78
|
-
providers,
|
|
79
|
-
exports:
|
|
80
|
-
_config.StorageConfigService,
|
|
81
|
-
_services.StorageDataSourceProvider,
|
|
82
|
-
_services.FileManagerService,
|
|
83
|
-
_services.FolderService,
|
|
84
|
-
_storageproviderconfigservice.StorageProviderConfigService,
|
|
85
|
-
_services.UploadService,
|
|
86
|
-
_providers.StorageFactoryService
|
|
87
|
-
]
|
|
90
|
+
controllers: options.includeController !== false ? CONTROLLERS : [],
|
|
91
|
+
providers: this.buildProviders(options),
|
|
92
|
+
exports: EXPORTED_SERVICES
|
|
88
93
|
};
|
|
89
94
|
}
|
|
90
|
-
|
|
91
|
-
* Register StorageModule asynchronously
|
|
92
|
-
*/ static forRootAsync(options) {
|
|
93
|
-
const controllers = this.getControllers(options);
|
|
94
|
-
const asyncProviders = this.createAsyncProviders(options);
|
|
95
|
+
static forRootAsync(options) {
|
|
95
96
|
return {
|
|
96
97
|
module: StorageModule,
|
|
97
98
|
global: options.global ?? false,
|
|
@@ -100,53 +101,21 @@ let StorageModule = class StorageModule {
|
|
|
100
101
|
_modules.CacheModule,
|
|
101
102
|
_modules.UtilsModule
|
|
102
103
|
],
|
|
103
|
-
controllers: options.includeController !== false ?
|
|
104
|
-
// Pass false to exclude STORAGE_MODULE_OPTIONS - it's already in asyncProviders
|
|
104
|
+
controllers: options.includeController !== false ? CONTROLLERS : [],
|
|
105
105
|
providers: [
|
|
106
|
-
...
|
|
107
|
-
...this.
|
|
106
|
+
...this.createAsyncProviders(options),
|
|
107
|
+
...this.buildProviders()
|
|
108
108
|
],
|
|
109
|
-
exports:
|
|
110
|
-
_config.StorageConfigService,
|
|
111
|
-
_services.StorageDataSourceProvider,
|
|
112
|
-
_services.FileManagerService,
|
|
113
|
-
_services.FolderService,
|
|
114
|
-
_storageproviderconfigservice.StorageProviderConfigService,
|
|
115
|
-
_services.UploadService,
|
|
116
|
-
_providers.StorageFactoryService
|
|
117
|
-
]
|
|
109
|
+
exports: EXPORTED_SERVICES
|
|
118
110
|
};
|
|
119
111
|
}
|
|
120
|
-
// Private
|
|
121
|
-
|
|
122
|
-
return [
|
|
123
|
-
_controllers.FileManagerController,
|
|
124
|
-
_controllers.FolderController,
|
|
125
|
-
_controllers.StorageConfigController,
|
|
126
|
-
_controllers.UploadController
|
|
127
|
-
];
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* NOTE: Repository providers removed - services now use StorageDataSourceProvider directly
|
|
131
|
-
* This ensures dynamic entity loading based on runtime configuration
|
|
132
|
-
*/ /**
|
|
133
|
-
* Get providers (all providers always loaded)
|
|
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) {
|
|
112
|
+
// ─── Private Helpers ──────────────────────────────────────────────────────────
|
|
113
|
+
static buildProviders(options) {
|
|
137
114
|
const providers = [
|
|
138
|
-
|
|
139
|
-
_services.StorageDataSourceProvider,
|
|
140
|
-
_services.FileManagerService,
|
|
141
|
-
_services.FolderService,
|
|
142
|
-
_storageproviderconfigservice.StorageProviderConfigService,
|
|
143
|
-
_services.UploadService,
|
|
144
|
-
_providers.StorageFactoryService,
|
|
115
|
+
...EXPORTED_SERVICES,
|
|
145
116
|
_middlewares.FileServeMiddleware
|
|
146
117
|
];
|
|
147
|
-
|
|
148
|
-
// For async registration, createAsyncProviders handles it
|
|
149
|
-
if (includeOptionsProvider) {
|
|
118
|
+
if (options) {
|
|
150
119
|
providers.unshift({
|
|
151
120
|
provide: _storageconstants.STORAGE_MODULE_OPTIONS,
|
|
152
121
|
useValue: options
|
|
@@ -154,63 +123,40 @@ let StorageModule = class StorageModule {
|
|
|
154
123
|
}
|
|
155
124
|
return providers;
|
|
156
125
|
}
|
|
157
|
-
|
|
158
|
-
* Create async providers for forRootAsync
|
|
159
|
-
*/ static createAsyncProviders(options) {
|
|
126
|
+
static createAsyncProviders(options) {
|
|
160
127
|
if (options.useFactory) {
|
|
161
128
|
return [
|
|
162
129
|
{
|
|
163
130
|
provide: _storageconstants.STORAGE_MODULE_OPTIONS,
|
|
164
|
-
useFactory: async (...args)=>{
|
|
165
|
-
const config = await options.useFactory(...args);
|
|
166
|
-
return {
|
|
131
|
+
useFactory: async (...args)=>({
|
|
167
132
|
...options,
|
|
168
|
-
config
|
|
169
|
-
}
|
|
170
|
-
},
|
|
133
|
+
config: await options.useFactory(...args)
|
|
134
|
+
}),
|
|
171
135
|
inject: options.inject || []
|
|
172
136
|
}
|
|
173
137
|
];
|
|
174
138
|
}
|
|
139
|
+
const factoryClass = options.useClass || options.useExisting;
|
|
140
|
+
if (!factoryClass) return [];
|
|
141
|
+
const providers = [
|
|
142
|
+
{
|
|
143
|
+
provide: _storageconstants.STORAGE_MODULE_OPTIONS,
|
|
144
|
+
useFactory: async (factory)=>({
|
|
145
|
+
...options,
|
|
146
|
+
config: await factory.createStorageOptions()
|
|
147
|
+
}),
|
|
148
|
+
inject: [
|
|
149
|
+
factoryClass
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
];
|
|
175
153
|
if (options.useClass) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const config = await optionsFactory.createStorageOptions();
|
|
181
|
-
return {
|
|
182
|
-
...options,
|
|
183
|
-
config
|
|
184
|
-
};
|
|
185
|
-
},
|
|
186
|
-
inject: [
|
|
187
|
-
options.useClass
|
|
188
|
-
]
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
provide: options.useClass,
|
|
192
|
-
useClass: options.useClass
|
|
193
|
-
}
|
|
194
|
-
];
|
|
195
|
-
}
|
|
196
|
-
if (options.useExisting) {
|
|
197
|
-
return [
|
|
198
|
-
{
|
|
199
|
-
provide: _storageconstants.STORAGE_MODULE_OPTIONS,
|
|
200
|
-
useFactory: async (optionsFactory)=>{
|
|
201
|
-
const config = await optionsFactory.createStorageOptions();
|
|
202
|
-
return {
|
|
203
|
-
...options,
|
|
204
|
-
config
|
|
205
|
-
};
|
|
206
|
-
},
|
|
207
|
-
inject: [
|
|
208
|
-
options.useExisting
|
|
209
|
-
]
|
|
210
|
-
}
|
|
211
|
-
];
|
|
154
|
+
providers.push({
|
|
155
|
+
provide: options.useClass,
|
|
156
|
+
useClass: options.useClass
|
|
157
|
+
});
|
|
212
158
|
}
|
|
213
|
-
return
|
|
159
|
+
return providers;
|
|
214
160
|
}
|
|
215
161
|
};
|
|
216
162
|
StorageModule = _ts_decorate([
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* This provider requires @azure/storage-blob package to be installed.
|
|
5
|
-
* Only import this if you need Azure Blob storage.
|
|
6
|
-
*
|
|
7
|
-
* Installation:
|
|
8
|
-
* npm install @azure/storage-blob
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* import { AzureProvider } from '@flusys/nestjs-storage/providers/azure-provider.optional';
|
|
12
|
-
* import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
|
|
13
|
-
*
|
|
14
|
-
* StorageProviderRegistry.register(FileLocationEnum.AZURE, AzureProvider);
|
|
2
|
+
* Optional Azure Blob Storage Provider
|
|
3
|
+
* Requires: npm install @azure/storage-blob
|
|
15
4
|
*/ "use strict";
|
|
16
5
|
Object.defineProperty(exports, "__esModule", {
|
|
17
6
|
value: true
|
|
@@ -22,9 +11,9 @@ Object.defineProperty(exports, "AzureProvider", {
|
|
|
22
11
|
return AzureProvider;
|
|
23
12
|
}
|
|
24
13
|
});
|
|
25
|
-
const _imagecompressorutil = require("../utils/image-compressor.util");
|
|
26
14
|
const _common = require("@nestjs/common");
|
|
27
15
|
const _uuid = require("uuid");
|
|
16
|
+
const _imagecompressorutil = require("../utils/image-compressor.util");
|
|
28
17
|
function _define_property(obj, key, value) {
|
|
29
18
|
if (key in obj) {
|
|
30
19
|
Object.defineProperty(obj, key, {
|
|
@@ -80,37 +69,23 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
80
69
|
return newObj;
|
|
81
70
|
}
|
|
82
71
|
let AzureProvider = class AzureProvider {
|
|
83
|
-
|
|
84
|
-
* Initialize Azure provider with configuration
|
|
85
|
-
*/ async initialize(config) {
|
|
72
|
+
async initialize(config) {
|
|
86
73
|
try {
|
|
87
|
-
|
|
88
|
-
const { BlobServiceClient } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@azure/storage-blob")));
|
|
74
|
+
const { BlobServiceClient, StorageSharedKeyCredential } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@azure/storage-blob")));
|
|
89
75
|
this.accountName = config.accountName;
|
|
90
76
|
this.containerName = config.containerName;
|
|
91
|
-
|
|
92
|
-
if (config.connectionString) {
|
|
93
|
-
this.blobServiceClient = BlobServiceClient.fromConnectionString(config.connectionString);
|
|
94
|
-
} else {
|
|
95
|
-
const { StorageSharedKeyCredential } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@azure/storage-blob")));
|
|
96
|
-
const sharedKeyCredential = new StorageSharedKeyCredential(config.accountName, config.accountKey);
|
|
97
|
-
this.blobServiceClient = new BlobServiceClient(`https://${config.accountName}.blob.core.windows.net`, sharedKeyCredential);
|
|
98
|
-
}
|
|
99
|
-
// Get container client
|
|
77
|
+
this.blobServiceClient = config.connectionString ? BlobServiceClient.fromConnectionString(config.connectionString) : new BlobServiceClient(`https://${config.accountName}.blob.core.windows.net`, new StorageSharedKeyCredential(config.accountName, config.accountKey));
|
|
100
78
|
this.containerClient = this.blobServiceClient.getContainerClient(config.containerName);
|
|
101
|
-
// Ensure container exists
|
|
102
79
|
await this.containerClient.createIfNotExists();
|
|
103
80
|
this.logger.log(`Azure Provider initialized: account=${config.accountName}, container=${config.containerName}`);
|
|
104
|
-
} catch
|
|
105
|
-
|
|
106
|
-
throw new Error('Azure Storage SDK not found. Install it with: npm install @azure/storage-blob');
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error('Azure Storage SDK not found. Install: npm install @azure/storage-blob');
|
|
107
83
|
}
|
|
108
84
|
}
|
|
109
85
|
async uploadFile(file, options) {
|
|
110
86
|
let processedBuffer = file.buffer;
|
|
111
87
|
let contentType = file.mimetype;
|
|
112
88
|
const fileName = `${(0, _uuid.v4)()}-${file.originalname}`;
|
|
113
|
-
// Compress image if needed
|
|
114
89
|
if (options.compress && file.mimetype.startsWith('image/')) {
|
|
115
90
|
const result = await _imagecompressorutil.ImageCompressor.compress(file.buffer, file.mimetype, {
|
|
116
91
|
maxWidth: options.maxWidth,
|
|
@@ -122,9 +97,7 @@ let AzureProvider = class AzureProvider {
|
|
|
122
97
|
contentType = result.format;
|
|
123
98
|
}
|
|
124
99
|
const blobName = options.folderPath ? `${options.folderPath}/${fileName}` : fileName;
|
|
125
|
-
|
|
126
|
-
// Upload to Azure
|
|
127
|
-
await blockBlobClient.upload(processedBuffer, processedBuffer.length, {
|
|
100
|
+
await this.containerClient.getBlockBlobClient(blobName).upload(processedBuffer, processedBuffer.length, {
|
|
128
101
|
blobHTTPHeaders: {
|
|
129
102
|
blobContentType: contentType
|
|
130
103
|
}
|
|
@@ -140,8 +113,7 @@ let AzureProvider = class AzureProvider {
|
|
|
140
113
|
return Promise.all(files.map((file)=>this.uploadFile(file, options)));
|
|
141
114
|
}
|
|
142
115
|
async deleteFile(key) {
|
|
143
|
-
|
|
144
|
-
await blockBlobClient.deleteIfExists();
|
|
116
|
+
await this.containerClient.getBlockBlobClient(key).deleteIfExists();
|
|
145
117
|
this.logger.log(`Deleted file from Azure: ${key}`);
|
|
146
118
|
}
|
|
147
119
|
async deleteMultipleFiles(keys) {
|
|
@@ -219,49 +219,6 @@ let LocalProvider = class LocalProvider {
|
|
|
219
219
|
return false;
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
-
/**
|
|
223
|
-
* Get the absolute path for a file key
|
|
224
|
-
* Key now includes basePath, so resolve from cwd
|
|
225
|
-
*/ getAbsolutePath(key) {
|
|
226
|
-
// SECURITY: Validate key format
|
|
227
|
-
this.validateKeyFormat(key);
|
|
228
|
-
const filePath = _path.resolve(key);
|
|
229
|
-
// SECURITY: Validate path is within base directory
|
|
230
|
-
this.validatePathWithinBase(filePath);
|
|
231
|
-
return filePath;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Check if a file exists
|
|
235
|
-
*/ async fileExists(key) {
|
|
236
|
-
try {
|
|
237
|
-
// SECURITY: Validate key format
|
|
238
|
-
this.validateKeyFormat(key);
|
|
239
|
-
// Key now includes basePath, resolve from cwd
|
|
240
|
-
const filePath = _path.resolve(key);
|
|
241
|
-
// SECURITY: Validate path is within base directory
|
|
242
|
-
this.validatePathWithinBase(filePath);
|
|
243
|
-
await _promises.access(filePath);
|
|
244
|
-
return true;
|
|
245
|
-
} catch {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Get file stats
|
|
251
|
-
*/ async getFileStats(key) {
|
|
252
|
-
// SECURITY: Validate key format
|
|
253
|
-
this.validateKeyFormat(key);
|
|
254
|
-
// Key now includes basePath, resolve from cwd
|
|
255
|
-
const filePath = _path.resolve(key);
|
|
256
|
-
// SECURITY: Validate path is within base directory
|
|
257
|
-
this.validatePathWithinBase(filePath);
|
|
258
|
-
const stats = await _promises.stat(filePath);
|
|
259
|
-
return {
|
|
260
|
-
size: stats.size,
|
|
261
|
-
createdAt: stats.birthtime,
|
|
262
|
-
modifiedAt: stats.mtime
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
222
|
constructor(){
|
|
266
223
|
_define_property(this, "logger", new _common.Logger(LocalProvider.name));
|
|
267
224
|
_define_property(this, "basePath", '');
|