@flusys/nestjs-storage 3.0.0 → 4.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 (55) hide show
  1. package/README.md +1 -1
  2. package/cjs/config/index.js +1 -0
  3. package/cjs/config/message-keys.js +90 -0
  4. package/cjs/controllers/file-manager.controller.js +7 -1
  5. package/cjs/controllers/folder.controller.js +1 -0
  6. package/cjs/controllers/storage-config.controller.js +1 -0
  7. package/cjs/controllers/upload.controller.js +9 -4
  8. package/cjs/middlewares/file-serve.middleware.js +39 -15
  9. package/cjs/modules/storage.module.js +2 -2
  10. package/cjs/providers/azure-provider.optional.js +8 -5
  11. package/cjs/providers/local-provider.js +16 -14
  12. package/cjs/providers/s3-provider.optional.js +8 -5
  13. package/cjs/providers/sftp-provider.optional.js +13 -11
  14. package/cjs/providers/storage-factory.service.js +19 -10
  15. package/cjs/services/file-manager.service.js +30 -23
  16. package/cjs/services/folder.service.js +2 -1
  17. package/cjs/services/storage-datasource.provider.js +6 -2
  18. package/cjs/services/storage-provider-config.service.js +2 -1
  19. package/cjs/services/upload.service.js +129 -79
  20. package/cjs/utils/file-validator.util.js +4 -22
  21. package/config/index.d.ts +1 -0
  22. package/config/message-keys.d.ts +114 -0
  23. package/controllers/folder.controller.d.ts +7 -7
  24. package/controllers/storage-config.controller.d.ts +7 -7
  25. package/controllers/upload.controller.d.ts +1 -1
  26. package/fesm/config/index.js +1 -0
  27. package/fesm/config/message-keys.js +64 -0
  28. package/fesm/controllers/file-manager.controller.js +7 -1
  29. package/fesm/controllers/folder.controller.js +1 -0
  30. package/fesm/controllers/storage-config.controller.js +1 -0
  31. package/fesm/controllers/upload.controller.js +9 -4
  32. package/fesm/middlewares/file-serve.middleware.js +40 -16
  33. package/fesm/modules/storage.module.js +2 -2
  34. package/fesm/providers/azure-provider.optional.js +9 -6
  35. package/fesm/providers/local-provider.js +28 -26
  36. package/fesm/providers/s3-provider.optional.js +9 -6
  37. package/fesm/providers/sftp-provider.optional.js +26 -24
  38. package/fesm/providers/storage-factory.service.js +20 -11
  39. package/fesm/services/file-manager.service.js +31 -24
  40. package/fesm/services/folder.service.js +2 -1
  41. package/fesm/services/storage-datasource.provider.js +7 -3
  42. package/fesm/services/storage-provider-config.service.js +2 -1
  43. package/fesm/services/upload.service.js +131 -81
  44. package/fesm/utils/file-validator.util.js +3 -21
  45. package/middlewares/file-serve.middleware.d.ts +0 -1
  46. package/package.json +3 -3
  47. package/providers/azure-provider.optional.d.ts +0 -1
  48. package/providers/local-provider.d.ts +0 -1
  49. package/providers/s3-provider.optional.d.ts +0 -1
  50. package/providers/sftp-provider.optional.d.ts +0 -1
  51. package/providers/storage-factory.service.d.ts +0 -1
  52. package/services/file-manager.service.d.ts +2 -2
  53. package/services/storage-datasource.provider.d.ts +0 -2
  54. package/services/upload.service.d.ts +0 -1
  55. package/utils/file-validator.util.d.ts +0 -1
@@ -1,15 +1,4 @@
1
- /**
2
- * LOCAL File System Provider
3
- *
4
- * This provider uses Node.js built-in 'fs' module - NO external dependencies required!
5
- * Perfect for same-server storage scenarios.
6
- *
7
- * Usage:
8
- * import { LocalProvider } from '@flusys/nestjs-storage/providers/local-provider';
9
- * import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
10
- *
11
- * StorageProviderRegistry.register(FileLocationEnum.LOCAL, LocalProvider);
12
- */ function _define_property(obj, key, value) {
1
+ function _define_property(obj, key, value) {
13
2
  if (key in obj) {
14
3
  Object.defineProperty(obj, key, {
15
4
  value: value,
@@ -22,8 +11,20 @@
22
11
  }
23
12
  return obj;
24
13
  }
14
+ /**
15
+ * LOCAL File System Provider
16
+ *
17
+ * This provider uses Node.js built-in 'fs' module - NO external dependencies required!
18
+ * Perfect for same-server storage scenarios.
19
+ *
20
+ * Usage:
21
+ * import { LocalProvider } from '@flusys/nestjs-storage/providers/local-provider';
22
+ * import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
23
+ *
24
+ * StorageProviderRegistry.register(FileLocationEnum.LOCAL, LocalProvider);
25
+ */ import { BadRequestException } from '@nestjs/common';
26
+ import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
25
27
  import { ImageCompressor } from '../utils/image-compressor.util';
26
- import { Logger } from '@nestjs/common';
27
28
  import * as fs from 'fs/promises';
28
29
  import * as path from 'path';
29
30
  import { v4 as uuidv4 } from 'uuid';
@@ -39,8 +40,10 @@ import { v4 as uuidv4 } from 'uuid';
39
40
  const normalizedBasePath = path.resolve(this.basePath);
40
41
  const normalizedTargetPath = path.resolve(targetPath);
41
42
  if (!normalizedTargetPath.startsWith(normalizedBasePath + path.sep) && normalizedTargetPath !== normalizedBasePath) {
42
- this.logger.warn(`Path traversal attempt detected: ${targetPath}`);
43
- throw new Error('Invalid path: Path traversal attempt detected');
43
+ throw new BadRequestException({
44
+ message: 'Invalid path: Path traversal attempt detected',
45
+ messageKey: SYSTEM_MESSAGES.PATH_TRAVERSAL_DETECTED
46
+ });
44
47
  }
45
48
  }
46
49
  /**
@@ -48,10 +51,16 @@ import { v4 as uuidv4 } from 'uuid';
48
51
  * @throws Error if key contains suspicious patterns
49
52
  */ validateKeyFormat(key) {
50
53
  if (!key || typeof key !== 'string' || key.trim().length === 0) {
51
- throw new Error('Invalid file key: empty or invalid');
54
+ throw new BadRequestException({
55
+ message: 'Invalid file key: empty or invalid',
56
+ messageKey: SYSTEM_MESSAGES.INVALID_FILE_KEY
57
+ });
52
58
  }
53
59
  if (key.includes('\0')) {
54
- throw new Error('Invalid file key: contains null bytes');
60
+ throw new BadRequestException({
61
+ message: 'Invalid file key: contains null bytes',
62
+ messageKey: SYSTEM_MESSAGES.INVALID_FILE_KEY
63
+ });
55
64
  }
56
65
  }
57
66
  /**
@@ -68,7 +77,6 @@ import { v4 as uuidv4 } from 'uuid';
68
77
  await fs.mkdir(this.basePath, {
69
78
  recursive: true
70
79
  });
71
- this.logger.log(`Local Provider initialized: ${this.basePath} (relative: ${this.relativeBasePath})`);
72
80
  }
73
81
  async uploadFile(file, options) {
74
82
  let processedBuffer = file.buffer;
@@ -101,7 +109,6 @@ import { v4 as uuidv4 } from 'uuid';
101
109
  // Generate key that includes the base path (relative to cwd)
102
110
  // This makes the key self-contained for serving files
103
111
  const relativeKey = path.join(this.relativeBasePath, options.folderPath || '', fileName).replace(/\\/g, '/'); // Normalize path separators
104
- this.logger.log(`Uploaded file to local storage: ${relativeKey}`);
105
112
  return {
106
113
  name: fileName,
107
114
  key: relativeKey,
@@ -131,26 +138,22 @@ import { v4 as uuidv4 } from 'uuid';
131
138
  try {
132
139
  await fs.access(fallbackPath);
133
140
  filePath = fallbackPath;
134
- this.logger.debug(`Using fallback path for delete: ${fallbackPath}`);
135
141
  } catch {
136
- // File doesn't exist at either location
137
- this.logger.warn(`File not found for deletion: ${key}`);
142
+ // File doesn't exist at either location - silent return
138
143
  return;
139
144
  }
140
145
  }
141
146
  await fs.unlink(filePath);
142
- this.logger.log(`Deleted file from local storage: ${key}`);
143
147
  } catch (error) {
144
148
  // Re-throw security errors
145
149
  if (error instanceof Error && error.message.includes('Invalid')) {
146
150
  throw error;
147
151
  }
148
- this.logger.warn(`Failed to delete file from local storage: ${key}`);
152
+ // Silent failure for other errors
149
153
  }
150
154
  }
151
155
  async deleteMultipleFiles(keys) {
152
156
  await Promise.all(keys.map((key)=>this.deleteFile(key)));
153
- this.logger.log(`Deleted ${keys.length} files from local storage`);
154
157
  }
155
158
  async generatePresignedUrl(key, _expiresInSeconds) {
156
159
  // Return public URL or relative path
@@ -172,7 +175,6 @@ import { v4 as uuidv4 } from 'uuid';
172
175
  }
173
176
  }
174
177
  constructor(){
175
- _define_property(this, "logger", new Logger(LocalProvider.name));
176
178
  _define_property(this, "basePath", '');
177
179
  _define_property(this, "baseUrl", '');
178
180
  _define_property(this, "relativeBasePath", ''); // Path relative to cwd for key generation
@@ -14,7 +14,8 @@ function _define_property(obj, key, value) {
14
14
  /**
15
15
  * Optional AWS S3 Storage Provider
16
16
  * Requires: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
17
- */ import { Logger } from '@nestjs/common';
17
+ */ import { InternalServerErrorException } from '@nestjs/common';
18
+ import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
18
19
  import { v4 as uuidv4 } from 'uuid';
19
20
  import { ImageCompressor } from '../utils/image-compressor.util';
20
21
  export class S3Provider {
@@ -31,9 +32,14 @@ export class S3Provider {
31
32
  secretAccessKey: config.secretAccessKey
32
33
  } : undefined
33
34
  });
34
- this.logger.log(`S3 Provider initialized: region=${config.region}, bucket=${config.bucket}`);
35
35
  } catch {
36
- throw new Error('AWS SDK not found. Install: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner');
36
+ throw new InternalServerErrorException({
37
+ message: 'AWS SDK not found',
38
+ messageKey: SYSTEM_MESSAGES.SDK_NOT_INSTALLED,
39
+ messageParams: {
40
+ sdk: '@aws-sdk/client-s3'
41
+ }
42
+ });
37
43
  }
38
44
  }
39
45
  async uploadFile(file, options) {
@@ -77,7 +83,6 @@ export class S3Provider {
77
83
  Bucket: this.bucketName,
78
84
  Key: key
79
85
  }));
80
- this.logger.log(`Deleted file from S3: ${key}`);
81
86
  }
82
87
  async deleteMultipleFiles(keys) {
83
88
  const { DeleteObjectsCommand } = await import('@aws-sdk/client-s3');
@@ -89,7 +94,6 @@ export class S3Provider {
89
94
  }))
90
95
  }
91
96
  }));
92
- this.logger.log(`Deleted ${keys.length} files from S3`);
93
97
  }
94
98
  async generatePresignedUrl(key, expiresInSeconds = 3600) {
95
99
  const { GetObjectCommand } = await import('@aws-sdk/client-s3');
@@ -113,7 +117,6 @@ export class S3Provider {
113
117
  }
114
118
  }
115
119
  constructor(){
116
- _define_property(this, "logger", new Logger(S3Provider.name));
117
120
  _define_property(this, "s3", void 0);
118
121
  _define_property(this, "bucketName", '');
119
122
  _define_property(this, "region", '');
@@ -1,3 +1,16 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
1
14
  /**
2
15
  * OPTIONAL SFTP Provider
3
16
  *
@@ -13,21 +26,9 @@
13
26
  * import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
14
27
  *
15
28
  * StorageProviderRegistry.register(FileLocationEnum.SFTP, SftpProvider);
16
- */ function _define_property(obj, key, value) {
17
- if (key in obj) {
18
- Object.defineProperty(obj, key, {
19
- value: value,
20
- enumerable: true,
21
- configurable: true,
22
- writable: true
23
- });
24
- } else {
25
- obj[key] = value;
26
- }
27
- return obj;
28
- }
29
+ */ import { InternalServerErrorException } from '@nestjs/common';
30
+ import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
29
31
  import { ImageCompressor } from '../utils/image-compressor.util';
30
- import { Logger } from '@nestjs/common';
31
32
  import * as path from 'path';
32
33
  import { v4 as uuidv4 } from 'uuid';
33
34
  /**
@@ -54,10 +55,14 @@ import { v4 as uuidv4 } from 'uuid';
54
55
  await this.connect();
55
56
  // Ensure base directory exists
56
57
  await this.ensureDirectory(this.config.basePath);
57
- this.logger.log(`SFTP Provider initialized: ${config.username}@${config.host}:${config.port || 22}`);
58
- } catch (_error) {
59
- this.logger.error('Failed to initialize SftpProvider. Make sure ssh2-sftp-client is installed.');
60
- throw new Error('SFTP client not found. Install it with: npm install ssh2-sftp-client');
58
+ } catch {
59
+ throw new InternalServerErrorException({
60
+ message: 'SFTP client not found',
61
+ messageKey: SYSTEM_MESSAGES.SDK_NOT_INSTALLED,
62
+ messageParams: {
63
+ sdk: 'ssh2-sftp-client'
64
+ }
65
+ });
61
66
  }
62
67
  }
63
68
  async connect() {
@@ -75,7 +80,7 @@ import { v4 as uuidv4 } from 'uuid';
75
80
  async ensureDirectory(dirPath) {
76
81
  try {
77
82
  await this.sftp.mkdir(dirPath, true);
78
- } catch (_error) {
83
+ } catch {
79
84
  // Directory might already exist
80
85
  }
81
86
  }
@@ -115,14 +120,12 @@ import { v4 as uuidv4 } from 'uuid';
115
120
  await this.connect();
116
121
  try {
117
122
  await this.sftp.delete(key);
118
- this.logger.log(`Deleted file from SFTP: ${key}`);
119
- } catch (_error) {
120
- this.logger.warn(`Failed to delete file from SFTP: ${key}`);
123
+ } catch {
124
+ // Silent failure
121
125
  }
122
126
  }
123
127
  async deleteMultipleFiles(keys) {
124
128
  await Promise.all(keys.map((key)=>this.deleteFile(key)));
125
- this.logger.log(`Deleted ${keys.length} files from SFTP`);
126
129
  }
127
130
  async generatePresignedUrl(key, _expiresInSeconds) {
128
131
  // SFTP doesn't support presigned URLs, return the key
@@ -145,7 +148,6 @@ import { v4 as uuidv4 } from 'uuid';
145
148
  }
146
149
  }
147
150
  constructor(){
148
- _define_property(this, "logger", new Logger(SftpProvider.name));
149
151
  _define_property(this, "sftp", void 0); // SftpClient
150
152
  _define_property(this, "config", void 0);
151
153
  _define_property(this, "connected", false);
@@ -25,8 +25,9 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
28
+ import { Inject, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
29
29
  import * as crypto from 'crypto';
30
+ import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
30
31
  import { StorageConfigService } from '../services';
31
32
  import { StorageProviderRegistry } from './storage-provider.registry';
32
33
  export class StorageFactoryService {
@@ -36,17 +37,30 @@ export class StorageFactoryService {
36
37
  if (cached) return cached;
37
38
  const ProviderClass = StorageProviderRegistry.get(config.provider);
38
39
  if (!ProviderClass) {
39
- throw new NotFoundException(`Storage provider '${config.provider}' not registered. Available: ${StorageProviderRegistry.getAll().join(', ')}`);
40
+ throw new NotFoundException({
41
+ message: `Storage provider '${config.provider}' not registered. Available: ${StorageProviderRegistry.getAll().join(', ')}`,
42
+ messageKey: SYSTEM_MESSAGES.SERVICE_NOT_AVAILABLE,
43
+ messageParams: {
44
+ provider: config.provider,
45
+ available: StorageProviderRegistry.getAll().join(', ')
46
+ }
47
+ });
40
48
  }
41
49
  try {
42
50
  const instance = new ProviderClass();
43
51
  await this.initializeProvider(instance, config);
44
52
  this.cache.set(cacheKey, instance);
45
- this.logger.log(`Created provider: ${config.provider} (${cacheKey})`);
46
53
  return instance;
47
54
  } catch (error) {
48
- const message = error instanceof Error ? error.message : 'Unknown error';
49
- throw new Error(`Failed to initialize '${config.provider}': ${message}`);
55
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
56
+ throw new InternalServerErrorException({
57
+ message: `Failed to initialize '${config.provider}': ${errorMessage}`,
58
+ messageKey: SYSTEM_MESSAGES.INTERNAL_ERROR,
59
+ messageParams: {
60
+ provider: config.provider,
61
+ error: errorMessage
62
+ }
63
+ });
50
64
  }
51
65
  }
52
66
  async getProvider(providerName) {
@@ -66,11 +80,9 @@ export class StorageFactoryService {
66
80
  this.cache.clear();
67
81
  }
68
82
  async onModuleDestroy() {
69
- this.logger.log('Cleaning up storage providers...');
70
- const closePromises = Array.from(this.cache.entries()).filter(([, p])=>'close' in p && typeof p.close === 'function').map(([key, p])=>p.close().then(()=>this.logger.debug(`Closed: ${key}`)).catch((e)=>this.logger.warn(`Failed to close ${key}: ${e.message}`)));
83
+ const closePromises = Array.from(this.cache.entries()).filter(([, p])=>'close' in p && typeof p.close === 'function').map(([, p])=>p.close().catch(()=>{}));
71
84
  await Promise.allSettled(closePromises);
72
85
  this.cache.clear();
73
- this.logger.log('Storage provider cleanup complete');
74
86
  }
75
87
  // ─── Private Helpers ──────────────────────────────────────────────────────────
76
88
  generateCacheKey(config) {
@@ -91,17 +103,14 @@ export class StorageFactoryService {
91
103
  ...config.config,
92
104
  baseUrl: appUrl
93
105
  };
94
- this.logger.debug(`Using appUrl as baseUrl: ${appUrl}`);
95
106
  }
96
107
  }
97
108
  await instance.initialize(initConfig);
98
109
  }
99
110
  constructor(configService){
100
111
  _define_property(this, "configService", void 0);
101
- _define_property(this, "logger", void 0);
102
112
  _define_property(this, "cache", void 0);
103
113
  this.configService = configService;
104
- this.logger = new Logger(StorageFactoryService.name);
105
114
  this.cache = new Map();
106
115
  }
107
116
  }
@@ -27,7 +27,8 @@ function _ts_param(paramIndex, decorator) {
27
27
  }
28
28
  import { HybridCache, RequestScopedApiService } from '@flusys/nestjs-shared/classes';
29
29
  import { UtilsService } from '@flusys/nestjs-shared/modules';
30
- import { applyCompanyFilter, ErrorHandler } from '@flusys/nestjs-shared/utils';
30
+ import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
31
+ import { FILE_MESSAGES, FOLDER_MESSAGES, UPLOAD_MESSAGES } from '../config';
31
32
  import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
32
33
  import { Brackets, In } from 'typeorm';
33
34
  import { StorageConfigService } from './storage-config.service';
@@ -67,7 +68,10 @@ export class FileManagerService extends RequestScopedApiService {
67
68
  id: dto.id
68
69
  }
69
70
  });
70
- if (!existing) throw new NotFoundException('Entity not found for update');
71
+ if (!existing) throw new NotFoundException({
72
+ message: 'Entity not found for update',
73
+ messageKey: FILE_MESSAGES.NOT_FOUND
74
+ });
71
75
  entity = existing;
72
76
  }
73
77
  const validatedFolder = dto.folderId ? await this.validateFolder(dto.folderId, user) : null;
@@ -97,7 +101,7 @@ export class FileManagerService extends RequestScopedApiService {
97
101
  }
98
102
  async getFilterQuery(query, filter, _user) {
99
103
  for (const [key, value] of Object.entries(filter)){
100
- if (key === 'contentType') {
104
+ if (key === 'contentType' && typeof value === 'string') {
101
105
  this.applyContentTypeFilter(query, value);
102
106
  } else {
103
107
  query.andWhere(`${this.entityName}.${key} = :value`, {
@@ -169,8 +173,7 @@ export class FileManagerService extends RequestScopedApiService {
169
173
  ...item,
170
174
  providerName: item.storageConfigId ? nameMap.get(item.storageConfigId) : undefined
171
175
  }));
172
- } catch (error) {
173
- this.logger.warn(`Failed to fetch provider names: ${ErrorHandler.getErrorMessage(error)}`);
176
+ } catch {
174
177
  return items.map((item)=>({
175
178
  ...item,
176
179
  providerName: undefined
@@ -180,7 +183,10 @@ export class FileManagerService extends RequestScopedApiService {
180
183
  async getFiles(dtos, protocol, host, user) {
181
184
  await this.ensureRepositoryInitialized();
182
185
  const ids = dtos.map((d)=>d.id).filter(Boolean);
183
- if (!ids.length) throw new BadRequestException('No valid file IDs provided');
186
+ if (!ids.length) throw new BadRequestException({
187
+ message: 'No valid file IDs provided',
188
+ messageKey: UPLOAD_MESSAGES.NO_FILES_PROVIDED
189
+ });
184
190
  const files = await this.repository.findBy({
185
191
  id: In(ids)
186
192
  });
@@ -194,17 +200,13 @@ export class FileManagerService extends RequestScopedApiService {
194
200
  if (updatedFiles.length) {
195
201
  try {
196
202
  await this.repository.save(updatedFiles);
197
- } catch (error) {
198
- this.logger.error(`Failed to save updated URLs: ${ErrorHandler.getErrorMessage(error)}`);
203
+ } catch {
204
+ // Silent failure - URL update is non-critical
199
205
  }
200
206
  }
201
207
  return responses;
202
208
  }
203
209
  // ─── Private Helpers ────────────────────────────────────────────────────────
204
- async getStorageConfigRepository() {
205
- const entity = this.storageConfig.isCompanyFeatureEnabled() ? (await import('../entities')).StorageConfigWithCompany : (await import('../entities')).StorageConfig;
206
- return this.dataSourceProvider.getRepository(entity);
207
- }
208
210
  buildWhereWithCompany(id, user) {
209
211
  const where = {
210
212
  id
@@ -214,6 +216,10 @@ export class FileManagerService extends RequestScopedApiService {
214
216
  }
215
217
  return where;
216
218
  }
219
+ async getStorageConfigRepository() {
220
+ const entity = this.storageConfig.isCompanyFeatureEnabled() ? (await import('../entities')).StorageConfigWithCompany : (await import('../entities')).StorageConfig;
221
+ return this.dataSourceProvider.getRepository(entity);
222
+ }
217
223
  async getStorageConfigBasePath(configId) {
218
224
  try {
219
225
  const repo = await this.getStorageConfigRepository();
@@ -227,8 +233,7 @@ export class FileManagerService extends RequestScopedApiService {
227
233
  ]
228
234
  });
229
235
  return config?.config?.basePath?.replace(/^\.\//, '') ?? null;
230
- } catch (error) {
231
- this.logger.warn(`Failed to get basePath for ${configId}: ${ErrorHandler.getErrorMessage(error)}`);
236
+ } catch {
232
237
  return null;
233
238
  }
234
239
  }
@@ -239,7 +244,13 @@ export class FileManagerService extends RequestScopedApiService {
239
244
  where: this.buildWhereWithCompany(folderId, user)
240
245
  });
241
246
  if (!folder) {
242
- throw new BadRequestException(`Folder ${folderId} not found or access denied`);
247
+ throw new BadRequestException({
248
+ message: `Folder "${folderId}" not found or access denied`,
249
+ messageKey: FOLDER_MESSAGES.NOT_FOUND,
250
+ messageParams: {
251
+ folderId
252
+ }
253
+ });
243
254
  }
244
255
  return folder;
245
256
  }
@@ -252,8 +263,7 @@ export class FileManagerService extends RequestScopedApiService {
252
263
  where: this.buildWhereWithCompany(configId, user)
253
264
  });
254
265
  return config?.storage || dto.location || FileLocationEnum.LOCAL;
255
- } catch (error) {
256
- this.logger.warn(`Failed to resolve storage location: ${ErrorHandler.getErrorMessage(error)}`);
266
+ } catch {
257
267
  return dto.location || FileLocationEnum.LOCAL;
258
268
  }
259
269
  }
@@ -299,12 +309,10 @@ export class FileManagerService extends RequestScopedApiService {
299
309
  return grouped;
300
310
  }
301
311
  getFileBaseUrl(protocol, host) {
302
- return this.storageConfig.getAppUrl()?.replace(/\/$/, '') ?? `${protocol}://${host}`;
312
+ const appUrl = this.storageConfig.getAppUrl()?.replace(/\/$/, '');
313
+ return appUrl || `${protocol}://${host}`;
303
314
  }
304
315
  async refreshFileUrl(file, protocol, host, now, user) {
305
- if (!file.storageConfigId) {
306
- this.logger.warn(`File ${file.id} has no storageConfigId`);
307
- }
308
316
  const isCloudProvider = file.location === FileLocationEnum.AWS || file.location === FileLocationEnum.AZURE;
309
317
  const needsNewUrl = !file.url || isCloudProvider && (typeof file.expiresAt !== 'number' || now >= file.expiresAt);
310
318
  if (needsNewUrl && file.storageConfigId) {
@@ -312,8 +320,7 @@ export class FileManagerService extends RequestScopedApiService {
312
320
  file.url = await this.uploadService.makeFileUrl(file.key, file.storageConfigId, URL_EXPIRY_SECONDS, user);
313
321
  file.expiresAt = now + URL_EXPIRY_SECONDS * 1000;
314
322
  return true;
315
- } catch (error) {
316
- this.logger.error(`Failed to generate URL for ${file.id}: ${ErrorHandler.getErrorMessage(error)}`);
323
+ } catch {
317
324
  file.url = `${this.getFileBaseUrl(protocol, host)}/storage/upload/file/${file.key}`;
318
325
  }
319
326
  }
@@ -342,7 +349,7 @@ export class FileManagerService extends RequestScopedApiService {
342
349
  };
343
350
  }
344
351
  constructor(cacheManager, utilsService, uploadService, storageConfig, dataSourceProvider){
345
- super('file_manager', null, cacheManager, utilsService, FileManagerService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "uploadService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.uploadService = uploadService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
352
+ super('file_manager', null, cacheManager, utilsService, FileManagerService.name, true, 'storage'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "uploadService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.uploadService = uploadService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
346
353
  }
347
354
  }
348
355
  FileManagerService = _ts_decorate([
@@ -39,6 +39,7 @@ export class FolderService extends RequestScopedApiService {
39
39
  getDataSourceProvider() {
40
40
  return this.dataSourceProvider;
41
41
  }
42
+ // ─── Override Methods ───────────────────────────────────────────────────────
42
43
  async convertSingleDtoToEntity(dto, user) {
43
44
  const entity = await super.convertSingleDtoToEntity(dto, user);
44
45
  if (this.storageConfig.isCompanyFeatureEnabled()) {
@@ -74,7 +75,7 @@ export class FolderService extends RequestScopedApiService {
74
75
  return result;
75
76
  }
76
77
  constructor(cacheManager, utilsService, storageConfig, dataSourceProvider){
77
- 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;
78
+ super('folder', null, cacheManager, utilsService, FolderService.name, true, 'storage'), _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;
78
79
  }
79
80
  }
80
81
  FolderService = _ts_decorate([
@@ -26,7 +26,8 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
29
- import { Inject, Injectable, Logger, Optional, Scope } from '@nestjs/common';
29
+ import { Inject, Injectable, InternalServerErrorException, Optional, Scope } from '@nestjs/common';
30
+ import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
30
31
  import { REQUEST } from '@nestjs/core';
31
32
  import { Request } from 'express';
32
33
  import { StorageConfigService } from './storage-config.service';
@@ -90,7 +91,10 @@ export class StorageDataSourceProvider extends MultiTenantDataSourceService {
90
91
  }
91
92
  const config = this.getDefaultDatabaseConfig();
92
93
  if (!config) {
93
- throw new Error('No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.');
94
+ throw new InternalServerErrorException({
95
+ message: 'No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.',
96
+ messageKey: SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
97
+ });
94
98
  }
95
99
  // Create connection with lock to prevent race conditions
96
100
  const connectionPromise = this.createDataSourceFromConfig(config);
@@ -129,7 +133,7 @@ export class StorageDataSourceProvider extends MultiTenantDataSourceService {
129
133
  }
130
134
  }
131
135
  constructor(configService, request){
132
- super(StorageDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new Logger(StorageDataSourceProvider.name);
136
+ super(StorageDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
133
137
  }
134
138
  }
135
139
  // Override parent's static properties to have Storage-specific cache
@@ -39,6 +39,7 @@ export class StorageProviderConfigService extends RequestScopedApiService {
39
39
  getDataSourceProvider() {
40
40
  return this.dataSourceProvider;
41
41
  }
42
+ // ─── Override Methods ───────────────────────────────────────────────────────
42
43
  async convertSingleDtoToEntity(dto, user) {
43
44
  const entity = await super.convertSingleDtoToEntity(dto, user);
44
45
  if (this.storageConfig.isCompanyFeatureEnabled()) {
@@ -120,7 +121,7 @@ export class StorageProviderConfigService extends RequestScopedApiService {
120
121
  });
121
122
  }
122
123
  constructor(cacheManager, utilsService, storageConfig, dataSourceProvider){
123
- super('storageConfig', null, cacheManager, utilsService, StorageProviderConfigService.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;
124
+ super('storageConfig', null, cacheManager, utilsService, StorageProviderConfigService.name, true, 'storage'), _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;
124
125
  }
125
126
  }
126
127
  StorageProviderConfigService = _ts_decorate([