@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.
- package/README.md +3 -7
- package/cjs/config/storage-config.service.js +31 -0
- package/cjs/controllers/file-manager.controller.js +8 -6
- package/cjs/controllers/folder.controller.js +3 -4
- package/cjs/controllers/storage-config.controller.js +3 -4
- package/cjs/controllers/upload.controller.js +22 -169
- package/cjs/dtos/file-manager.dto.js +36 -2
- package/cjs/dtos/upload.dto.js +16 -0
- package/cjs/middlewares/file-serve.middleware.js +204 -0
- package/cjs/middlewares/index.js +18 -0
- package/cjs/modules/storage.module.js +58 -14
- package/cjs/providers/azure-provider.optional.js +1 -2
- package/cjs/providers/local-provider.js +43 -11
- package/cjs/providers/storage-factory.service.js +49 -5
- package/cjs/services/file-manager.service.js +134 -9
- package/cjs/services/folder.service.js +17 -48
- package/cjs/services/storage-datasource.provider.js +10 -16
- package/cjs/services/storage-provider-config.service.js +26 -32
- package/cjs/services/upload.service.js +135 -24
- package/cjs/utils/image-compressor.util.js +43 -5
- package/config/storage-config.service.d.ts +2 -0
- package/controllers/file-manager.controller.d.ts +1 -1
- package/controllers/upload.controller.d.ts +5 -4
- package/dtos/file-manager.dto.d.ts +4 -0
- package/dtos/upload.dto.d.ts +2 -0
- package/fesm/config/storage-config.service.js +31 -0
- package/fesm/controllers/file-manager.controller.js +8 -6
- package/fesm/controllers/folder.controller.js +5 -6
- package/fesm/controllers/storage-config.controller.js +5 -6
- package/fesm/controllers/upload.controller.js +25 -131
- package/fesm/dtos/file-manager.dto.js +36 -2
- package/fesm/dtos/upload.dto.js +16 -0
- package/fesm/middlewares/file-serve.middleware.js +153 -0
- package/fesm/middlewares/index.js +1 -0
- package/fesm/modules/storage.module.js +60 -16
- package/fesm/providers/azure-provider.optional.js +1 -2
- package/fesm/providers/local-provider.js +43 -11
- package/fesm/providers/storage-factory.service.js +50 -6
- package/fesm/services/file-manager.service.js +134 -9
- package/fesm/services/folder.service.js +18 -49
- package/fesm/services/storage-datasource.provider.js +10 -16
- package/fesm/services/storage-provider-config.service.js +26 -32
- package/fesm/services/upload.service.js +135 -24
- package/fesm/utils/image-compressor.util.js +3 -1
- package/interfaces/file-manager.interface.d.ts +2 -0
- package/interfaces/storage-module-options.interface.d.ts +2 -0
- package/interfaces/storage-provider.interface.d.ts +2 -0
- package/middlewares/file-serve.middleware.d.ts +9 -0
- package/middlewares/index.d.ts +1 -0
- package/modules/storage.module.d.ts +3 -2
- package/package.json +26 -11
- package/providers/local-provider.d.ts +2 -1
- package/providers/storage-factory.service.d.ts +4 -0
- package/services/file-manager.service.d.ts +7 -1
- package/services/folder.service.d.ts +1 -2
- package/services/storage-provider-config.service.d.ts +2 -2
- package/services/upload.service.d.ts +6 -2
|
@@ -32,34 +32,24 @@ import { StorageConfigService } from '../config';
|
|
|
32
32
|
import { StorageConfig, StorageConfigWithCompany } from '../entities';
|
|
33
33
|
import { StorageDataSourceProvider } from './storage-datasource.provider';
|
|
34
34
|
export class StorageProviderConfigService extends RequestScopedApiService {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* @returns StorageConfig or StorageConfigWithCompany based on configuration
|
|
38
|
-
*/ resolveEntity() {
|
|
39
|
-
const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
|
|
40
|
-
return enableCompanyFeature ? StorageConfigWithCompany : StorageConfig;
|
|
35
|
+
resolveEntity() {
|
|
36
|
+
return this.storageConfig.isCompanyFeatureEnabled() ? StorageConfigWithCompany : StorageConfig;
|
|
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
|
-
|
|
50
|
-
// Set
|
|
51
|
-
storageConfig
|
|
52
|
-
|
|
53
|
-
...dto
|
|
54
|
-
};
|
|
55
|
-
// Only set company fields if they exist on the entity (when company feature is enabled)
|
|
56
|
-
if ('companyId' in storageConfig) {
|
|
57
|
-
storageConfig.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;
|
|
58
47
|
}
|
|
59
|
-
return
|
|
48
|
+
return entity;
|
|
60
49
|
}
|
|
50
|
+
// Query Customization
|
|
61
51
|
async getSelectQuery(query, _user, select) {
|
|
62
|
-
if (!select
|
|
52
|
+
if (!select?.length) {
|
|
63
53
|
select = [
|
|
64
54
|
'id',
|
|
65
55
|
'name',
|
|
@@ -68,25 +58,19 @@ export class StorageProviderConfigService extends RequestScopedApiService {
|
|
|
68
58
|
'createdAt',
|
|
69
59
|
'updatedAt'
|
|
70
60
|
];
|
|
71
|
-
// Add company fields if company feature is enabled
|
|
72
61
|
if (this.storageConfig.isCompanyFeatureEnabled()) {
|
|
73
62
|
select.push('companyId');
|
|
74
63
|
}
|
|
75
64
|
}
|
|
76
|
-
|
|
77
|
-
query.select(selectFields);
|
|
65
|
+
query.select(select.map((f)=>`${this.entityName}.${f}`));
|
|
78
66
|
return {
|
|
79
67
|
query,
|
|
80
68
|
isRaw: false
|
|
81
69
|
};
|
|
82
70
|
}
|
|
83
|
-
|
|
84
|
-
* Override: Extra query manipulation - Auto-filter by user's company
|
|
85
|
-
*/ async getExtraManipulateQuery(query, filterDto, user) {
|
|
71
|
+
async getExtraManipulateQuery(query, filterDto, user) {
|
|
86
72
|
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
87
|
-
|
|
88
|
-
const enableCompanyFeature = this.storageConfig.isCompanyFeatureEnabled();
|
|
89
|
-
if (enableCompanyFeature && user?.companyId) {
|
|
73
|
+
if (this.storageConfig.isCompanyFeatureEnabled() && user?.companyId) {
|
|
90
74
|
query.andWhere('storageConfig.companyId = :companyId', {
|
|
91
75
|
companyId: user.companyId
|
|
92
76
|
});
|
|
@@ -95,6 +79,18 @@ export class StorageProviderConfigService extends RequestScopedApiService {
|
|
|
95
79
|
return result;
|
|
96
80
|
}
|
|
97
81
|
/**
|
|
82
|
+
* Find storage config by ID without throwing (returns null if not found)
|
|
83
|
+
* Uses direct repository query - bypasses company filtering
|
|
84
|
+
* Use for internal operations like file deletion where config ID is already known
|
|
85
|
+
*/ async findByIdDirect(id) {
|
|
86
|
+
await this.ensureRepositoryInitialized();
|
|
87
|
+
return await this.repository.findOne({
|
|
88
|
+
where: {
|
|
89
|
+
id
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
98
94
|
* Get default storage configuration (scoped to user's company if enabled)
|
|
99
95
|
* Falls back to any available config if 'default' not found
|
|
100
96
|
*/ async getDefaultConfig(user) {
|
|
@@ -140,9 +136,7 @@ export class StorageProviderConfigService extends RequestScopedApiService {
|
|
|
140
136
|
where
|
|
141
137
|
});
|
|
142
138
|
}
|
|
143
|
-
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
144
139
|
constructor(cacheManager, utilsService, storageConfig, dataSourceProvider){
|
|
145
|
-
// Repository will be set asynchronously by RequestScopedApiService
|
|
146
140
|
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;
|
|
147
141
|
}
|
|
148
142
|
}
|
|
@@ -27,6 +27,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
27
27
|
}
|
|
28
28
|
import { BadRequestException, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException, Scope } from '@nestjs/common';
|
|
29
29
|
import { StorageConfigService } from '../config';
|
|
30
|
+
import { FileLocationEnum } from '../enums/file-location.enum';
|
|
30
31
|
import { StorageFactoryService } from '../providers/storage-factory.service';
|
|
31
32
|
import { StorageProviderConfigService } from './storage-provider-config.service';
|
|
32
33
|
export class UploadService {
|
|
@@ -45,19 +46,14 @@ export class UploadService {
|
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
|
-
* Get storage provider based on storage config ID
|
|
49
|
+
* Get storage provider and config info based on storage config ID
|
|
49
50
|
* Validates company ownership when company feature is enabled
|
|
50
|
-
*/ async
|
|
51
|
+
*/ async getStorageProviderWithConfig(storageConfigId, user) {
|
|
51
52
|
let storageConfig;
|
|
52
|
-
// Get storage config from database
|
|
53
|
+
// Get storage config from database
|
|
53
54
|
if (storageConfigId) {
|
|
54
|
-
//
|
|
55
|
-
const config = await this.storageProviderConfigService.
|
|
56
|
-
'name',
|
|
57
|
-
'storage',
|
|
58
|
-
'config',
|
|
59
|
-
'companyId'
|
|
60
|
-
]);
|
|
55
|
+
// Use direct lookup (bypasses company filtering, returns null instead of throwing)
|
|
56
|
+
const config = await this.storageProviderConfigService.findByIdDirect(storageConfigId);
|
|
61
57
|
if (!config) {
|
|
62
58
|
throw new NotFoundException('Storage configuration not found');
|
|
63
59
|
}
|
|
@@ -83,17 +79,98 @@ export class UploadService {
|
|
|
83
79
|
config: storageConfig.config
|
|
84
80
|
};
|
|
85
81
|
// Get or create provider instance
|
|
86
|
-
|
|
82
|
+
const provider = await this.storageFactory.createProvider(providerConfig);
|
|
83
|
+
return {
|
|
84
|
+
provider,
|
|
85
|
+
location: storageConfig.storage,
|
|
86
|
+
configId: storageConfig.id
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get storage provider based on storage config ID (convenience method)
|
|
91
|
+
*/ async getStorageProvider(storageConfigId, user) {
|
|
92
|
+
const { provider } = await this.getStorageProviderWithConfig(storageConfigId, user);
|
|
93
|
+
return provider;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get storage provider for delete operations with fallback
|
|
97
|
+
* If the original storage config doesn't exist, falls back based on locationHint
|
|
98
|
+
* This ensures files can still be deleted even if storage config was removed
|
|
99
|
+
* @param storageConfigId - The storage config ID to look up
|
|
100
|
+
* @param user - User context for company filtering
|
|
101
|
+
* @param locationHint - The file's original location type (used for fallback)
|
|
102
|
+
*/ async getStorageProviderForDelete(storageConfigId, user, locationHint) {
|
|
103
|
+
// If storageConfigId provided, try to find it
|
|
104
|
+
if (storageConfigId) {
|
|
105
|
+
const config = await this.storageProviderConfigService.findByIdDirect(storageConfigId);
|
|
106
|
+
if (config) {
|
|
107
|
+
const providerConfig = {
|
|
108
|
+
provider: config.storage,
|
|
109
|
+
config: config.config
|
|
110
|
+
};
|
|
111
|
+
return await this.storageFactory.createProvider(providerConfig);
|
|
112
|
+
}
|
|
113
|
+
// Config not found, log warning and try fallback
|
|
114
|
+
this.logger.warn(`Storage config ${storageConfigId} not found, trying fallback for delete`);
|
|
115
|
+
}
|
|
116
|
+
// Fallback: Use locationHint to find a matching config or create provider
|
|
117
|
+
if (locationHint) {
|
|
118
|
+
// Try to find a config matching the file's original location type
|
|
119
|
+
const matchingConfigs = await this.storageProviderConfigService.getConfigByType(locationHint, user);
|
|
120
|
+
if (matchingConfigs.length > 0) {
|
|
121
|
+
const providerConfig = {
|
|
122
|
+
provider: matchingConfigs[0].storage,
|
|
123
|
+
config: matchingConfigs[0].config
|
|
124
|
+
};
|
|
125
|
+
this.logger.debug(`Using matching ${locationHint} config for delete fallback`);
|
|
126
|
+
return await this.storageFactory.createProvider(providerConfig);
|
|
127
|
+
}
|
|
128
|
+
// No matching config found, create a basic provider based on locationHint
|
|
129
|
+
if (locationHint === FileLocationEnum.LOCAL) {
|
|
130
|
+
this.logger.warn('No local config found, using fallback local provider for delete');
|
|
131
|
+
const localProviderConfig = {
|
|
132
|
+
provider: FileLocationEnum.LOCAL,
|
|
133
|
+
config: {
|
|
134
|
+
basePath: this.storageConfigService.getDefaultLocalStoragePath()
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
return await this.storageFactory.createProvider(localProviderConfig);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Last resort: Try default config (might be different provider type)
|
|
141
|
+
const defaultConfig = await this.storageProviderConfigService.getDefaultConfig(user);
|
|
142
|
+
if (defaultConfig) {
|
|
143
|
+
const providerConfig = {
|
|
144
|
+
provider: defaultConfig.storage,
|
|
145
|
+
config: defaultConfig.config
|
|
146
|
+
};
|
|
147
|
+
return await this.storageFactory.createProvider(providerConfig);
|
|
148
|
+
}
|
|
149
|
+
// Final fallback: Create a basic local provider
|
|
150
|
+
this.logger.warn('No storage config found, using fallback local provider for delete');
|
|
151
|
+
const localProviderConfig = {
|
|
152
|
+
provider: FileLocationEnum.LOCAL,
|
|
153
|
+
config: {
|
|
154
|
+
basePath: this.storageConfigService.getDefaultLocalStoragePath()
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
return await this.storageFactory.createProvider(localProviderConfig);
|
|
87
158
|
}
|
|
88
159
|
async uploadSingleFile(file, options, user) {
|
|
89
160
|
try {
|
|
90
161
|
// Validate file before upload
|
|
91
162
|
this.validateFile(file);
|
|
92
|
-
const provider = await this.
|
|
93
|
-
|
|
163
|
+
const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
|
|
164
|
+
const result = await provider.uploadFile(file, options);
|
|
165
|
+
// Enrich result with storage info
|
|
166
|
+
return {
|
|
167
|
+
...result,
|
|
168
|
+
location,
|
|
169
|
+
storageConfigId: configId
|
|
170
|
+
};
|
|
94
171
|
} catch (err) {
|
|
95
172
|
this.logger.error('Single file upload failed', err);
|
|
96
|
-
throw new InternalServerErrorException(err
|
|
173
|
+
throw new InternalServerErrorException(err?.message || 'Single file upload failed');
|
|
97
174
|
}
|
|
98
175
|
}
|
|
99
176
|
async uploadMultipleFiles(files, options, user) {
|
|
@@ -102,39 +179,73 @@ export class UploadService {
|
|
|
102
179
|
for (const file of files){
|
|
103
180
|
this.validateFile(file);
|
|
104
181
|
}
|
|
105
|
-
const provider = await this.
|
|
106
|
-
|
|
182
|
+
const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
|
|
183
|
+
const results = await provider.uploadMultipleFiles(files, options);
|
|
184
|
+
// Enrich results with storage info
|
|
185
|
+
return results.map((result)=>({
|
|
186
|
+
...result,
|
|
187
|
+
location,
|
|
188
|
+
storageConfigId: configId
|
|
189
|
+
}));
|
|
107
190
|
} catch (err) {
|
|
108
191
|
this.logger.error('Multiple files upload failed', err);
|
|
109
|
-
throw new InternalServerErrorException(err
|
|
192
|
+
throw new InternalServerErrorException(err?.message || 'Multiple files upload failed');
|
|
110
193
|
}
|
|
111
194
|
}
|
|
112
|
-
async deleteSingleFile(key, storageConfigId, user) {
|
|
195
|
+
async deleteSingleFile(key, storageConfigId, user, locationHint) {
|
|
113
196
|
try {
|
|
114
197
|
if (!key) throw new Error('No file path provided');
|
|
115
|
-
const provider = await this.
|
|
198
|
+
const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
|
|
116
199
|
await provider.deleteFile(key);
|
|
117
200
|
return true;
|
|
118
201
|
} catch (err) {
|
|
119
202
|
this.logger.error('Delete single file failed', err);
|
|
120
|
-
throw new InternalServerErrorException(err
|
|
203
|
+
throw new InternalServerErrorException(err?.message || 'Delete single file failed');
|
|
121
204
|
}
|
|
122
205
|
}
|
|
123
|
-
async deleteMultipleFile(keys, storageConfigId, user) {
|
|
206
|
+
async deleteMultipleFile(keys, storageConfigId, user, locationHint) {
|
|
124
207
|
try {
|
|
125
208
|
if (!keys || !keys.length) throw new Error('No file paths provided');
|
|
126
|
-
const provider = await this.
|
|
209
|
+
const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
|
|
127
210
|
await provider.deleteMultipleFiles(keys);
|
|
128
211
|
return true;
|
|
129
212
|
} catch (err) {
|
|
130
213
|
this.logger.error('Delete multiple files failed', err);
|
|
131
|
-
throw new InternalServerErrorException(err
|
|
214
|
+
throw new InternalServerErrorException(err?.message || 'Delete multiple files failed');
|
|
132
215
|
}
|
|
133
216
|
}
|
|
134
217
|
bytesToKb(bytes) {
|
|
135
218
|
return Number((bytes * 0.001).toFixed(2));
|
|
136
219
|
}
|
|
137
220
|
/**
|
|
221
|
+
* Get local storage basePath from DB config
|
|
222
|
+
* Initializes local provider if not cached, ensuring basePath is from DB
|
|
223
|
+
*/ async getLocalStorageBasePath() {
|
|
224
|
+
try {
|
|
225
|
+
// First check if already cached
|
|
226
|
+
const cachedPath = this.storageFactory.getLocalProviderBasePath();
|
|
227
|
+
if (cachedPath) {
|
|
228
|
+
return cachedPath;
|
|
229
|
+
}
|
|
230
|
+
// Get local config from DB (without user context for public access)
|
|
231
|
+
const localConfigs = await this.storageProviderConfigService.getConfigByType(FileLocationEnum.LOCAL);
|
|
232
|
+
if (localConfigs.length > 0) {
|
|
233
|
+
const localConfig = localConfigs[0];
|
|
234
|
+
// Initialize provider to cache it
|
|
235
|
+
await this.storageFactory.createProvider({
|
|
236
|
+
provider: FileLocationEnum.LOCAL,
|
|
237
|
+
config: localConfig.config
|
|
238
|
+
});
|
|
239
|
+
// Now get the cached basePath
|
|
240
|
+
return this.storageFactory.getLocalProviderBasePath();
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.logger.warn(`Failed to get local storage basePath: ${error.message}`);
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
138
249
|
* Generate a URL for accessing a file
|
|
139
250
|
* For S3/Azure: generates presigned URL
|
|
140
251
|
* For Local/SFTP: returns direct path
|
|
@@ -149,7 +260,7 @@ export class UploadService {
|
|
|
149
260
|
return key;
|
|
150
261
|
} catch (err) {
|
|
151
262
|
this.logger.error('Generate file URL failed', err);
|
|
152
|
-
throw new InternalServerErrorException(err
|
|
263
|
+
throw new InternalServerErrorException(err?.message || 'Generate file URL failed');
|
|
153
264
|
}
|
|
154
265
|
}
|
|
155
266
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as sharpModule from 'sharp';
|
|
2
|
+
// Handle ESM/CJS interop - sharp exports differently in each module system
|
|
3
|
+
const sharp = sharpModule.default || sharpModule;
|
|
2
4
|
/**
|
|
3
5
|
* Image Compression Utility
|
|
4
6
|
* Handles image compression and format conversion using sharp
|
|
@@ -4,6 +4,8 @@ export interface IStorageModuleConfig extends IDataSourceServiceOptions {
|
|
|
4
4
|
maxFileSize?: number;
|
|
5
5
|
allowedFileTypes?: string[];
|
|
6
6
|
defaultStorageProvider?: string;
|
|
7
|
+
localStoragePath?: string;
|
|
8
|
+
appUrl?: string;
|
|
7
9
|
}
|
|
8
10
|
export interface IStorageModuleConfigFull {
|
|
9
11
|
bootstrapAppConfig?: IBootstrapAppConfig;
|
|
@@ -8,6 +8,8 @@ export interface IUploadedFileInfo {
|
|
|
8
8
|
contentType: string;
|
|
9
9
|
size: number;
|
|
10
10
|
name: string;
|
|
11
|
+
location?: string;
|
|
12
|
+
storageConfigId?: string;
|
|
11
13
|
}
|
|
12
14
|
export interface IStorageProvider {
|
|
13
15
|
uploadFile(file: Express.Multer.File, options: UploadOptionsDto): Promise<IUploadedFileInfo>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NestMiddleware } from '@nestjs/common';
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
import { UploadService } from '../services/upload.service';
|
|
4
|
+
export declare class FileServeMiddleware implements NestMiddleware {
|
|
5
|
+
private uploadService;
|
|
6
|
+
private logger;
|
|
7
|
+
constructor(uploadService: UploadService);
|
|
8
|
+
use(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './file-serve.middleware';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { DynamicModule } from '@nestjs/common';
|
|
1
|
+
import { DynamicModule, MiddlewareConsumer, NestModule } from '@nestjs/common';
|
|
2
2
|
import { StorageModuleAsyncOptions, StorageModuleOptions } from '../interfaces';
|
|
3
|
-
export declare class StorageModule {
|
|
3
|
+
export declare class StorageModule implements NestModule {
|
|
4
|
+
configure(consumer: MiddlewareConsumer): void;
|
|
4
5
|
static forRoot(options: StorageModuleOptions): DynamicModule;
|
|
5
6
|
static forRootAsync(options: StorageModuleAsyncOptions): DynamicModule;
|
|
6
7
|
private static getControllers;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-storage",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0-beta",
|
|
4
4
|
"description": "Modular storage package with optional AWS S3, Azure Blob, and SFTP providers",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -93,9 +93,16 @@
|
|
|
93
93
|
"@nestjs/typeorm": "^10.0.0 || ^11.0.0",
|
|
94
94
|
"class-transformer": "^0.5.0",
|
|
95
95
|
"class-validator": "^0.14.0",
|
|
96
|
+
"typeorm": "^0.3.0",
|
|
96
97
|
"multer": "^1.4.0",
|
|
97
98
|
"sharp": "^0.33.0",
|
|
98
|
-
"
|
|
99
|
+
"mime-types": "^2.1.0",
|
|
100
|
+
"uuid": "^9.0.0 || ^11.0.0",
|
|
101
|
+
"@aws-sdk/client-s3": "^3.400.0",
|
|
102
|
+
"@aws-sdk/lib-storage": "^3.400.0",
|
|
103
|
+
"@aws-sdk/s3-request-presigner": "^3.400.0",
|
|
104
|
+
"@azure/storage-blob": "^12.15.0",
|
|
105
|
+
"ssh2-sftp-client": "^10.0.0"
|
|
99
106
|
},
|
|
100
107
|
"peerDependenciesMeta": {
|
|
101
108
|
"sharp": {
|
|
@@ -103,17 +110,25 @@
|
|
|
103
110
|
},
|
|
104
111
|
"multer": {
|
|
105
112
|
"optional": true
|
|
113
|
+
},
|
|
114
|
+
"@aws-sdk/client-s3": {
|
|
115
|
+
"optional": true
|
|
116
|
+
},
|
|
117
|
+
"@aws-sdk/lib-storage": {
|
|
118
|
+
"optional": true
|
|
119
|
+
},
|
|
120
|
+
"@aws-sdk/s3-request-presigner": {
|
|
121
|
+
"optional": true
|
|
122
|
+
},
|
|
123
|
+
"@azure/storage-blob": {
|
|
124
|
+
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"ssh2-sftp-client": {
|
|
127
|
+
"optional": true
|
|
106
128
|
}
|
|
107
129
|
},
|
|
108
130
|
"dependencies": {
|
|
109
|
-
"@flusys/nestjs-core": "
|
|
110
|
-
"@flusys/nestjs-shared": "
|
|
111
|
-
},
|
|
112
|
-
"optionalDependencies": {
|
|
113
|
-
"@aws-sdk/client-s3": "^3.400.0",
|
|
114
|
-
"@aws-sdk/lib-storage": "^3.400.0",
|
|
115
|
-
"@aws-sdk/s3-request-presigner": "^3.400.0",
|
|
116
|
-
"@azure/storage-blob": "^12.15.0",
|
|
117
|
-
"ssh2-sftp-client": "^10.0.0"
|
|
131
|
+
"@flusys/nestjs-core": "1.0.0-beta",
|
|
132
|
+
"@flusys/nestjs-shared": "1.0.0-beta"
|
|
118
133
|
}
|
|
119
134
|
}
|
|
@@ -4,8 +4,9 @@ export declare class LocalProvider implements IStorageProvider {
|
|
|
4
4
|
private logger;
|
|
5
5
|
private basePath;
|
|
6
6
|
private baseUrl;
|
|
7
|
+
private relativeBasePath;
|
|
7
8
|
initialize(config: {
|
|
8
|
-
basePath
|
|
9
|
+
basePath?: string;
|
|
9
10
|
baseUrl?: string;
|
|
10
11
|
}): Promise<void>;
|
|
11
12
|
uploadFile(file: Express.Multer.File, options: UploadOptionsDto): Promise<IUploadedFileInfo>;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { IStorageProvider, IStorageProviderConfig } from '../interfaces/storage-provider.interface';
|
|
2
2
|
import { OnModuleDestroy } from '@nestjs/common';
|
|
3
|
+
import { StorageConfigService } from '../config';
|
|
3
4
|
export declare class StorageFactoryService implements OnModuleDestroy {
|
|
5
|
+
private readonly storageConfigService;
|
|
4
6
|
private readonly logger;
|
|
5
7
|
private providerCache;
|
|
8
|
+
constructor(storageConfigService: StorageConfigService);
|
|
6
9
|
private generateCacheKey;
|
|
7
10
|
createProvider(config: IStorageProviderConfig): Promise<IStorageProvider>;
|
|
8
11
|
getProvider(providerName: string): Promise<IStorageProvider>;
|
|
9
12
|
clearCache(): void;
|
|
10
13
|
isProviderAvailable(providerName: string): boolean;
|
|
11
14
|
getAvailableProviders(): string[];
|
|
15
|
+
getLocalProviderBasePath(): string | null;
|
|
12
16
|
onModuleDestroy(): Promise<void>;
|
|
13
17
|
}
|
|
@@ -16,6 +16,7 @@ export declare class FileManagerService extends RequestScopedApiService<CreateFi
|
|
|
16
16
|
private readonly storageConfig;
|
|
17
17
|
private readonly dataSourceProvider;
|
|
18
18
|
private readonly FILE_URL_EXPIRY_SECONDS;
|
|
19
|
+
private getFileBaseUrl;
|
|
19
20
|
constructor(cacheManager: HybridCache, utilsService: UtilsService, uploadService: UploadService, storageConfig: StorageConfigService, dataSourceProvider: StorageDataSourceProvider);
|
|
20
21
|
protected resolveEntity(): EntityTarget<FileManagerBase>;
|
|
21
22
|
protected getDataSourceProvider(): StorageDataSourceProvider;
|
|
@@ -24,6 +25,11 @@ export declare class FileManagerService extends RequestScopedApiService<CreateFi
|
|
|
24
25
|
query: SelectQueryBuilder<FileManagerBase>;
|
|
25
26
|
isRaw: boolean;
|
|
26
27
|
}>;
|
|
28
|
+
enrichWithProviderNames<T extends {
|
|
29
|
+
storageConfigId?: string | null;
|
|
30
|
+
}>(items: T[]): Promise<(T & {
|
|
31
|
+
providerName?: string;
|
|
32
|
+
})[]>;
|
|
27
33
|
protected getFilterQuery(query: SelectQueryBuilder<FileManagerBase>, filter: {
|
|
28
34
|
[key: string]: any;
|
|
29
35
|
}, _user: ILoggedUserInfo | null): Promise<{
|
|
@@ -34,6 +40,6 @@ export declare class FileManagerService extends RequestScopedApiService<CreateFi
|
|
|
34
40
|
query: SelectQueryBuilder<FileManagerBase>;
|
|
35
41
|
isRaw: boolean;
|
|
36
42
|
}>;
|
|
37
|
-
beforeDeleteOperation(dto: DeleteDto,
|
|
43
|
+
beforeDeleteOperation(dto: DeleteDto, user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
|
|
38
44
|
getFiles(dtos: GetFilesRequestDto[], protocol: string, host: string, user?: ILoggedUserInfo): Promise<FilesResponseDto[]>;
|
|
39
45
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
2
|
-
import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
|
|
3
2
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
4
3
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
5
4
|
import { EntityTarget, Repository, SelectQueryBuilder } from 'typeorm';
|
|
@@ -21,7 +20,7 @@ export declare class FolderService extends RequestScopedApiService<CreateFolderD
|
|
|
21
20
|
query: SelectQueryBuilder<FolderBase>;
|
|
22
21
|
isRaw: boolean;
|
|
23
22
|
}>;
|
|
24
|
-
protected getExtraManipulateQuery(query: SelectQueryBuilder<FolderBase>, filterDto:
|
|
23
|
+
protected getExtraManipulateQuery(query: SelectQueryBuilder<FolderBase>, filterDto: any, user: ILoggedUserInfo | null): Promise<{
|
|
25
24
|
query: SelectQueryBuilder<FolderBase>;
|
|
26
25
|
isRaw: boolean;
|
|
27
26
|
}>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
2
|
-
import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
|
|
3
2
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
4
3
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
5
4
|
import { EntityTarget, Repository, SelectQueryBuilder } from 'typeorm';
|
|
@@ -22,10 +21,11 @@ export declare class StorageProviderConfigService extends RequestScopedApiServic
|
|
|
22
21
|
query: SelectQueryBuilder<StorageConfigBase>;
|
|
23
22
|
isRaw: boolean;
|
|
24
23
|
}>;
|
|
25
|
-
protected getExtraManipulateQuery(query: SelectQueryBuilder<StorageConfigBase>, filterDto:
|
|
24
|
+
protected getExtraManipulateQuery(query: SelectQueryBuilder<StorageConfigBase>, filterDto: any, user: ILoggedUserInfo | null): Promise<{
|
|
26
25
|
query: SelectQueryBuilder<StorageConfigBase>;
|
|
27
26
|
isRaw: boolean;
|
|
28
27
|
}>;
|
|
28
|
+
findByIdDirect(id: string): Promise<StorageConfigBase | null>;
|
|
29
29
|
getDefaultConfig(user?: ILoggedUserInfo): Promise<StorageConfigBase | null>;
|
|
30
30
|
getConfigByType(storage: FileLocationEnum, user?: ILoggedUserInfo): Promise<StorageConfigBase[]>;
|
|
31
31
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
2
2
|
import { StorageConfigService } from '../config';
|
|
3
3
|
import { UploadOptionsDto } from '../dtos/upload.dto';
|
|
4
|
+
import { FileLocationEnum } from '../enums/file-location.enum';
|
|
4
5
|
import { IUploadedFileInfo } from '../interfaces/storage-provider.interface';
|
|
5
6
|
import { StorageFactoryService } from '../providers/storage-factory.service';
|
|
6
7
|
import { StorageProviderConfigService } from './storage-provider-config.service';
|
|
@@ -11,11 +12,14 @@ export declare class UploadService {
|
|
|
11
12
|
private logger;
|
|
12
13
|
constructor(storageFactory: StorageFactoryService, storageConfigService: StorageConfigService, storageProviderConfigService: StorageProviderConfigService);
|
|
13
14
|
private validateFile;
|
|
15
|
+
private getStorageProviderWithConfig;
|
|
14
16
|
private getStorageProvider;
|
|
17
|
+
private getStorageProviderForDelete;
|
|
15
18
|
uploadSingleFile(file: Express.Multer.File, options: UploadOptionsDto, user?: ILoggedUserInfo): Promise<IUploadedFileInfo>;
|
|
16
19
|
uploadMultipleFiles(files: Express.Multer.File[], options: UploadOptionsDto, user?: ILoggedUserInfo): Promise<IUploadedFileInfo[]>;
|
|
17
|
-
deleteSingleFile(key: string, storageConfigId?: string, user?: ILoggedUserInfo): Promise<boolean>;
|
|
18
|
-
deleteMultipleFile(keys: string[], storageConfigId?: string, user?: ILoggedUserInfo): Promise<boolean>;
|
|
20
|
+
deleteSingleFile(key: string, storageConfigId?: string, user?: ILoggedUserInfo, locationHint?: FileLocationEnum): Promise<boolean>;
|
|
21
|
+
deleteMultipleFile(keys: string[], storageConfigId?: string, user?: ILoggedUserInfo, locationHint?: FileLocationEnum): Promise<boolean>;
|
|
19
22
|
bytesToKb(bytes: number): number;
|
|
23
|
+
getLocalStorageBasePath(): Promise<string | null>;
|
|
20
24
|
makeFileUrl(key: string, storageConfigId: string, expiresIn?: number, user?: ILoggedUserInfo): Promise<string>;
|
|
21
25
|
}
|