@flusys/nestjs-storage 0.1.0-beta.3 → 1.0.0-rc
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 +131 -19
- package/cjs/config/storage-config.service.js +5 -0
- package/cjs/config/storage.constants.js +0 -8
- package/cjs/controllers/file-manager.controller.js +50 -5
- package/cjs/controllers/folder.controller.js +46 -4
- package/cjs/controllers/storage-config.controller.js +46 -4
- package/cjs/controllers/upload.controller.js +6 -12
- package/cjs/dtos/file-manager.dto.js +8 -5
- package/cjs/dtos/storage-config.dto.js +41 -1
- package/cjs/dtos/upload.dto.js +7 -0
- package/cjs/entities/storage-config-base.entity.js +31 -2
- package/cjs/interfaces/index.js +0 -1
- package/cjs/middlewares/file-serve.middleware.js +6 -0
- package/cjs/modules/storage.module.js +2 -4
- package/cjs/providers/local-provider.js +52 -2
- package/cjs/providers/storage-factory.service.js +2 -2
- package/cjs/services/file-manager.service.js +37 -24
- package/cjs/services/folder.service.js +18 -52
- package/cjs/services/storage-datasource.provider.js +10 -16
- package/cjs/services/storage-provider-config.service.js +28 -63
- package/cjs/services/upload.service.js +39 -27
- package/cjs/utils/file-validator.util.js +470 -0
- package/cjs/utils/image-compressor.util.js +1 -3
- package/config/storage-config.service.d.ts +5 -2
- package/config/storage.constants.d.ts +0 -2
- package/controllers/file-manager.controller.d.ts +1 -1
- package/controllers/upload.controller.d.ts +2 -6
- package/dtos/file-manager.dto.d.ts +2 -4
- package/dtos/folder.dto.d.ts +2 -4
- package/dtos/storage-config.dto.d.ts +9 -6
- package/entities/storage-config-base.entity.d.ts +2 -0
- package/fesm/config/storage-config.service.js +5 -0
- package/fesm/config/storage.constants.js +0 -2
- package/fesm/controllers/file-manager.controller.js +51 -6
- package/fesm/controllers/folder.controller.js +49 -7
- package/fesm/controllers/storage-config.controller.js +49 -7
- package/fesm/controllers/upload.controller.js +7 -13
- package/fesm/dtos/file-manager.dto.js +8 -5
- package/fesm/dtos/storage-config.dto.js +45 -11
- package/fesm/dtos/upload.dto.js +8 -1
- package/fesm/entities/index.js +1 -5
- package/fesm/entities/storage-config-base.entity.js +33 -7
- package/fesm/interfaces/index.js +0 -1
- package/fesm/interfaces/storage-config.interface.js +1 -3
- package/fesm/middlewares/file-serve.middleware.js +7 -1
- package/fesm/modules/storage.module.js +2 -4
- package/fesm/providers/local-provider.js +52 -2
- package/fesm/providers/storage-factory.service.js +2 -2
- package/fesm/services/file-manager.service.js +38 -25
- package/fesm/services/folder.service.js +19 -53
- package/fesm/services/storage-datasource.provider.js +10 -16
- package/fesm/services/storage-provider-config.service.js +28 -63
- package/fesm/services/upload.service.js +40 -28
- package/fesm/utils/file-validator.util.js +463 -0
- package/fesm/utils/image-compressor.util.js +1 -3
- package/interfaces/file-manager.interface.d.ts +7 -4
- package/interfaces/index.d.ts +0 -1
- package/interfaces/storage-config.interface.d.ts +2 -20
- package/package.json +6 -6
- package/providers/local-provider.d.ts +2 -0
- package/services/file-manager.service.d.ts +2 -2
- package/services/folder.service.d.ts +1 -2
- package/services/storage-provider-config.service.d.ts +1 -2
- package/utils/file-validator.util.d.ts +16 -0
- package/cjs/interfaces/file-upload-response.interface.js +0 -4
- package/fesm/interfaces/file-upload-response.interface.js +0 -1
- package/interfaces/file-upload-response.interface.d.ts +0 -6
package/cjs/dtos/upload.dto.js
CHANGED
|
@@ -68,6 +68,8 @@ _ts_decorate([
|
|
|
68
68
|
description: 'Key of the file to delete',
|
|
69
69
|
example: '/uploads/some path'
|
|
70
70
|
}),
|
|
71
|
+
(0, _classvalidator.IsNotEmpty)(),
|
|
72
|
+
(0, _classvalidator.IsString)(),
|
|
71
73
|
_ts_metadata("design:type", String)
|
|
72
74
|
], DeleteSingleFileDto.prototype, "key", void 0);
|
|
73
75
|
_ts_decorate([
|
|
@@ -96,6 +98,11 @@ _ts_decorate([
|
|
|
96
98
|
String
|
|
97
99
|
]
|
|
98
100
|
}),
|
|
101
|
+
(0, _classvalidator.IsNotEmpty)(),
|
|
102
|
+
(0, _classvalidator.IsArray)(),
|
|
103
|
+
(0, _classvalidator.IsString)({
|
|
104
|
+
each: true
|
|
105
|
+
}),
|
|
99
106
|
_ts_metadata("design:type", Array)
|
|
100
107
|
], DeleteMultipleFileDto.prototype, "keys", void 0);
|
|
101
108
|
_ts_decorate([
|
|
@@ -35,8 +35,7 @@ function _ts_metadata(k, v) {
|
|
|
35
35
|
}
|
|
36
36
|
let StorageConfigBase = class StorageConfigBase extends _entities.Identity {
|
|
37
37
|
constructor(...args){
|
|
38
|
-
super(...args), _define_property(this, "name", void 0), _define_property(this, "storage", void 0),
|
|
39
|
-
_define_property(this, "config", void 0);
|
|
38
|
+
super(...args), _define_property(this, "name", void 0), _define_property(this, "storage", void 0), _define_property(this, "config", void 0), _define_property(this, "isActive", void 0), _define_property(this, "isDefault", void 0);
|
|
40
39
|
}
|
|
41
40
|
};
|
|
42
41
|
_ts_decorate([
|
|
@@ -62,3 +61,33 @@ _ts_decorate([
|
|
|
62
61
|
}),
|
|
63
62
|
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
64
63
|
], StorageConfigBase.prototype, "config", void 0);
|
|
64
|
+
_ts_decorate([
|
|
65
|
+
(0, _typeorm.Column)({
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
default: true,
|
|
68
|
+
name: 'is_active'
|
|
69
|
+
}),
|
|
70
|
+
_ts_metadata("design:type", Boolean)
|
|
71
|
+
], StorageConfigBase.prototype, "isActive", void 0);
|
|
72
|
+
_ts_decorate([
|
|
73
|
+
(0, _typeorm.Column)({
|
|
74
|
+
type: 'boolean',
|
|
75
|
+
default: false,
|
|
76
|
+
name: 'is_default'
|
|
77
|
+
}),
|
|
78
|
+
_ts_metadata("design:type", Boolean)
|
|
79
|
+
], StorageConfigBase.prototype, "isDefault", void 0);
|
|
80
|
+
StorageConfigBase = _ts_decorate([
|
|
81
|
+
(0, _typeorm.Index)([
|
|
82
|
+
'name'
|
|
83
|
+
]),
|
|
84
|
+
(0, _typeorm.Index)([
|
|
85
|
+
'storage'
|
|
86
|
+
]),
|
|
87
|
+
(0, _typeorm.Index)([
|
|
88
|
+
'isActive'
|
|
89
|
+
]),
|
|
90
|
+
(0, _typeorm.Index)([
|
|
91
|
+
'isDefault'
|
|
92
|
+
])
|
|
93
|
+
], StorageConfigBase);
|
package/cjs/interfaces/index.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./file-manager.interface"), exports);
|
|
6
|
-
_export_star(require("./file-upload-response.interface"), exports);
|
|
7
6
|
_export_star(require("./folder.interface"), exports);
|
|
8
7
|
_export_star(require("./storage-config.interface"), exports);
|
|
9
8
|
_export_star(require("./storage-module-options.interface"), exports);
|
|
@@ -76,6 +76,11 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
76
76
|
function _ts_metadata(k, v) {
|
|
77
77
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
78
78
|
}
|
|
79
|
+
function _ts_param(paramIndex, decorator) {
|
|
80
|
+
return function(target, key) {
|
|
81
|
+
decorator(target, key, paramIndex);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
79
84
|
let FileServeMiddleware = class FileServeMiddleware {
|
|
80
85
|
async use(req, res, next) {
|
|
81
86
|
try {
|
|
@@ -197,6 +202,7 @@ let FileServeMiddleware = class FileServeMiddleware {
|
|
|
197
202
|
};
|
|
198
203
|
FileServeMiddleware = _ts_decorate([
|
|
199
204
|
(0, _common.Injectable)(),
|
|
205
|
+
_ts_param(0, (0, _common.Inject)(_uploadservice.UploadService)),
|
|
200
206
|
_ts_metadata("design:type", Function),
|
|
201
207
|
_ts_metadata("design:paramtypes", [
|
|
202
208
|
typeof _uploadservice.UploadService === "undefined" ? Object : _uploadservice.UploadService
|
|
@@ -117,10 +117,8 @@ let StorageModule = class StorageModule {
|
|
|
117
117
|
]
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
|
-
//
|
|
121
|
-
/**
|
|
122
|
-
* Get controllers (all controllers always loaded)
|
|
123
|
-
*/ static getControllers(options) {
|
|
120
|
+
// Private Helper Methods
|
|
121
|
+
/** Get controllers (all controllers always loaded) */ static getControllers(options) {
|
|
124
122
|
return [
|
|
125
123
|
_controllers.FileManagerController,
|
|
126
124
|
_controllers.FolderController,
|
|
@@ -80,6 +80,29 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
80
80
|
}
|
|
81
81
|
let LocalProvider = class LocalProvider {
|
|
82
82
|
/**
|
|
83
|
+
* SECURITY: Validates that a target path does not escape the base directory
|
|
84
|
+
* Prevents path traversal attacks using ../ sequences
|
|
85
|
+
* @throws Error if path traversal is detected
|
|
86
|
+
*/ validatePathWithinBase(targetPath) {
|
|
87
|
+
const normalizedBasePath = _path.resolve(this.basePath);
|
|
88
|
+
const normalizedTargetPath = _path.resolve(targetPath);
|
|
89
|
+
if (!normalizedTargetPath.startsWith(normalizedBasePath + _path.sep) && normalizedTargetPath !== normalizedBasePath) {
|
|
90
|
+
this.logger.warn(`Path traversal attempt detected: ${targetPath}`);
|
|
91
|
+
throw new Error('Invalid path: Path traversal attempt detected');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* SECURITY: Validates file key format to prevent malicious input
|
|
96
|
+
* @throws Error if key contains suspicious patterns
|
|
97
|
+
*/ validateKeyFormat(key) {
|
|
98
|
+
if (!key || typeof key !== 'string' || key.trim().length === 0) {
|
|
99
|
+
throw new Error('Invalid file key: empty or invalid');
|
|
100
|
+
}
|
|
101
|
+
if (key.includes('\0')) {
|
|
102
|
+
throw new Error('Invalid file key: contains null bytes');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
83
106
|
* Initialize Local File System provider with configuration
|
|
84
107
|
* @param config.basePath - Base path for file storage (default: './uploads')
|
|
85
108
|
* @param config.baseUrl - Optional base URL for generating file URLs
|
|
@@ -112,7 +135,11 @@ let LocalProvider = class LocalProvider {
|
|
|
112
135
|
}
|
|
113
136
|
// Build file path
|
|
114
137
|
const folderPath = options.folderPath ? _path.join(this.basePath, options.folderPath) : this.basePath;
|
|
138
|
+
// SECURITY: Validate path does not escape base directory
|
|
139
|
+
this.validatePathWithinBase(folderPath);
|
|
115
140
|
const filePath = _path.join(folderPath, fileName);
|
|
141
|
+
// SECURITY: Double-check final file path
|
|
142
|
+
this.validatePathWithinBase(filePath);
|
|
116
143
|
// Ensure directory exists
|
|
117
144
|
await _promises.mkdir(folderPath, {
|
|
118
145
|
recursive: true
|
|
@@ -135,14 +162,20 @@ let LocalProvider = class LocalProvider {
|
|
|
135
162
|
}
|
|
136
163
|
async deleteFile(key) {
|
|
137
164
|
try {
|
|
165
|
+
// SECURITY: Validate key format first
|
|
166
|
+
this.validateKeyFormat(key);
|
|
138
167
|
// Key now includes the basePath, resolve from cwd
|
|
139
168
|
let filePath = _path.resolve(key);
|
|
169
|
+
// SECURITY: Validate resolved path is within base directory
|
|
170
|
+
this.validatePathWithinBase(filePath);
|
|
140
171
|
// Check if file exists at the resolved path
|
|
141
172
|
try {
|
|
142
173
|
await _promises.access(filePath);
|
|
143
174
|
} catch {
|
|
144
175
|
// Fallback: try with basePath prefix (for old keys without basePath)
|
|
145
176
|
const fallbackPath = _path.join(this.basePath, key);
|
|
177
|
+
// SECURITY: Validate fallback path as well
|
|
178
|
+
this.validatePathWithinBase(fallbackPath);
|
|
146
179
|
try {
|
|
147
180
|
await _promises.access(fallbackPath);
|
|
148
181
|
filePath = fallbackPath;
|
|
@@ -155,7 +188,11 @@ let LocalProvider = class LocalProvider {
|
|
|
155
188
|
}
|
|
156
189
|
await _promises.unlink(filePath);
|
|
157
190
|
this.logger.log(`Deleted file from local storage: ${key}`);
|
|
158
|
-
} catch (
|
|
191
|
+
} catch (error) {
|
|
192
|
+
// Re-throw security errors
|
|
193
|
+
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
159
196
|
this.logger.warn(`Failed to delete file from local storage: ${key}`);
|
|
160
197
|
}
|
|
161
198
|
}
|
|
@@ -186,14 +223,23 @@ let LocalProvider = class LocalProvider {
|
|
|
186
223
|
* Get the absolute path for a file key
|
|
187
224
|
* Key now includes basePath, so resolve from cwd
|
|
188
225
|
*/ getAbsolutePath(key) {
|
|
189
|
-
|
|
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;
|
|
190
232
|
}
|
|
191
233
|
/**
|
|
192
234
|
* Check if a file exists
|
|
193
235
|
*/ async fileExists(key) {
|
|
194
236
|
try {
|
|
237
|
+
// SECURITY: Validate key format
|
|
238
|
+
this.validateKeyFormat(key);
|
|
195
239
|
// Key now includes basePath, resolve from cwd
|
|
196
240
|
const filePath = _path.resolve(key);
|
|
241
|
+
// SECURITY: Validate path is within base directory
|
|
242
|
+
this.validatePathWithinBase(filePath);
|
|
197
243
|
await _promises.access(filePath);
|
|
198
244
|
return true;
|
|
199
245
|
} catch {
|
|
@@ -203,8 +249,12 @@ let LocalProvider = class LocalProvider {
|
|
|
203
249
|
/**
|
|
204
250
|
* Get file stats
|
|
205
251
|
*/ async getFileStats(key) {
|
|
252
|
+
// SECURITY: Validate key format
|
|
253
|
+
this.validateKeyFormat(key);
|
|
206
254
|
// Key now includes basePath, resolve from cwd
|
|
207
255
|
const filePath = _path.resolve(key);
|
|
256
|
+
// SECURITY: Validate path is within base directory
|
|
257
|
+
this.validatePathWithinBase(filePath);
|
|
208
258
|
const stats = await _promises.stat(filePath);
|
|
209
259
|
return {
|
|
210
260
|
size: stats.size,
|
|
@@ -130,8 +130,8 @@ let StorageFactoryService = class StorageFactoryService {
|
|
|
130
130
|
} catch (error) {
|
|
131
131
|
this.logger.error(`Failed to create provider ${config.provider}:`, error);
|
|
132
132
|
// Preserve original error message for better debugging
|
|
133
|
-
const
|
|
134
|
-
throw new Error(`Failed to initialize storage provider '${config.provider}': ${
|
|
133
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
134
|
+
throw new Error(`Failed to initialize storage provider '${config.provider}': ${errorMessage}`);
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "FileManagerService", {
|
|
|
10
10
|
});
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
12
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
|
+
const _utils = require("@flusys/nestjs-shared/utils");
|
|
13
14
|
const _common = require("@nestjs/common");
|
|
14
15
|
const _typeorm = require("typeorm");
|
|
15
16
|
const _config = require("../config");
|
|
@@ -154,8 +155,9 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
154
155
|
const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
|
|
155
156
|
const storageConfigEntity = enableCompanyFeature ? (await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")))).StorageConfigWithCompany : (await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")))).StorageConfig;
|
|
156
157
|
const storageConfigRepository = await this.dataSourceProvider.getRepository(storageConfigEntity);
|
|
158
|
+
const storageConfigId = dto.storageConfigId;
|
|
157
159
|
const whereCondition = {
|
|
158
|
-
id:
|
|
160
|
+
id: storageConfigId
|
|
159
161
|
};
|
|
160
162
|
// Filter by company if company feature is enabled
|
|
161
163
|
if (enableCompanyFeature && user?.companyId) {
|
|
@@ -165,25 +167,30 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
165
167
|
where: whereCondition
|
|
166
168
|
});
|
|
167
169
|
if (storageConfig) {
|
|
168
|
-
|
|
170
|
+
const typedConfig = storageConfig;
|
|
171
|
+
if (typedConfig.storage) {
|
|
172
|
+
storageLocation = typedConfig.storage;
|
|
173
|
+
}
|
|
169
174
|
}
|
|
170
175
|
} catch (error) {
|
|
171
|
-
|
|
176
|
+
const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
|
|
177
|
+
this.logger.warn(`Failed to get storage location from config ${dto.storageConfigId}: ${errorMessage}`);
|
|
172
178
|
// Fall back to DTO location or default
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
|
-
// Set basic fields
|
|
176
|
-
|
|
181
|
+
// Set basic fields - merge existing data with DTO
|
|
182
|
+
const mergedFileManager = {
|
|
177
183
|
...fileManager,
|
|
178
184
|
...dto,
|
|
179
185
|
location: storageLocation,
|
|
180
186
|
folder: validatedFolder
|
|
181
187
|
};
|
|
182
|
-
// Only set company fields if
|
|
183
|
-
|
|
184
|
-
|
|
188
|
+
// Only set company fields if company feature is enabled
|
|
189
|
+
const enableCompanyFeatureForEntity = this.storageConfig.isCompanyFeatureEnabled();
|
|
190
|
+
if (enableCompanyFeatureForEntity) {
|
|
191
|
+
mergedFileManager.companyId = user?.companyId ?? null;
|
|
185
192
|
}
|
|
186
|
-
return
|
|
193
|
+
return mergedFileManager;
|
|
187
194
|
}
|
|
188
195
|
async getSelectQuery(query, _user, select) {
|
|
189
196
|
if (!select || !select.length) {
|
|
@@ -260,7 +267,8 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
260
267
|
this.logger.debug(`enrichWithProviderNames: First enriched item: ${JSON.stringify(enrichedItems[0])}`);
|
|
261
268
|
return enrichedItems;
|
|
262
269
|
} catch (error) {
|
|
263
|
-
|
|
270
|
+
const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
|
|
271
|
+
this.logger.warn(`Failed to fetch provider names: ${errorMessage}`);
|
|
264
272
|
return items.map((item)=>({
|
|
265
273
|
...item,
|
|
266
274
|
providerName: undefined
|
|
@@ -298,13 +306,12 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
298
306
|
* Override: Extra query manipulation - Auto-filter by user's company and private file permissions
|
|
299
307
|
*/ async getExtraManipulateQuery(query, filterDto, user) {
|
|
300
308
|
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
301
|
-
//
|
|
309
|
+
// Apply company filter using shared utility
|
|
302
310
|
const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
311
|
+
(0, _utils.applyCompanyFilter)(query, {
|
|
312
|
+
isCompanyFeatureEnabled: enableCompanyFeature,
|
|
313
|
+
entityAlias: 'file_manager'
|
|
314
|
+
}, user);
|
|
308
315
|
// Check if user has permission to see private files
|
|
309
316
|
if (user) {
|
|
310
317
|
const cacheKey = enableCompanyFeature ? `${USER_ACTION_PERMISSION_CACHE_KEY}_${user.id}_${user.companyId}` : `${USER_ACTION_PERMISSION_CACHE_KEY}_${user.id}`;
|
|
@@ -367,8 +374,9 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
367
374
|
'config'
|
|
368
375
|
]
|
|
369
376
|
});
|
|
370
|
-
|
|
371
|
-
|
|
377
|
+
const typedConfig = config;
|
|
378
|
+
if (typedConfig?.config?.basePath) {
|
|
379
|
+
const basePath = typedConfig.config.basePath.replace(/^\.\//, '');
|
|
372
380
|
// Convert old keys to new format
|
|
373
381
|
deleteKeys = keys.map((key)=>{
|
|
374
382
|
if (!key.includes('/')) {
|
|
@@ -378,7 +386,8 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
378
386
|
});
|
|
379
387
|
}
|
|
380
388
|
} catch (error) {
|
|
381
|
-
|
|
389
|
+
const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
|
|
390
|
+
this.logger.warn(`Failed to get basePath for delete: ${errorMessage}`);
|
|
382
391
|
}
|
|
383
392
|
}
|
|
384
393
|
await this.uploadService.deleteMultipleFile(deleteKeys, configId === 'default' ? undefined : configId, user ?? undefined, location);
|
|
@@ -412,7 +421,8 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
412
421
|
file.expiresAt = now + expiresIn * 1000;
|
|
413
422
|
shouldUpdate = true;
|
|
414
423
|
} catch (error) {
|
|
415
|
-
|
|
424
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
425
|
+
this.logger.error(`Failed to generate URL for file ${file.id}: ${errorMessage}`);
|
|
416
426
|
// Use fallback URL with appUrl from config
|
|
417
427
|
const baseUrl = this.getFileBaseUrl(protocol, host);
|
|
418
428
|
file.url = `${baseUrl}/storage/upload/file/${file.key}`;
|
|
@@ -439,13 +449,15 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
439
449
|
'config'
|
|
440
450
|
]
|
|
441
451
|
});
|
|
442
|
-
|
|
443
|
-
|
|
452
|
+
const typedConfig = config;
|
|
453
|
+
if (typedConfig?.config?.basePath) {
|
|
454
|
+
const basePath = typedConfig.config.basePath.replace(/^\.\//, ''); // Remove leading ./
|
|
444
455
|
fileKey = `${basePath}/${file.key}`;
|
|
445
456
|
this.logger.debug(`Prefixed old key with basePath: ${fileKey}`);
|
|
446
457
|
}
|
|
447
458
|
} catch (error) {
|
|
448
|
-
|
|
459
|
+
const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
|
|
460
|
+
this.logger.warn(`Failed to get basePath for file ${file.id}: ${errorMessage}`);
|
|
449
461
|
}
|
|
450
462
|
}
|
|
451
463
|
const baseUrl = this.getFileBaseUrl(protocol, host);
|
|
@@ -470,7 +482,8 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
470
482
|
try {
|
|
471
483
|
await this.repository.save(updatedFiles);
|
|
472
484
|
} catch (error) {
|
|
473
|
-
|
|
485
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
486
|
+
this.logger.error(`Failed to save updated file URLs: ${errorMessage}`);
|
|
474
487
|
}
|
|
475
488
|
}
|
|
476
489
|
return responses;
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "FolderService", {
|
|
|
10
10
|
});
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
12
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
|
+
const _utils = require("@flusys/nestjs-shared/utils");
|
|
13
14
|
const _common = require("@nestjs/common");
|
|
14
15
|
const _config = require("../config");
|
|
15
16
|
const _entities = require("../entities");
|
|
@@ -42,46 +43,21 @@ function _ts_param(paramIndex, decorator) {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
let FolderService = class FolderService extends _classes.RequestScopedApiService {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* @returns Folder or FolderWithCompany based on configuration
|
|
48
|
-
*/ resolveEntity() {
|
|
49
|
-
const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
|
|
50
|
-
return enableCompanyFeature ? _entities.FolderWithCompany : _entities.Folder;
|
|
46
|
+
resolveEntity() {
|
|
47
|
+
return this.storageConfig.isCompanyFeatureEnabled() ? _entities.FolderWithCompany : _entities.Folder;
|
|
51
48
|
}
|
|
52
|
-
|
|
53
|
-
* Get DataSource provider for this service
|
|
54
|
-
* @returns StorageDataSourceProvider instance
|
|
55
|
-
*/ getDataSourceProvider() {
|
|
49
|
+
getDataSourceProvider() {
|
|
56
50
|
return this.dataSourceProvider;
|
|
57
51
|
}
|
|
58
52
|
async convertSingleDtoToEntity(dto, user) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const dbData = await this.repository.findOne({
|
|
63
|
-
where: {
|
|
64
|
-
id: dto.id
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
if (!dbData) {
|
|
68
|
-
throw new _common.NotFoundException('No such entity data found for update! Please, Try Again.');
|
|
69
|
-
}
|
|
70
|
-
folder = dbData;
|
|
71
|
-
}
|
|
72
|
-
// Set company/branch IDs if company feature is enabled
|
|
73
|
-
folder = {
|
|
74
|
-
...folder,
|
|
75
|
-
...dto
|
|
76
|
-
};
|
|
77
|
-
// Only set company fields if they exist on the entity (when company feature is enabled)
|
|
78
|
-
if ('companyId' in folder) {
|
|
79
|
-
folder.companyId = user?.companyId ?? null;
|
|
53
|
+
const entity = await super.convertSingleDtoToEntity(dto, user);
|
|
54
|
+
if (this.storageConfig.isCompanyFeatureEnabled()) {
|
|
55
|
+
entity.companyId = user?.companyId ?? null;
|
|
80
56
|
}
|
|
81
|
-
return
|
|
57
|
+
return entity;
|
|
82
58
|
}
|
|
83
59
|
async getSelectQuery(query, _user, select) {
|
|
84
|
-
if (!select
|
|
60
|
+
if (!select?.length) {
|
|
85
61
|
select = [
|
|
86
62
|
'id',
|
|
87
63
|
'name',
|
|
@@ -89,35 +65,25 @@ let FolderService = class FolderService extends _classes.RequestScopedApiService
|
|
|
89
65
|
'createdAt',
|
|
90
66
|
'deletedAt'
|
|
91
67
|
];
|
|
68
|
+
if (this.storageConfig.isCompanyFeatureEnabled()) {
|
|
69
|
+
select.push('companyId');
|
|
70
|
+
}
|
|
92
71
|
}
|
|
93
|
-
|
|
94
|
-
// Add company context fields if company feature is enabled
|
|
95
|
-
// The entity will have these fields only if company feature is enabled
|
|
96
|
-
if (this.storageConfig.isCompanyFeatureEnabled()) {
|
|
97
|
-
selectFields.push('folder.companyId');
|
|
98
|
-
}
|
|
99
|
-
query.select(selectFields);
|
|
72
|
+
query.select(select.map((f)=>`${this.entityName}.${f}`));
|
|
100
73
|
return {
|
|
101
74
|
query,
|
|
102
75
|
isRaw: false
|
|
103
76
|
};
|
|
104
77
|
}
|
|
105
|
-
|
|
106
|
-
* Override: Extra query manipulation - Auto-filter by user's company
|
|
107
|
-
*/ async getExtraManipulateQuery(query, filterDto, user) {
|
|
78
|
+
async getExtraManipulateQuery(query, filterDto, user) {
|
|
108
79
|
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
companyId: user.companyId
|
|
114
|
-
});
|
|
115
|
-
}
|
|
80
|
+
(0, _utils.applyCompanyFilter)(query, {
|
|
81
|
+
isCompanyFeatureEnabled: this.storageConfig.isCompanyFeatureEnabled(),
|
|
82
|
+
entityAlias: 'folder'
|
|
83
|
+
}, user);
|
|
116
84
|
return result;
|
|
117
85
|
}
|
|
118
|
-
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
119
86
|
constructor(cacheManager, utilsService, storageConfig, dataSourceProvider){
|
|
120
|
-
// Repository will be set asynchronously by RequestScopedApiService
|
|
121
87
|
super('folder', null, cacheManager, utilsService, FolderService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
|
|
122
88
|
}
|
|
123
89
|
};
|
|
@@ -83,10 +83,8 @@ function _ts_param(paramIndex, decorator) {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules.MultiTenantDataSourceService {
|
|
86
|
-
//
|
|
87
|
-
/**
|
|
88
|
-
* Build parent options from StorageModuleOptions
|
|
89
|
-
*/ static buildParentOptions(options) {
|
|
86
|
+
// Factory Methods
|
|
87
|
+
/** Build parent options from StorageModuleOptions */ static buildParentOptions(options) {
|
|
90
88
|
return {
|
|
91
89
|
bootstrapAppConfig: options.bootstrapAppConfig,
|
|
92
90
|
defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
|
|
@@ -94,10 +92,8 @@ let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules
|
|
|
94
92
|
tenants: options.config?.tenants
|
|
95
93
|
};
|
|
96
94
|
}
|
|
97
|
-
//
|
|
98
|
-
/**
|
|
99
|
-
* Get global enable company feature flag
|
|
100
|
-
*/ getEnableCompanyFeature() {
|
|
95
|
+
// Feature Flags
|
|
96
|
+
/** Get global enable company feature flag */ getEnableCompanyFeature() {
|
|
101
97
|
return this.storageOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
102
98
|
}
|
|
103
99
|
/**
|
|
@@ -111,11 +107,11 @@ let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules
|
|
|
111
107
|
*/ getEnableCompanyFeatureForCurrentTenant() {
|
|
112
108
|
return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
|
|
113
109
|
}
|
|
114
|
-
//
|
|
110
|
+
// Entity Management
|
|
115
111
|
/**
|
|
116
|
-
* Get storage entities for migrations based on company feature flag
|
|
117
|
-
*
|
|
118
|
-
*
|
|
112
|
+
* Get storage entities for migrations based on company feature flag.
|
|
113
|
+
* For TypeORM repositories, we always use the base entities (FileManager, etc.)
|
|
114
|
+
* but for migrations, we need the correct entity based on the feature flag.
|
|
119
115
|
*/ async getStorageEntities(enableCompanyFeature) {
|
|
120
116
|
const enable = enableCompanyFeature ?? this.getEnableCompanyFeature();
|
|
121
117
|
const { FileManager, Folder, StorageConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
|
|
@@ -135,10 +131,8 @@ let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules
|
|
|
135
131
|
StorageConfig
|
|
136
132
|
];
|
|
137
133
|
}
|
|
138
|
-
//
|
|
139
|
-
/**
|
|
140
|
-
* Override to dynamically set entities based on tenant config
|
|
141
|
-
*/ async createDataSourceFromConfig(config) {
|
|
134
|
+
// Overrides
|
|
135
|
+
/** Override to dynamically set entities based on tenant config */ async createDataSourceFromConfig(config) {
|
|
142
136
|
const currentTenant = this.getCurrentTenant();
|
|
143
137
|
const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
|
|
144
138
|
const entities = await this.getStorageEntities(enableCompanyFeature);
|