@flusys/nestjs-storage 1.0.0-rc → 1.0.1

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 (103) hide show
  1. package/README.md +44 -1
  2. package/cjs/config/index.js +0 -1
  3. package/cjs/config/storage.constants.js +0 -9
  4. package/cjs/controllers/upload.controller.js +12 -17
  5. package/cjs/docs/storage-swagger.config.js +24 -136
  6. package/cjs/dtos/file-manager.dto.js +65 -32
  7. package/cjs/dtos/folder.dto.js +15 -9
  8. package/cjs/dtos/storage-config.dto.js +5 -86
  9. package/cjs/dtos/upload.dto.js +17 -17
  10. package/cjs/entities/file-manager-with-company.entity.js +3 -4
  11. package/cjs/entities/file-manager.entity.js +71 -3
  12. package/cjs/entities/folder-with-company.entity.js +3 -4
  13. package/cjs/entities/folder.entity.js +19 -3
  14. package/cjs/entities/index.js +9 -10
  15. package/cjs/entities/storage-config-with-company.entity.js +3 -4
  16. package/cjs/entities/storage-config.entity.js +73 -3
  17. package/cjs/middlewares/file-serve.middleware.js +107 -100
  18. package/cjs/modules/storage.module.js +82 -136
  19. package/cjs/providers/azure-provider.optional.js +10 -38
  20. package/cjs/providers/local-provider.js +0 -43
  21. package/cjs/providers/s3-provider.optional.js +19 -40
  22. package/cjs/providers/storage-factory.service.js +54 -99
  23. package/cjs/providers/storage-provider.registry.js +8 -18
  24. package/cjs/services/file-manager.service.js +239 -337
  25. package/cjs/services/folder.service.js +3 -3
  26. package/cjs/services/index.js +1 -0
  27. package/cjs/{config → services}/storage-config.service.js +30 -79
  28. package/cjs/services/storage-datasource.provider.js +16 -26
  29. package/cjs/services/storage-provider-config.service.js +3 -3
  30. package/cjs/services/upload.service.js +33 -61
  31. package/cjs/utils/file-validator.util.js +54 -66
  32. package/cjs/utils/image-compressor.util.js +2 -5
  33. package/config/index.d.ts +0 -1
  34. package/config/storage.constants.d.ts +0 -6
  35. package/controllers/upload.controller.d.ts +1 -0
  36. package/dtos/file-manager.dto.d.ts +11 -3
  37. package/dtos/folder.dto.d.ts +3 -1
  38. package/dtos/storage-config.dto.d.ts +7 -11
  39. package/entities/file-manager-with-company.entity.d.ts +2 -2
  40. package/entities/file-manager.entity.d.ts +11 -2
  41. package/entities/folder-with-company.entity.d.ts +2 -2
  42. package/entities/folder.entity.d.ts +4 -2
  43. package/entities/index.d.ts +3 -4
  44. package/entities/storage-config-with-company.entity.d.ts +2 -2
  45. package/entities/storage-config.entity.d.ts +7 -2
  46. package/fesm/config/index.js +0 -1
  47. package/fesm/config/storage.constants.js +0 -6
  48. package/fesm/controllers/upload.controller.js +12 -17
  49. package/fesm/docs/storage-swagger.config.js +27 -142
  50. package/fesm/dtos/file-manager.dto.js +66 -33
  51. package/fesm/dtos/folder.dto.js +16 -10
  52. package/fesm/dtos/storage-config.dto.js +7 -88
  53. package/fesm/dtos/upload.dto.js +17 -18
  54. package/fesm/entities/file-manager-with-company.entity.js +3 -4
  55. package/fesm/entities/file-manager.entity.js +72 -4
  56. package/fesm/entities/folder-with-company.entity.js +3 -4
  57. package/fesm/entities/folder.entity.js +20 -4
  58. package/fesm/entities/index.js +4 -8
  59. package/fesm/entities/storage-config-with-company.entity.js +3 -4
  60. package/fesm/entities/storage-config.entity.js +74 -4
  61. package/fesm/middlewares/file-serve.middleware.js +107 -100
  62. package/fesm/modules/storage.module.js +83 -136
  63. package/fesm/providers/azure-provider.optional.js +14 -45
  64. package/fesm/providers/local-provider.js +0 -43
  65. package/fesm/providers/s3-provider.optional.js +23 -47
  66. package/fesm/providers/storage-factory.service.js +52 -97
  67. package/fesm/providers/storage-provider.registry.js +10 -20
  68. package/fesm/services/file-manager.service.js +237 -335
  69. package/fesm/services/folder.service.js +1 -1
  70. package/fesm/services/index.js +1 -0
  71. package/fesm/{config → services}/storage-config.service.js +30 -79
  72. package/fesm/services/storage-datasource.provider.js +16 -26
  73. package/fesm/services/storage-provider-config.service.js +1 -1
  74. package/fesm/services/upload.service.js +31 -59
  75. package/fesm/utils/file-validator.util.js +54 -66
  76. package/fesm/utils/image-compressor.util.js +2 -5
  77. package/interfaces/storage-config.interface.d.ts +1 -2
  78. package/interfaces/storage-module-options.interface.d.ts +0 -5
  79. package/middlewares/file-serve.middleware.d.ts +9 -1
  80. package/modules/storage.module.d.ts +1 -2
  81. package/package.json +3 -3
  82. package/providers/azure-provider.optional.d.ts +8 -6
  83. package/providers/local-provider.d.ts +0 -7
  84. package/providers/s3-provider.optional.d.ts +9 -7
  85. package/providers/storage-factory.service.d.ts +8 -9
  86. package/providers/storage-provider.registry.d.ts +4 -4
  87. package/services/file-manager.service.d.ts +21 -14
  88. package/services/folder.service.d.ts +4 -4
  89. package/services/index.d.ts +1 -0
  90. package/{config → services}/storage-config.service.d.ts +9 -10
  91. package/services/storage-datasource.provider.d.ts +3 -4
  92. package/services/storage-provider-config.service.d.ts +5 -6
  93. package/services/upload.service.d.ts +5 -5
  94. package/utils/file-validator.util.d.ts +3 -0
  95. package/cjs/entities/file-manager-base.entity.js +0 -115
  96. package/cjs/entities/folder-base.entity.js +0 -55
  97. package/cjs/entities/storage-config-base.entity.js +0 -93
  98. package/entities/file-manager-base.entity.d.ts +0 -13
  99. package/entities/folder-base.entity.d.ts +0 -5
  100. package/entities/storage-config-base.entity.d.ts +0 -9
  101. package/fesm/entities/file-manager-base.entity.js +0 -108
  102. package/fesm/entities/folder-base.entity.js +0 -48
  103. package/fesm/entities/storage-config-base.entity.js +0 -83
@@ -1,18 +1,4 @@
1
- /**
2
- * OPTIONAL S3 Provider
3
- *
4
- * This provider requires @aws-sdk packages to be installed.
5
- * Only import this if you need AWS S3 storage.
6
- *
7
- * Installation:
8
- * npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
9
- *
10
- * Usage:
11
- * import { S3Provider } from '@flusys/nestjs-storage/providers/s3-provider.optional';
12
- * import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
13
- *
14
- * StorageProviderRegistry.register(FileLocationEnum.AWS, S3Provider);
15
- */ function _define_property(obj, key, value) {
1
+ function _define_property(obj, key, value) {
16
2
  if (key in obj) {
17
3
  Object.defineProperty(obj, key, {
18
4
  value: value,
@@ -25,33 +11,29 @@
25
11
  }
26
12
  return obj;
27
13
  }
28
- import { ImageCompressor } from '../utils/image-compressor.util';
29
- import { Logger } from '@nestjs/common';
30
- import { v4 as uuidv4 } from 'uuid';
31
14
  /**
32
- * AWS S3 Storage Provider
33
- * Requires @aws-sdk packages to be installed
34
- */ export class S3Provider {
35
- /**
36
- * Initialize S3 provider with configuration
37
- */ async initialize(config) {
15
+ * Optional AWS S3 Storage Provider
16
+ * Requires: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
17
+ */ import { Logger } from '@nestjs/common';
18
+ import { v4 as uuidv4 } from 'uuid';
19
+ import { ImageCompressor } from '../utils/image-compressor.util';
20
+ export class S3Provider {
21
+ async initialize(config) {
38
22
  try {
39
- // Dynamic import - only loads AWS SDK if this provider is used
40
23
  const { S3Client } = await import('@aws-sdk/client-s3');
41
24
  this.region = config.region;
42
25
  this.bucketName = config.bucket;
43
26
  this.s3 = new S3Client({
44
27
  region: config.region,
28
+ endpoint: config.endpoint,
45
29
  credentials: config.accessKeyId && config.secretAccessKey ? {
46
30
  accessKeyId: config.accessKeyId,
47
31
  secretAccessKey: config.secretAccessKey
48
- } : undefined,
49
- endpoint: config.endpoint
32
+ } : undefined
50
33
  });
51
34
  this.logger.log(`S3 Provider initialized: region=${config.region}, bucket=${config.bucket}`);
52
- } catch (_error) {
53
- this.logger.error('Failed to initialize S3Provider. Make sure @aws-sdk packages are installed.');
54
- throw new Error('AWS SDK not found. Install it with: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner');
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');
55
37
  }
56
38
  }
57
39
  async uploadFile(file, options) {
@@ -59,7 +41,6 @@ import { v4 as uuidv4 } from 'uuid';
59
41
  let processedBuffer = file.buffer;
60
42
  let contentType = file.mimetype;
61
43
  const fileName = `${uuidv4()}-${file.originalname}`;
62
- // Compress image if needed
63
44
  if (options.compress && file.mimetype.startsWith('image/')) {
64
45
  const result = await ImageCompressor.compress(file.buffer, file.mimetype, {
65
46
  maxWidth: options.maxWidth,
@@ -71,7 +52,7 @@ import { v4 as uuidv4 } from 'uuid';
71
52
  contentType = result.format;
72
53
  }
73
54
  const key = options.folderPath ? `${options.folderPath}/${fileName}` : fileName;
74
- const upload = new Upload({
55
+ await new Upload({
75
56
  client: this.s3,
76
57
  params: {
77
58
  Bucket: this.bucketName,
@@ -79,8 +60,7 @@ import { v4 as uuidv4 } from 'uuid';
79
60
  Body: processedBuffer,
80
61
  ContentType: contentType
81
62
  }
82
- });
83
- await upload.done();
63
+ }).done();
84
64
  return {
85
65
  name: fileName,
86
66
  key,
@@ -93,44 +73,40 @@ import { v4 as uuidv4 } from 'uuid';
93
73
  }
94
74
  async deleteFile(key) {
95
75
  const { DeleteObjectCommand } = await import('@aws-sdk/client-s3');
96
- const command = new DeleteObjectCommand({
76
+ await this.s3.send(new DeleteObjectCommand({
97
77
  Bucket: this.bucketName,
98
78
  Key: key
99
- });
100
- await this.s3.send(command);
79
+ }));
101
80
  this.logger.log(`Deleted file from S3: ${key}`);
102
81
  }
103
82
  async deleteMultipleFiles(keys) {
104
83
  const { DeleteObjectsCommand } = await import('@aws-sdk/client-s3');
105
- const command = new DeleteObjectsCommand({
84
+ await this.s3.send(new DeleteObjectsCommand({
106
85
  Bucket: this.bucketName,
107
86
  Delete: {
108
87
  Objects: keys.map((key)=>({
109
88
  Key: key
110
89
  }))
111
90
  }
112
- });
113
- await this.s3.send(command);
91
+ }));
114
92
  this.logger.log(`Deleted ${keys.length} files from S3`);
115
93
  }
116
94
  async generatePresignedUrl(key, expiresInSeconds = 3600) {
117
95
  const { GetObjectCommand } = await import('@aws-sdk/client-s3');
118
96
  const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
119
- const command = new GetObjectCommand({
97
+ return getSignedUrl(this.s3, new GetObjectCommand({
120
98
  Bucket: this.bucketName,
121
99
  Key: key
122
- });
123
- return getSignedUrl(this.s3, command, {
100
+ }), {
124
101
  expiresIn: expiresInSeconds
125
102
  });
126
103
  }
127
104
  async healthCheck() {
128
105
  try {
129
106
  const { HeadBucketCommand } = await import('@aws-sdk/client-s3');
130
- const command = new HeadBucketCommand({
107
+ await this.s3.send(new HeadBucketCommand({
131
108
  Bucket: this.bucketName
132
- });
133
- await this.s3.send(command);
109
+ }));
134
110
  return true;
135
111
  } catch {
136
112
  return false;
@@ -138,7 +114,7 @@ import { v4 as uuidv4 } from 'uuid';
138
114
  }
139
115
  constructor(){
140
116
  _define_property(this, "logger", new Logger(S3Provider.name));
141
- _define_property(this, "s3", void 0); // S3Client - typed as any to avoid import
117
+ _define_property(this, "s3", void 0);
142
118
  _define_property(this, "bucketName", '');
143
119
  _define_property(this, "region", '');
144
120
  }
@@ -25,129 +25,84 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { StorageProviderRegistry } from './storage-provider.registry';
29
28
  import { Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
30
29
  import * as crypto from 'crypto';
31
- import { StorageConfigService } from '../config';
30
+ import { StorageConfigService } from '../services';
31
+ import { StorageProviderRegistry } from './storage-provider.registry';
32
32
  export class StorageFactoryService {
33
- /**
34
- * Generate a stable cache key for provider configuration
35
- * Uses SHA256 hash to handle complex config objects consistently
36
- */ generateCacheKey(config) {
37
- // Sort keys for consistent ordering, then hash
38
- const configString = JSON.stringify(config.config, Object.keys(config.config || {}).sort());
39
- const configHash = crypto.createHash('sha256').update(configString).digest('hex').substring(0, 16);
40
- return `${config.provider}-${configHash}`;
41
- }
42
- /**
43
- * Create a storage provider instance based on configuration
44
- * Uses lazy loading - provider is only instantiated when requested
45
- */ async createProvider(config) {
46
- const providerKey = this.generateCacheKey(config);
47
- // Check cache first
48
- if (this.providerCache.has(providerKey)) {
49
- return this.providerCache.get(providerKey);
50
- }
51
- // Get provider class from registry
33
+ async createProvider(config) {
34
+ const cacheKey = this.generateCacheKey(config);
35
+ const cached = this.cache.get(cacheKey);
36
+ if (cached) return cached;
52
37
  const ProviderClass = StorageProviderRegistry.get(config.provider);
53
38
  if (!ProviderClass) {
54
- throw new NotFoundException(`Storage provider '${config.provider}' is not registered. ` + `Available providers: ${StorageProviderRegistry.getAll().join(', ')}`);
39
+ throw new NotFoundException(`Storage provider '${config.provider}' not registered. Available: ${StorageProviderRegistry.getAll().join(', ')}`);
55
40
  }
56
- // Instantiate provider
57
41
  try {
58
42
  const instance = new ProviderClass();
59
- // Initialize if method exists
60
- if ('initialize' in instance && typeof instance.initialize === 'function') {
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);
74
- }
75
- // Cache the instance
76
- this.providerCache.set(providerKey, instance);
77
- this.logger.log(`Created storage provider: ${config.provider} (key: ${providerKey})`);
43
+ await this.initializeProvider(instance, config);
44
+ this.cache.set(cacheKey, instance);
45
+ this.logger.log(`Created provider: ${config.provider} (${cacheKey})`);
78
46
  return instance;
79
47
  } catch (error) {
80
- this.logger.error(`Failed to create provider ${config.provider}:`, error);
81
- // Preserve original error message for better debugging
82
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
83
- throw new Error(`Failed to initialize storage provider '${config.provider}': ${errorMessage}`);
48
+ const message = error instanceof Error ? error.message : 'Unknown error';
49
+ throw new Error(`Failed to initialize '${config.provider}': ${message}`);
84
50
  }
85
51
  }
86
- /**
87
- * Get provider by name (creates with default config if not cached)
88
- */ async getProvider(providerName) {
89
- const cachedKey = Array.from(this.providerCache.keys()).find((key)=>key.startsWith(providerName));
90
- if (cachedKey) {
91
- return this.providerCache.get(cachedKey);
92
- }
93
- // Create with empty config
52
+ async getProvider(providerName) {
53
+ const cachedKey = Array.from(this.cache.keys()).find((k)=>k.startsWith(providerName));
54
+ if (cachedKey) return this.cache.get(cachedKey);
94
55
  return this.createProvider({
95
56
  provider: providerName,
96
57
  config: {}
97
58
  });
98
59
  }
99
- /**
100
- * Clear cached providers
101
- */ clearCache() {
102
- this.providerCache.clear();
60
+ getLocalProviderBasePath() {
61
+ const localKey = Array.from(this.cache.keys()).find((k)=>k.startsWith('local'));
62
+ const provider = localKey ? this.cache.get(localKey) : null;
63
+ return provider && 'basePath' in provider ? provider.basePath : null;
103
64
  }
104
- /**
105
- * Check if a provider is available
106
- */ isProviderAvailable(providerName) {
107
- return StorageProviderRegistry.has(providerName);
65
+ clearCache() {
66
+ this.cache.clear();
108
67
  }
109
- /**
110
- * Get list of available providers
111
- */ getAvailableProviders() {
112
- return StorageProviderRegistry.getAll();
68
+ 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}`)));
71
+ await Promise.allSettled(closePromises);
72
+ this.cache.clear();
73
+ this.logger.log('Storage provider cleanup complete');
113
74
  }
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;
75
+ // ─── Private Helpers ──────────────────────────────────────────────────────────
76
+ generateCacheKey(config) {
77
+ const sortedKeys = Object.keys(config.config || {}).sort();
78
+ const hash = crypto.createHash('sha256').update(JSON.stringify(config.config, sortedKeys)).digest('hex').substring(0, 16);
79
+ return `${config.provider}-${hash}`;
127
80
  }
128
- /**
129
- * Cleanup all cached provider connections on module destroy
130
- * Ensures SFTP connections and other resources are properly released
131
- */ async onModuleDestroy() {
132
- this.logger.log('Cleaning up storage provider connections...');
133
- const closePromises = [];
134
- for (const [key, provider] of this.providerCache.entries()){
135
- // Check if provider has a close method (e.g., SFTP)
136
- if ('close' in provider && typeof provider.close === 'function') {
137
- closePromises.push(provider.close().then(()=>this.logger.debug(`Closed provider: ${key}`)).catch((err)=>this.logger.warn(`Failed to close provider ${key}: ${err.message}`)));
81
+ async initializeProvider(instance, config) {
82
+ if (!('initialize' in instance) || typeof instance.initialize !== 'function') {
83
+ return;
84
+ }
85
+ let initConfig = config.config;
86
+ // Inject appUrl as baseUrl fallback for local provider
87
+ if (config.provider === 'local' && !config.config?.baseUrl) {
88
+ const appUrl = this.configService.getAppUrl();
89
+ if (appUrl) {
90
+ initConfig = {
91
+ ...config.config,
92
+ baseUrl: appUrl
93
+ };
94
+ this.logger.debug(`Using appUrl as baseUrl: ${appUrl}`);
138
95
  }
139
96
  }
140
- await Promise.allSettled(closePromises);
141
- this.providerCache.clear();
142
- this.logger.log('Storage provider cleanup complete');
97
+ await instance.initialize(initConfig);
143
98
  }
144
- constructor(storageConfigService){
145
- _define_property(this, "storageConfigService", void 0);
99
+ constructor(configService){
100
+ _define_property(this, "configService", void 0);
146
101
  _define_property(this, "logger", void 0);
147
- _define_property(this, "providerCache", void 0);
148
- this.storageConfigService = storageConfigService;
102
+ _define_property(this, "cache", void 0);
103
+ this.configService = configService;
149
104
  this.logger = new Logger(StorageFactoryService.name);
150
- this.providerCache = new Map();
105
+ this.cache = new Map();
151
106
  }
152
107
  }
153
108
  StorageFactoryService = _ts_decorate([
@@ -12,32 +12,22 @@ function _define_property(obj, key, value) {
12
12
  return obj;
13
13
  }
14
14
  /**
15
- * Registry for storage providers
16
- * Allows dynamic registration of providers at runtime
15
+ * Static registry for storage provider classes
16
+ * Allows dynamic registration at runtime
17
17
  */ export class StorageProviderRegistry {
18
- /**
19
- * Register a storage provider
20
- */ static register(providerName, providerClass) {
21
- this.providers.set(providerName.toLowerCase(), providerClass);
18
+ static register(name, providerClass) {
19
+ this.providers.set(name.toLowerCase(), providerClass);
22
20
  }
23
- /**
24
- * Get a registered provider class
25
- */ static get(providerName) {
26
- return this.providers.get(providerName.toLowerCase());
21
+ static get(name) {
22
+ return this.providers.get(name.toLowerCase());
27
23
  }
28
- /**
29
- * Check if a provider is registered
30
- */ static has(providerName) {
31
- return this.providers.has(providerName.toLowerCase());
24
+ static has(name) {
25
+ return this.providers.has(name.toLowerCase());
32
26
  }
33
- /**
34
- * Get all registered provider names
35
- */ static getAll() {
27
+ static getAll() {
36
28
  return Array.from(this.providers.keys());
37
29
  }
38
- /**
39
- * Clear all providers (useful for testing)
40
- */ static clear() {
30
+ static clear() {
41
31
  this.providers.clear();
42
32
  }
43
33
  }