@flusys/nestjs-storage 0.1.0-beta.2 → 1.0.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +3 -7
  2. package/cjs/config/storage-config.service.js +31 -0
  3. package/cjs/controllers/file-manager.controller.js +8 -6
  4. package/cjs/controllers/folder.controller.js +3 -4
  5. package/cjs/controllers/storage-config.controller.js +3 -4
  6. package/cjs/controllers/upload.controller.js +22 -169
  7. package/cjs/dtos/file-manager.dto.js +36 -2
  8. package/cjs/dtos/upload.dto.js +16 -0
  9. package/cjs/middlewares/file-serve.middleware.js +204 -0
  10. package/cjs/middlewares/index.js +18 -0
  11. package/cjs/modules/storage.module.js +58 -14
  12. package/cjs/providers/azure-provider.optional.js +1 -2
  13. package/cjs/providers/local-provider.js +43 -11
  14. package/cjs/providers/storage-factory.service.js +49 -5
  15. package/cjs/services/file-manager.service.js +134 -9
  16. package/cjs/services/folder.service.js +17 -48
  17. package/cjs/services/storage-datasource.provider.js +10 -16
  18. package/cjs/services/storage-provider-config.service.js +26 -32
  19. package/cjs/services/upload.service.js +135 -24
  20. package/cjs/utils/image-compressor.util.js +43 -5
  21. package/config/storage-config.service.d.ts +2 -0
  22. package/controllers/file-manager.controller.d.ts +1 -1
  23. package/controllers/upload.controller.d.ts +5 -4
  24. package/dtos/file-manager.dto.d.ts +4 -0
  25. package/dtos/upload.dto.d.ts +2 -0
  26. package/fesm/config/storage-config.service.js +31 -0
  27. package/fesm/controllers/file-manager.controller.js +8 -6
  28. package/fesm/controllers/folder.controller.js +5 -6
  29. package/fesm/controllers/storage-config.controller.js +5 -6
  30. package/fesm/controllers/upload.controller.js +25 -131
  31. package/fesm/dtos/file-manager.dto.js +36 -2
  32. package/fesm/dtos/upload.dto.js +16 -0
  33. package/fesm/middlewares/file-serve.middleware.js +153 -0
  34. package/fesm/middlewares/index.js +1 -0
  35. package/fesm/modules/storage.module.js +60 -16
  36. package/fesm/providers/azure-provider.optional.js +1 -2
  37. package/fesm/providers/local-provider.js +43 -11
  38. package/fesm/providers/storage-factory.service.js +50 -6
  39. package/fesm/services/file-manager.service.js +134 -9
  40. package/fesm/services/folder.service.js +18 -49
  41. package/fesm/services/storage-datasource.provider.js +10 -16
  42. package/fesm/services/storage-provider-config.service.js +26 -32
  43. package/fesm/services/upload.service.js +135 -24
  44. package/fesm/utils/image-compressor.util.js +3 -1
  45. package/interfaces/file-manager.interface.d.ts +2 -0
  46. package/interfaces/storage-module-options.interface.d.ts +2 -0
  47. package/interfaces/storage-provider.interface.d.ts +2 -0
  48. package/middlewares/file-serve.middleware.d.ts +9 -0
  49. package/middlewares/index.d.ts +1 -0
  50. package/modules/storage.module.d.ts +3 -2
  51. package/package.json +26 -11
  52. package/providers/local-provider.d.ts +2 -1
  53. package/providers/storage-factory.service.d.ts +4 -0
  54. package/services/file-manager.service.d.ts +7 -1
  55. package/services/folder.service.d.ts +1 -2
  56. package/services/storage-provider-config.service.d.ts +2 -2
  57. package/services/upload.service.d.ts +6 -2
@@ -33,14 +33,19 @@ import { v4 as uuidv4 } from 'uuid';
33
33
  */ export class LocalProvider {
34
34
  /**
35
35
  * Initialize Local File System provider with configuration
36
+ * @param config.basePath - Base path for file storage (default: './uploads')
37
+ * @param config.baseUrl - Optional base URL for generating file URLs
36
38
  */ async initialize(config) {
37
- this.basePath = path.resolve(config.basePath);
38
- this.baseUrl = config.baseUrl || '';
39
+ // Store original relative path for key generation
40
+ this.relativeBasePath = config?.basePath || './uploads';
41
+ // Resolve to absolute path for file operations
42
+ this.basePath = path.resolve(this.relativeBasePath);
43
+ this.baseUrl = config?.baseUrl || '';
39
44
  // Ensure base directory exists
40
45
  await fs.mkdir(this.basePath, {
41
46
  recursive: true
42
47
  });
43
- this.logger.log(`Local Provider initialized: ${this.basePath}`);
48
+ this.logger.log(`Local Provider initialized: ${this.basePath} (relative: ${this.relativeBasePath})`);
44
49
  }
45
50
  async uploadFile(file, options) {
46
51
  let processedBuffer = file.buffer;
@@ -66,8 +71,9 @@ import { v4 as uuidv4 } from 'uuid';
66
71
  });
67
72
  // Write file to disk
68
73
  await fs.writeFile(filePath, processedBuffer);
69
- // Generate relative key (for URL generation and deletion)
70
- const relativeKey = path.relative(this.basePath, filePath);
74
+ // Generate key that includes the base path (relative to cwd)
75
+ // This makes the key self-contained for serving files
76
+ const relativeKey = path.join(this.relativeBasePath, options.folderPath || '', fileName).replace(/\\/g, '/'); // Normalize path separators
71
77
  this.logger.log(`Uploaded file to local storage: ${relativeKey}`);
72
78
  return {
73
79
  name: fileName,
@@ -81,7 +87,24 @@ import { v4 as uuidv4 } from 'uuid';
81
87
  }
82
88
  async deleteFile(key) {
83
89
  try {
84
- const filePath = path.join(this.basePath, key);
90
+ // Key now includes the basePath, resolve from cwd
91
+ let filePath = path.resolve(key);
92
+ // Check if file exists at the resolved path
93
+ try {
94
+ await fs.access(filePath);
95
+ } catch {
96
+ // Fallback: try with basePath prefix (for old keys without basePath)
97
+ const fallbackPath = path.join(this.basePath, key);
98
+ try {
99
+ await fs.access(fallbackPath);
100
+ filePath = fallbackPath;
101
+ this.logger.debug(`Using fallback path for delete: ${fallbackPath}`);
102
+ } catch {
103
+ // File doesn't exist at either location
104
+ this.logger.warn(`File not found for deletion: ${key}`);
105
+ return;
106
+ }
107
+ }
85
108
  await fs.unlink(filePath);
86
109
  this.logger.log(`Deleted file from local storage: ${key}`);
87
110
  } catch (_error) {
@@ -95,8 +118,13 @@ import { v4 as uuidv4 } from 'uuid';
95
118
  async generatePresignedUrl(key, _expiresInSeconds) {
96
119
  // Return public URL or relative path
97
120
  // Note: Local storage doesn't support true presigned URLs
98
- // If baseUrl is provided, return full URL, otherwise return relative path
99
- return this.baseUrl ? `${this.baseUrl}/${key}` : `/${key}`;
121
+ // If baseUrl is provided, return full URL with file serving endpoint, otherwise return relative path
122
+ if (this.baseUrl) {
123
+ // Construct URL with the file serving endpoint
124
+ const baseUrlWithoutTrailingSlash = this.baseUrl.replace(/\/$/, '');
125
+ return `${baseUrlWithoutTrailingSlash}/storage/upload/file/${key}`;
126
+ }
127
+ return `/storage/upload/file/${key}`;
100
128
  }
101
129
  async healthCheck() {
102
130
  try {
@@ -108,14 +136,16 @@ import { v4 as uuidv4 } from 'uuid';
108
136
  }
109
137
  /**
110
138
  * Get the absolute path for a file key
139
+ * Key now includes basePath, so resolve from cwd
111
140
  */ getAbsolutePath(key) {
112
- return path.join(this.basePath, key);
141
+ return path.resolve(key);
113
142
  }
114
143
  /**
115
144
  * Check if a file exists
116
145
  */ async fileExists(key) {
117
146
  try {
118
- const filePath = path.join(this.basePath, key);
147
+ // Key now includes basePath, resolve from cwd
148
+ const filePath = path.resolve(key);
119
149
  await fs.access(filePath);
120
150
  return true;
121
151
  } catch {
@@ -125,7 +155,8 @@ import { v4 as uuidv4 } from 'uuid';
125
155
  /**
126
156
  * Get file stats
127
157
  */ async getFileStats(key) {
128
- const filePath = path.join(this.basePath, key);
158
+ // Key now includes basePath, resolve from cwd
159
+ const filePath = path.resolve(key);
129
160
  const stats = await fs.stat(filePath);
130
161
  return {
131
162
  size: stats.size,
@@ -137,5 +168,6 @@ import { v4 as uuidv4 } from 'uuid';
137
168
  _define_property(this, "logger", new Logger(LocalProvider.name));
138
169
  _define_property(this, "basePath", '');
139
170
  _define_property(this, "baseUrl", '');
171
+ _define_property(this, "relativeBasePath", ''); // Path relative to cwd for key generation
140
172
  }
141
173
  }
@@ -17,9 +17,18 @@ function _ts_decorate(decorators, target, key, desc) {
17
17
  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;
18
18
  return c > 3 && r && Object.defineProperty(target, key, r), r;
19
19
  }
20
+ function _ts_metadata(k, v) {
21
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
+ }
23
+ function _ts_param(paramIndex, decorator) {
24
+ return function(target, key) {
25
+ decorator(target, key, paramIndex);
26
+ };
27
+ }
20
28
  import { StorageProviderRegistry } from './storage-provider.registry';
21
- import { Injectable, Logger, NotFoundException } from '@nestjs/common';
29
+ import { Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
22
30
  import * as crypto from 'crypto';
31
+ import { StorageConfigService } from '../config';
23
32
  export class StorageFactoryService {
24
33
  /**
25
34
  * Generate a stable cache key for provider configuration
@@ -49,7 +58,19 @@ export class StorageFactoryService {
49
58
  const instance = new ProviderClass();
50
59
  // Initialize if method exists
51
60
  if ('initialize' in instance && typeof instance.initialize === 'function') {
52
- await instance.initialize(config.config);
61
+ // For local provider, inject appUrl as fallback for baseUrl
62
+ let initConfig = config.config;
63
+ if (config.provider === 'local' && !config.config?.baseUrl) {
64
+ const appUrl = this.storageConfigService.getAppUrl();
65
+ if (appUrl) {
66
+ initConfig = {
67
+ ...config.config,
68
+ baseUrl: appUrl
69
+ };
70
+ this.logger.debug(`Using appUrl from config as baseUrl: ${appUrl}`);
71
+ }
72
+ }
73
+ await instance.initialize(initConfig);
53
74
  }
54
75
  // Cache the instance
55
76
  this.providerCache.set(providerKey, instance);
@@ -91,6 +112,20 @@ export class StorageFactoryService {
91
112
  return StorageProviderRegistry.getAll();
92
113
  }
93
114
  /**
115
+ * Get the basePath from cached local provider
116
+ * Returns null if no local provider is cached
117
+ */ getLocalProviderBasePath() {
118
+ // Find cached local provider
119
+ const localKey = Array.from(this.providerCache.keys()).find((key)=>key.startsWith('local'));
120
+ if (localKey) {
121
+ const provider = this.providerCache.get(localKey);
122
+ if (provider && 'basePath' in provider) {
123
+ return provider.basePath;
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+ /**
94
129
  * Cleanup all cached provider connections on module destroy
95
130
  * Ensures SFTP connections and other resources are properly released
96
131
  */ async onModuleDestroy() {
@@ -106,11 +141,20 @@ export class StorageFactoryService {
106
141
  this.providerCache.clear();
107
142
  this.logger.log('Storage provider cleanup complete');
108
143
  }
109
- constructor(){
110
- _define_property(this, "logger", new Logger(StorageFactoryService.name));
111
- _define_property(this, "providerCache", new Map());
144
+ constructor(storageConfigService){
145
+ _define_property(this, "storageConfigService", void 0);
146
+ _define_property(this, "logger", void 0);
147
+ _define_property(this, "providerCache", void 0);
148
+ this.storageConfigService = storageConfigService;
149
+ this.logger = new Logger(StorageFactoryService.name);
150
+ this.providerCache = new Map();
112
151
  }
113
152
  }
114
153
  StorageFactoryService = _ts_decorate([
115
- Injectable()
154
+ Injectable(),
155
+ _ts_param(0, Inject(StorageConfigService)),
156
+ _ts_metadata("design:type", Function),
157
+ _ts_metadata("design:paramtypes", [
158
+ typeof StorageConfigService === "undefined" ? Object : StorageConfigService
159
+ ])
116
160
  ], StorageFactoryService);
@@ -39,6 +39,13 @@ const USER_ACTION_PERMISSION_CACHE_KEY = 'user_action_permission';
39
39
  const SHOW_PRIVATE_FILE_ACTION = 'storage.file.viewPrivate';
40
40
  export class FileManagerService extends RequestScopedApiService {
41
41
  /**
42
+ * Get base URL for file serving
43
+ * Priority: appUrl config > request headers
44
+ */ getFileBaseUrl(protocol, host) {
45
+ const appUrl = this.storageConfig.getAppUrl();
46
+ return appUrl?.replace(/\/$/, '') ?? `${protocol}://${host}`;
47
+ }
48
+ /**
42
49
  * Resolve entity class for this service
43
50
  * @returns FileManager or FileManagerWithCompany based on configuration
44
51
  */ resolveEntity() {
@@ -137,6 +144,7 @@ export class FileManagerService extends RequestScopedApiService {
137
144
  'key',
138
145
  'url',
139
146
  'location',
147
+ 'storageConfigId',
140
148
  'isPrivate',
141
149
  'createdAt',
142
150
  'deletedAt'
@@ -147,6 +155,7 @@ export class FileManagerService extends RequestScopedApiService {
147
155
  if (this.storageConfig.isCompanyFeatureEnabled()) {
148
156
  selectFields.push('file_manager.companyId');
149
157
  }
158
+ // Join folder
150
159
  selectFields.push('folder.id');
151
160
  selectFields.push('folder.name');
152
161
  query.leftJoinAndSelect('file_manager.folder', 'folder');
@@ -156,6 +165,57 @@ export class FileManagerService extends RequestScopedApiService {
156
165
  isRaw: false
157
166
  };
158
167
  }
168
+ /**
169
+ * Enrich file list with provider names from storage_config table
170
+ * Call this method to add providerName to the list results
171
+ */ async enrichWithProviderNames(items) {
172
+ // Get unique storage config IDs
173
+ const configIds = [
174
+ ...new Set(items.map((item)=>item.storageConfigId).filter(Boolean))
175
+ ];
176
+ this.logger.debug(`enrichWithProviderNames: items count=${items.length}, configIds=${JSON.stringify(configIds)}`);
177
+ if (configIds.length === 0) {
178
+ this.logger.debug('enrichWithProviderNames: No storage config IDs found in items');
179
+ return items.map((item)=>({
180
+ ...item,
181
+ providerName: undefined
182
+ }));
183
+ }
184
+ // Fetch storage config names
185
+ try {
186
+ const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
187
+ const storageConfigEntity = enableCompanyFeature ? (await import('../entities')).StorageConfigWithCompany : (await import('../entities')).StorageConfig;
188
+ const storageConfigRepository = await this.dataSourceProvider.getRepository(storageConfigEntity);
189
+ const configs = await storageConfigRepository.find({
190
+ where: {
191
+ id: In(configIds)
192
+ },
193
+ select: [
194
+ 'id',
195
+ 'name'
196
+ ]
197
+ });
198
+ this.logger.debug(`enrichWithProviderNames: Found ${configs.length} configs: ${JSON.stringify(configs)}`);
199
+ // Create a map for quick lookup
200
+ const configNameMap = new Map(configs.map((c)=>[
201
+ c.id,
202
+ c.name
203
+ ]));
204
+ // Enrich items with provider names
205
+ const enrichedItems = items.map((item)=>({
206
+ ...item,
207
+ providerName: item.storageConfigId ? configNameMap.get(item.storageConfigId) : undefined
208
+ }));
209
+ this.logger.debug(`enrichWithProviderNames: First enriched item: ${JSON.stringify(enrichedItems[0])}`);
210
+ return enrichedItems;
211
+ } catch (error) {
212
+ this.logger.warn(`Failed to fetch provider names: ${error}`);
213
+ return items.map((item)=>({
214
+ ...item,
215
+ providerName: undefined
216
+ }));
217
+ }
218
+ }
159
219
  async getFilterQuery(query, filter, _user) {
160
220
  Object.entries(filter).forEach(([key, value])=>{
161
221
  if (key === 'contentType') {
@@ -218,7 +278,7 @@ export class FileManagerService extends RequestScopedApiService {
218
278
  }
219
279
  return result;
220
280
  }
221
- async beforeDeleteOperation(dto, _user, _queryRunner) {
281
+ async beforeDeleteOperation(dto, user, _queryRunner) {
222
282
  if (dto.type === 'permanent') {
223
283
  const ids = Array.isArray(dto.id) ? dto.id : [
224
284
  dto.id
@@ -231,13 +291,46 @@ export class FileManagerService extends RequestScopedApiService {
231
291
  fileManager.forEach((file)=>{
232
292
  const configId = file.storageConfigId || 'default';
233
293
  if (!filesByConfig.has(configId)) {
234
- filesByConfig.set(configId, []);
294
+ filesByConfig.set(configId, {
295
+ keys: [],
296
+ location: file.location
297
+ });
235
298
  }
236
- filesByConfig.get(configId).push(file.key);
299
+ filesByConfig.get(configId).keys.push(file.key);
237
300
  });
238
301
  // Delete files from each storage config
239
- for (const [configId, keys] of filesByConfig){
240
- await this.uploadService.deleteMultipleFile(keys, configId === 'default' ? undefined : configId);
302
+ for (const [configId, { keys, location }] of filesByConfig){
303
+ // For local files with old key format (no '/'), we need to prefix with basePath
304
+ let deleteKeys = keys;
305
+ if (location === FileLocationEnum.LOCAL && configId !== 'default') {
306
+ try {
307
+ const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
308
+ const storageConfigEntity = enableCompanyFeature ? (await import('../entities')).StorageConfigWithCompany : (await import('../entities')).StorageConfig;
309
+ const storageConfigRepository = await this.dataSourceProvider.getRepository(storageConfigEntity);
310
+ const config = await storageConfigRepository.findOne({
311
+ where: {
312
+ id: configId
313
+ },
314
+ select: [
315
+ 'id',
316
+ 'config'
317
+ ]
318
+ });
319
+ if (config && config.config?.basePath) {
320
+ const basePath = config.config.basePath.replace(/^\.\//, '');
321
+ // Convert old keys to new format
322
+ deleteKeys = keys.map((key)=>{
323
+ if (!key.includes('/')) {
324
+ return `${basePath}/${key}`;
325
+ }
326
+ return key;
327
+ });
328
+ }
329
+ } catch (error) {
330
+ this.logger.warn(`Failed to get basePath for delete: ${error}`);
331
+ }
332
+ }
333
+ await this.uploadService.deleteMultipleFile(deleteKeys, configId === 'default' ? undefined : configId, user ?? undefined, location);
241
334
  }
242
335
  }
243
336
  }
@@ -269,13 +362,43 @@ export class FileManagerService extends RequestScopedApiService {
269
362
  shouldUpdate = true;
270
363
  } catch (error) {
271
364
  this.logger.error(`Failed to generate URL for file ${file.id}: ${error?.message || 'Unknown error'}`);
272
- // Use fallback URL
273
- file.url = `${protocol}://${host}/storage/upload/file/${file.key}`;
365
+ // Use fallback URL with appUrl from config
366
+ const baseUrl = this.getFileBaseUrl(protocol, host);
367
+ file.url = `${baseUrl}/storage/upload/file/${file.key}`;
274
368
  }
275
369
  }
276
370
  // SFTP/Local files - always construct full URL (no expiry, but need full path)
277
371
  if (file.location === FileLocationEnum.SFTP || file.location === FileLocationEnum.LOCAL) {
278
- const expectedUrl = `${protocol}://${host}/storage/upload/file/${file.key}`;
372
+ // For backward compatibility: if key doesn't include basePath, look it up from config
373
+ let fileKey = file.key;
374
+ // Check if key looks like it's missing the basePath (old format)
375
+ // Old format: "uuid-filename.png"
376
+ // New format: "uploads/uuid-filename.png" or "uploads/company1/uuid-filename.png"
377
+ if (!fileKey.includes('/') && file.storageConfigId) {
378
+ try {
379
+ const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
380
+ const storageConfigEntity = enableCompanyFeature ? (await import('../entities')).StorageConfigWithCompany : (await import('../entities')).StorageConfig;
381
+ const storageConfigRepository = await this.dataSourceProvider.getRepository(storageConfigEntity);
382
+ const config = await storageConfigRepository.findOne({
383
+ where: {
384
+ id: file.storageConfigId
385
+ },
386
+ select: [
387
+ 'id',
388
+ 'config'
389
+ ]
390
+ });
391
+ if (config && config.config?.basePath) {
392
+ const basePath = config.config.basePath.replace(/^\.\//, ''); // Remove leading ./
393
+ fileKey = `${basePath}/${file.key}`;
394
+ this.logger.debug(`Prefixed old key with basePath: ${fileKey}`);
395
+ }
396
+ } catch (error) {
397
+ this.logger.warn(`Failed to get basePath for file ${file.id}: ${error}`);
398
+ }
399
+ }
400
+ const baseUrl = this.getFileBaseUrl(protocol, host);
401
+ const expectedUrl = `${baseUrl}/storage/upload/file/${fileKey}`;
279
402
  if (file.url !== expectedUrl) {
280
403
  file.url = expectedUrl;
281
404
  shouldUpdate = true;
@@ -286,7 +409,9 @@ export class FileManagerService extends RequestScopedApiService {
286
409
  id: file.id,
287
410
  name: file.name,
288
411
  contentType: file.contentType,
289
- url: file.url || ''
412
+ url: file.url || '',
413
+ location: file.location,
414
+ storageConfigId: file.storageConfigId || undefined
290
415
  };
291
416
  }));
292
417
  // Save only changed records
@@ -27,51 +27,29 @@ function _ts_param(paramIndex, decorator) {
27
27
  }
28
28
  import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
29
29
  import { UtilsService } from '@flusys/nestjs-shared/modules';
30
- import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
30
+ import { Inject, Injectable, Scope } from '@nestjs/common';
31
31
  import { StorageConfigService } from '../config';
32
32
  import { Folder, FolderWithCompany } from '../entities';
33
33
  import { StorageDataSourceProvider } from './storage-datasource.provider';
34
34
  export class FolderService extends RequestScopedApiService {
35
- /**
36
- * Resolve entity class for this service
37
- * @returns Folder or FolderWithCompany based on configuration
38
- */ resolveEntity() {
39
- const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
40
- return enableCompanyFeature ? FolderWithCompany : Folder;
35
+ resolveEntity() {
36
+ return this.storageConfig.isCompanyFeatureEnabled() ? FolderWithCompany : Folder;
41
37
  }
42
- /**
43
- * Get DataSource provider for this service
44
- * @returns StorageDataSourceProvider instance
45
- */ getDataSourceProvider() {
38
+ getDataSourceProvider() {
46
39
  return this.dataSourceProvider;
47
40
  }
41
+ // Entity Conversion
48
42
  async convertSingleDtoToEntity(dto, user) {
49
- let folder = {};
50
- // NOTE: Using 'id' in dto check instead of instanceof - instanceof may not work after esbuild bundling
51
- if ('id' in dto && dto.id && typeof dto.id === 'string') {
52
- const dbData = await this.repository.findOne({
53
- where: {
54
- id: dto.id
55
- }
56
- });
57
- if (!dbData) {
58
- throw new NotFoundException('No such entity data found for update! Please, Try Again.');
59
- }
60
- folder = dbData;
61
- }
62
- // Set company/branch IDs if company feature is enabled
63
- folder = {
64
- ...folder,
65
- ...dto
66
- };
67
- // Only set company fields if they exist on the entity (when company feature is enabled)
68
- if ('companyId' in folder) {
69
- folder.companyId = user?.companyId ?? null;
43
+ const entity = await super.convertSingleDtoToEntity(dto, user);
44
+ // Set companyId from user context if company feature enabled
45
+ if (this.storageConfig.isCompanyFeatureEnabled()) {
46
+ entity.companyId = user?.companyId ?? null;
70
47
  }
71
- return folder;
48
+ return entity;
72
49
  }
50
+ // Query Customization
73
51
  async getSelectQuery(query, _user, select) {
74
- if (!select || !select.length) {
52
+ if (!select?.length) {
75
53
  select = [
76
54
  'id',
77
55
  'name',
@@ -79,35 +57,26 @@ export class FolderService extends RequestScopedApiService {
79
57
  'createdAt',
80
58
  'deletedAt'
81
59
  ];
60
+ if (this.storageConfig.isCompanyFeatureEnabled()) {
61
+ select.push('companyId');
62
+ }
82
63
  }
83
- const selectFields = select.map((field)=>`${this.entityName}.${field}`);
84
- // Add company context fields if company feature is enabled
85
- // The entity will have these fields only if company feature is enabled
86
- if (this.storageConfig.isCompanyFeatureEnabled()) {
87
- selectFields.push('folder.companyId');
88
- }
89
- query.select(selectFields);
64
+ query.select(select.map((f)=>`${this.entityName}.${f}`));
90
65
  return {
91
66
  query,
92
67
  isRaw: false
93
68
  };
94
69
  }
95
- /**
96
- * Override: Extra query manipulation - Auto-filter by user's company
97
- */ async getExtraManipulateQuery(query, filterDto, user) {
70
+ async getExtraManipulateQuery(query, filterDto, user) {
98
71
  const result = await super.getExtraManipulateQuery(query, filterDto, user);
99
- // If company feature enabled and user has companyId, filter by user's company
100
- const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
101
- if (enableCompanyFeature && user?.companyId) {
72
+ if (this.storageConfig.isCompanyFeatureEnabled() && user?.companyId) {
102
73
  query.andWhere('folder.companyId = :companyId', {
103
74
  companyId: user.companyId
104
75
  });
105
76
  }
106
77
  return result;
107
78
  }
108
- // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
109
79
  constructor(cacheManager, utilsService, storageConfig, dataSourceProvider){
110
- // Repository will be set asynchronously by RequestScopedApiService
111
80
  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;
112
81
  }
113
82
  }
@@ -32,10 +32,8 @@ import { Request } from 'express';
32
32
  import { StorageModuleOptions } from '../interfaces';
33
33
  import { STORAGE_MODULE_OPTIONS } from '../config/storage.constants';
34
34
  export class StorageDataSourceProvider extends MultiTenantDataSourceService {
35
- // ==================== Factory Methods ====================
36
- /**
37
- * Build parent options from StorageModuleOptions
38
- */ static buildParentOptions(options) {
35
+ // Factory Methods
36
+ /** Build parent options from StorageModuleOptions */ static buildParentOptions(options) {
39
37
  return {
40
38
  bootstrapAppConfig: options.bootstrapAppConfig,
41
39
  defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
@@ -43,10 +41,8 @@ export class StorageDataSourceProvider extends MultiTenantDataSourceService {
43
41
  tenants: options.config?.tenants
44
42
  };
45
43
  }
46
- // ==================== Feature Flags ====================
47
- /**
48
- * Get global enable company feature flag
49
- */ getEnableCompanyFeature() {
44
+ // Feature Flags
45
+ /** Get global enable company feature flag */ getEnableCompanyFeature() {
50
46
  return this.storageOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
51
47
  }
52
48
  /**
@@ -60,11 +56,11 @@ export class StorageDataSourceProvider extends MultiTenantDataSourceService {
60
56
  */ getEnableCompanyFeatureForCurrentTenant() {
61
57
  return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
62
58
  }
63
- // ==================== Entity Management ====================
59
+ // Entity Management
64
60
  /**
65
- * Get storage entities for migrations based on company feature flag
66
- * Note: For TypeORM repositories, we always use the base entities (FileManager, etc.)
67
- * But for migrations, we need the correct entity based on the feature flag
61
+ * Get storage entities for migrations based on company feature flag.
62
+ * For TypeORM repositories, we always use the base entities (FileManager, etc.)
63
+ * but for migrations, we need the correct entity based on the feature flag.
68
64
  */ async getStorageEntities(enableCompanyFeature) {
69
65
  const enable = enableCompanyFeature ?? this.getEnableCompanyFeature();
70
66
  const { FileManager, Folder, StorageConfig } = await import('../entities');
@@ -84,10 +80,8 @@ export class StorageDataSourceProvider extends MultiTenantDataSourceService {
84
80
  StorageConfig
85
81
  ];
86
82
  }
87
- // ==================== Overrides ====================
88
- /**
89
- * Override to dynamically set entities based on tenant config
90
- */ async createDataSourceFromConfig(config) {
83
+ // Overrides
84
+ /** Override to dynamically set entities based on tenant config */ async createDataSourceFromConfig(config) {
91
85
  const currentTenant = this.getCurrentTenant();
92
86
  const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
93
87
  const entities = await this.getStorageEntities(enableCompanyFeature);