@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.
Files changed (67) hide show
  1. package/README.md +131 -19
  2. package/cjs/config/storage-config.service.js +5 -0
  3. package/cjs/config/storage.constants.js +0 -8
  4. package/cjs/controllers/file-manager.controller.js +50 -5
  5. package/cjs/controllers/folder.controller.js +46 -4
  6. package/cjs/controllers/storage-config.controller.js +46 -4
  7. package/cjs/controllers/upload.controller.js +6 -12
  8. package/cjs/dtos/file-manager.dto.js +8 -5
  9. package/cjs/dtos/storage-config.dto.js +41 -1
  10. package/cjs/dtos/upload.dto.js +7 -0
  11. package/cjs/entities/storage-config-base.entity.js +31 -2
  12. package/cjs/interfaces/index.js +0 -1
  13. package/cjs/middlewares/file-serve.middleware.js +6 -0
  14. package/cjs/modules/storage.module.js +2 -4
  15. package/cjs/providers/local-provider.js +52 -2
  16. package/cjs/providers/storage-factory.service.js +2 -2
  17. package/cjs/services/file-manager.service.js +37 -24
  18. package/cjs/services/folder.service.js +18 -52
  19. package/cjs/services/storage-datasource.provider.js +10 -16
  20. package/cjs/services/storage-provider-config.service.js +28 -63
  21. package/cjs/services/upload.service.js +39 -27
  22. package/cjs/utils/file-validator.util.js +470 -0
  23. package/cjs/utils/image-compressor.util.js +1 -3
  24. package/config/storage-config.service.d.ts +5 -2
  25. package/config/storage.constants.d.ts +0 -2
  26. package/controllers/file-manager.controller.d.ts +1 -1
  27. package/controllers/upload.controller.d.ts +2 -6
  28. package/dtos/file-manager.dto.d.ts +2 -4
  29. package/dtos/folder.dto.d.ts +2 -4
  30. package/dtos/storage-config.dto.d.ts +9 -6
  31. package/entities/storage-config-base.entity.d.ts +2 -0
  32. package/fesm/config/storage-config.service.js +5 -0
  33. package/fesm/config/storage.constants.js +0 -2
  34. package/fesm/controllers/file-manager.controller.js +51 -6
  35. package/fesm/controllers/folder.controller.js +49 -7
  36. package/fesm/controllers/storage-config.controller.js +49 -7
  37. package/fesm/controllers/upload.controller.js +7 -13
  38. package/fesm/dtos/file-manager.dto.js +8 -5
  39. package/fesm/dtos/storage-config.dto.js +45 -11
  40. package/fesm/dtos/upload.dto.js +8 -1
  41. package/fesm/entities/index.js +1 -5
  42. package/fesm/entities/storage-config-base.entity.js +33 -7
  43. package/fesm/interfaces/index.js +0 -1
  44. package/fesm/interfaces/storage-config.interface.js +1 -3
  45. package/fesm/middlewares/file-serve.middleware.js +7 -1
  46. package/fesm/modules/storage.module.js +2 -4
  47. package/fesm/providers/local-provider.js +52 -2
  48. package/fesm/providers/storage-factory.service.js +2 -2
  49. package/fesm/services/file-manager.service.js +38 -25
  50. package/fesm/services/folder.service.js +19 -53
  51. package/fesm/services/storage-datasource.provider.js +10 -16
  52. package/fesm/services/storage-provider-config.service.js +28 -63
  53. package/fesm/services/upload.service.js +40 -28
  54. package/fesm/utils/file-validator.util.js +463 -0
  55. package/fesm/utils/image-compressor.util.js +1 -3
  56. package/interfaces/file-manager.interface.d.ts +7 -4
  57. package/interfaces/index.d.ts +0 -1
  58. package/interfaces/storage-config.interface.d.ts +2 -20
  59. package/package.json +6 -6
  60. package/providers/local-provider.d.ts +2 -0
  61. package/services/file-manager.service.d.ts +2 -2
  62. package/services/folder.service.d.ts +1 -2
  63. package/services/storage-provider-config.service.d.ts +1 -2
  64. package/utils/file-validator.util.d.ts +16 -0
  65. package/cjs/interfaces/file-upload-response.interface.js +0 -4
  66. package/fesm/interfaces/file-upload-response.interface.js +0 -1
  67. package/interfaces/file-upload-response.interface.d.ts +0 -6
@@ -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), // add config for storage
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);
@@ -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
- // ==================== Private Helper Methods ====================
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 (_error) {
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
- return _path.resolve(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;
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 originalMessage = error?.message || 'Unknown error';
134
- throw new Error(`Failed to initialize storage provider '${config.provider}': ${originalMessage}`);
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: dto.storageConfigId
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
- storageLocation = storageConfig.storage;
170
+ const typedConfig = storageConfig;
171
+ if (typedConfig.storage) {
172
+ storageLocation = typedConfig.storage;
173
+ }
169
174
  }
170
175
  } catch (error) {
171
- this.logger.warn(`Failed to get storage location from config: ${error}`);
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
- fileManager = {
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 they exist on the entity (when company feature is enabled)
183
- if ('companyId' in fileManager) {
184
- fileManager.companyId = user?.companyId ?? null;
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 fileManager;
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
- this.logger.warn(`Failed to fetch provider names: ${error}`);
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
- // If company feature enabled and user has companyId, filter by user's company
309
+ // Apply company filter using shared utility
302
310
  const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
303
- if (enableCompanyFeature && user?.companyId) {
304
- query.andWhere('file_manager.companyId = :companyId', {
305
- companyId: user.companyId
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
- if (config && config.config?.basePath) {
371
- const basePath = config.config.basePath.replace(/^\.\//, '');
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
- this.logger.warn(`Failed to get basePath for delete: ${error}`);
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
- this.logger.error(`Failed to generate URL for file ${file.id}: ${error?.message || 'Unknown error'}`);
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
- if (config && config.config?.basePath) {
443
- const basePath = config.config.basePath.replace(/^\.\//, ''); // Remove leading ./
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
- this.logger.warn(`Failed to get basePath for file ${file.id}: ${error}`);
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
- this.logger.error(`Failed to save updated file URLs: ${error?.message || 'Unknown error'}`);
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
- * Resolve entity class for this service
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
- let folder = {};
60
- // NOTE: Using 'id' in dto check instead of instanceof - instanceof may not work after esbuild bundling
61
- if ('id' in dto && dto.id && typeof dto.id === 'string') {
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 folder;
57
+ return entity;
82
58
  }
83
59
  async getSelectQuery(query, _user, select) {
84
- if (!select || !select.length) {
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
- const selectFields = select.map((field)=>`${this.entityName}.${field}`);
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
- // If company feature enabled and user has companyId, filter by user's company
110
- const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
111
- if (enableCompanyFeature && user?.companyId) {
112
- query.andWhere('folder.companyId = :companyId', {
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
- // ==================== Factory Methods ====================
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
- // ==================== Feature Flags ====================
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
- // ==================== Entity Management ====================
110
+ // Entity Management
115
111
  /**
116
- * Get storage entities for migrations based on company feature flag
117
- * Note: For TypeORM repositories, we always use the base entities (FileManager, etc.)
118
- * But for migrations, we need the correct entity based on the feature flag
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
- // ==================== Overrides ====================
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);