@flusys/nestjs-storage 7.1.0 → 8.0.0
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/config/index.d.ts +3 -3
- package/controllers/file-manager.controller.d.ts +4 -4
- package/controllers/folder.controller.d.ts +2 -2
- package/controllers/index.d.ts +4 -4
- package/controllers/storage-config.controller.d.ts +2 -2
- package/controllers/upload.controller.d.ts +3 -3
- package/docs/index.d.ts +1 -1
- package/docs/storage-swagger.config.d.ts +2 -2
- package/dtos/index.d.ts +4 -4
- package/entities/file-manager-with-company.entity.d.ts +2 -2
- package/entities/file-manager.entity.d.ts +1 -1
- package/entities/folder-with-company.entity.d.ts +1 -1
- package/entities/index.d.ts +15 -15
- package/entities/storage-config-with-company.entity.d.ts +1 -1
- package/enums/index.d.ts +1 -1
- package/fesm/493.js +2768 -0
- package/fesm/537.js +1157 -0
- package/fesm/562.js +786 -0
- package/fesm/{providers/azure-provider.optional.js → chunks/190.js} +33 -30
- package/fesm/chunks/252.js +12 -0
- package/fesm/chunks/327.js +12 -0
- package/fesm/chunks/385.js +12 -0
- package/fesm/{providers/s3-provider.optional.js → chunks/591.js} +37 -34
- package/fesm/chunks/746.js +12 -0
- package/fesm/chunks/798.js +12 -0
- package/fesm/{providers/sftp-provider.optional.js → chunks/918.js} +35 -31
- package/fesm/config/index.js +119 -3
- package/fesm/controllers/index.js +16 -4
- package/fesm/docs/index.js +206 -1
- package/fesm/dtos/index.js +879 -4
- package/fesm/entities/index.js +463 -18
- package/fesm/enums/index.js +36 -1
- package/fesm/index.js +398 -12
- package/fesm/interfaces/index.js +19 -5
- package/fesm/modules/index.js +16 -1
- package/fesm/providers/index.js +364 -3
- package/fesm/runtime.js +252 -0
- package/fesm/services/index.js +16 -6
- package/fesm/utils/index.js +149 -1
- package/index.d.ts +11 -11
- package/interfaces/file-manager.interface.d.ts +1 -1
- package/interfaces/folder.interface.d.ts +1 -1
- package/interfaces/index.d.ts +5 -5
- package/interfaces/storage-config.interface.d.ts +1 -1
- package/interfaces/storage-module-options.interface.d.ts +3 -3
- package/interfaces/storage-provider.interface.d.ts +1 -1
- package/middlewares/file-serve.middleware.d.ts +4 -3
- package/middlewares/index.d.ts +1 -1
- package/modules/index.d.ts +1 -1
- package/modules/storage.module.d.ts +2 -2
- package/package.json +30 -41
- package/providers/azure-provider.optional.d.ts +2 -2
- package/providers/index.d.ts +3 -3
- package/providers/local-provider.d.ts +2 -2
- package/providers/s3-provider.optional.d.ts +2 -2
- package/providers/sftp-provider.optional.d.ts +2 -2
- package/providers/storage-factory.service.d.ts +3 -3
- package/providers/storage-provider.registry.d.ts +2 -2
- package/services/file-manager.service.d.ts +9 -8
- package/services/folder.service.d.ts +6 -6
- package/services/index.d.ts +6 -6
- package/services/storage-config.service.d.ts +3 -3
- package/services/storage-datasource.provider.d.ts +3 -3
- package/services/storage-provider-config.service.d.ts +7 -7
- package/services/upload.service.d.ts +6 -6
- package/utils/image-compressor.util.d.ts +1 -1
- package/utils/index.d.ts +1 -1
- package/cjs/config/event-actions.js +0 -34
- package/cjs/config/index.js +0 -20
- package/cjs/config/message-keys.js +0 -46
- package/cjs/config/storage.constants.js +0 -28
- package/cjs/controllers/file-manager.controller.js +0 -152
- package/cjs/controllers/file-manager.controller.spec.js +0 -135
- package/cjs/controllers/folder.controller.js +0 -102
- package/cjs/controllers/folder.controller.spec.js +0 -86
- package/cjs/controllers/index.js +0 -21
- package/cjs/controllers/storage-config.controller.js +0 -102
- package/cjs/controllers/storage-config.controller.spec.js +0 -85
- package/cjs/controllers/upload.controller.js +0 -234
- package/cjs/controllers/upload.controller.spec.js +0 -132
- package/cjs/docs/index.js +0 -11
- package/cjs/docs/storage-swagger.config.js +0 -188
- package/cjs/docs/storage-swagger.config.spec.js +0 -72
- package/cjs/dtos/file-manager.dto.js +0 -327
- package/cjs/dtos/folder.dto.js +0 -105
- package/cjs/dtos/index.js +0 -21
- package/cjs/dtos/storage-config.dto.js +0 -170
- package/cjs/dtos/upload.dto.js +0 -272
- package/cjs/entities/file-manager-with-company.entity.js +0 -61
- package/cjs/entities/file-manager.entity.js +0 -122
- package/cjs/entities/folder-with-company.entity.js +0 -55
- package/cjs/entities/folder.entity.js +0 -64
- package/cjs/entities/index.js +0 -63
- package/cjs/entities/index.spec.js +0 -18
- package/cjs/entities/storage-config-with-company.entity.js +0 -51
- package/cjs/entities/storage-config.entity.js +0 -96
- package/cjs/enums/file-location.enum.js +0 -17
- package/cjs/enums/index.js +0 -18
- package/cjs/index.js +0 -29
- package/cjs/interfaces/file-manager.interface.js +0 -4
- package/cjs/interfaces/folder.interface.js +0 -4
- package/cjs/interfaces/index.js +0 -22
- package/cjs/interfaces/storage-config.interface.js +0 -4
- package/cjs/interfaces/storage-module-options.interface.js +0 -4
- package/cjs/interfaces/storage-provider.interface.js +0 -4
- package/cjs/middlewares/file-serve.middleware.js +0 -241
- package/cjs/middlewares/file-serve.middleware.spec.js +0 -211
- package/cjs/middlewares/index.js +0 -18
- package/cjs/modules/index.js +0 -18
- package/cjs/modules/storage.module.js +0 -165
- package/cjs/modules/storage.module.spec.js +0 -184
- package/cjs/providers/azure-provider.optional.js +0 -155
- package/cjs/providers/azure-provider.optional.spec.js +0 -228
- package/cjs/providers/index.js +0 -26
- package/cjs/providers/local-provider.js +0 -230
- package/cjs/providers/local-provider.spec.js +0 -239
- package/cjs/providers/s3-provider.optional.js +0 -175
- package/cjs/providers/s3-provider.optional.spec.js +0 -243
- package/cjs/providers/sftp-provider.optional.js +0 -203
- package/cjs/providers/sftp-provider.optional.spec.js +0 -217
- package/cjs/providers/storage-factory.service.js +0 -175
- package/cjs/providers/storage-factory.service.spec.js +0 -253
- package/cjs/providers/storage-provider.registry.js +0 -41
- package/cjs/providers/storage-provider.registry.spec.js +0 -67
- package/cjs/services/file-manager.service.js +0 -387
- package/cjs/services/file-manager.service.spec.js +0 -580
- package/cjs/services/folder.service.js +0 -101
- package/cjs/services/folder.service.spec.js +0 -167
- package/cjs/services/index.js +0 -23
- package/cjs/services/storage-config.service.js +0 -129
- package/cjs/services/storage-config.service.spec.js +0 -257
- package/cjs/services/storage-datasource.provider.js +0 -137
- package/cjs/services/storage-datasource.provider.spec.js +0 -217
- package/cjs/services/storage-provider-config.service.js +0 -171
- package/cjs/services/storage-provider-config.service.spec.js +0 -265
- package/cjs/services/upload.service.js +0 -376
- package/cjs/services/upload.service.spec.js +0 -426
- package/cjs/utils/file-validator.util.js +0 -440
- package/cjs/utils/file-validator.util.spec.js +0 -259
- package/cjs/utils/image-compressor.util.js +0 -162
- package/cjs/utils/image-compressor.util.spec.js +0 -166
- package/cjs/utils/index.js +0 -18
- package/fesm/config/event-actions.js +0 -13
- package/fesm/config/message-keys.js +0 -22
- package/fesm/config/storage.constants.js +0 -7
- package/fesm/controllers/file-manager.controller.js +0 -142
- package/fesm/controllers/file-manager.controller.spec.js +0 -131
- package/fesm/controllers/folder.controller.js +0 -92
- package/fesm/controllers/folder.controller.spec.js +0 -82
- package/fesm/controllers/storage-config.controller.js +0 -92
- package/fesm/controllers/storage-config.controller.spec.js +0 -81
- package/fesm/controllers/upload.controller.js +0 -224
- package/fesm/controllers/upload.controller.spec.js +0 -128
- package/fesm/docs/storage-swagger.config.js +0 -181
- package/fesm/docs/storage-swagger.config.spec.js +0 -68
- package/fesm/dtos/file-manager.dto.js +0 -300
- package/fesm/dtos/folder.dto.js +0 -84
- package/fesm/dtos/storage-config.dto.js +0 -149
- package/fesm/dtos/upload.dto.js +0 -245
- package/fesm/entities/file-manager-with-company.entity.js +0 -51
- package/fesm/entities/file-manager.entity.js +0 -112
- package/fesm/entities/folder-with-company.entity.js +0 -45
- package/fesm/entities/folder.entity.js +0 -54
- package/fesm/entities/index.spec.js +0 -14
- package/fesm/entities/storage-config-with-company.entity.js +0 -41
- package/fesm/entities/storage-config.entity.js +0 -86
- package/fesm/enums/file-location.enum.js +0 -7
- package/fesm/interfaces/file-manager.interface.js +0 -1
- package/fesm/interfaces/folder.interface.js +0 -1
- package/fesm/interfaces/storage-config.interface.js +0 -1
- package/fesm/interfaces/storage-module-options.interface.js +0 -3
- package/fesm/interfaces/storage-provider.interface.js +0 -4
- package/fesm/middlewares/file-serve.middleware.js +0 -190
- package/fesm/middlewares/file-serve.middleware.spec.js +0 -207
- package/fesm/middlewares/index.js +0 -1
- package/fesm/modules/storage.module.js +0 -156
- package/fesm/modules/storage.module.spec.js +0 -180
- package/fesm/providers/azure-provider.optional.spec.js +0 -224
- package/fesm/providers/local-provider.js +0 -182
- package/fesm/providers/local-provider.spec.js +0 -194
- package/fesm/providers/s3-provider.optional.spec.js +0 -239
- package/fesm/providers/sftp-provider.optional.spec.js +0 -213
- package/fesm/providers/storage-factory.service.js +0 -124
- package/fesm/providers/storage-factory.service.spec.js +0 -249
- package/fesm/providers/storage-provider.registry.js +0 -34
- package/fesm/providers/storage-provider.registry.spec.js +0 -63
- package/fesm/services/file-manager.service.js +0 -336
- package/fesm/services/file-manager.service.spec.js +0 -576
- package/fesm/services/folder.service.js +0 -91
- package/fesm/services/folder.service.spec.js +0 -163
- package/fesm/services/storage-config.service.js +0 -119
- package/fesm/services/storage-config.service.spec.js +0 -253
- package/fesm/services/storage-datasource.provider.js +0 -86
- package/fesm/services/storage-datasource.provider.spec.js +0 -172
- package/fesm/services/storage-provider-config.service.js +0 -161
- package/fesm/services/storage-provider-config.service.spec.js +0 -261
- package/fesm/services/upload.service.js +0 -366
- package/fesm/services/upload.service.spec.js +0 -422
- package/fesm/utils/file-validator.util.js +0 -433
- package/fesm/utils/file-validator.util.spec.js +0 -255
- package/fesm/utils/image-compressor.util.js +0 -114
- package/fesm/utils/image-compressor.util.spec.js +0 -162
|
@@ -1,213 +0,0 @@
|
|
|
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
|
-
}
|
|
14
|
-
const mockConnect = jest.fn();
|
|
15
|
-
const mockMkdir = jest.fn();
|
|
16
|
-
const mockPut = jest.fn();
|
|
17
|
-
const mockDelete = jest.fn();
|
|
18
|
-
const mockEnd = jest.fn();
|
|
19
|
-
let MockSftpClient = class MockSftpClient {
|
|
20
|
-
constructor(){
|
|
21
|
-
_define_property(this, "connect", mockConnect);
|
|
22
|
-
_define_property(this, "mkdir", mockMkdir);
|
|
23
|
-
_define_property(this, "put", mockPut);
|
|
24
|
-
_define_property(this, "delete", mockDelete);
|
|
25
|
-
_define_property(this, "end", mockEnd);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
jest.mock('ssh2-sftp-client', ()=>({
|
|
29
|
-
__esModule: true,
|
|
30
|
-
default: MockSftpClient
|
|
31
|
-
}), {
|
|
32
|
-
virtual: true
|
|
33
|
-
});
|
|
34
|
-
jest.mock('../utils/image-compressor.util', ()=>({
|
|
35
|
-
ImageCompressor: {
|
|
36
|
-
compress: jest.fn()
|
|
37
|
-
}
|
|
38
|
-
}));
|
|
39
|
-
import { SftpProvider } from './sftp-provider.optional';
|
|
40
|
-
import { ImageCompressor } from '../utils/image-compressor.util';
|
|
41
|
-
function buildMulterFile(overrides = {}) {
|
|
42
|
-
return {
|
|
43
|
-
fieldname: 'file',
|
|
44
|
-
originalname: 'photo.jpg',
|
|
45
|
-
encoding: '7bit',
|
|
46
|
-
mimetype: 'image/jpeg',
|
|
47
|
-
buffer: Buffer.from('file-bytes'),
|
|
48
|
-
size: 10,
|
|
49
|
-
stream: undefined,
|
|
50
|
-
destination: '',
|
|
51
|
-
filename: '',
|
|
52
|
-
path: '',
|
|
53
|
-
...overrides
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
describe('SftpProvider', ()=>{
|
|
57
|
-
let provider;
|
|
58
|
-
beforeEach(async ()=>{
|
|
59
|
-
jest.clearAllMocks();
|
|
60
|
-
mockConnect.mockResolvedValue(undefined);
|
|
61
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
62
|
-
mockPut.mockResolvedValue(undefined);
|
|
63
|
-
mockDelete.mockResolvedValue(undefined);
|
|
64
|
-
mockEnd.mockResolvedValue(undefined);
|
|
65
|
-
provider = new SftpProvider();
|
|
66
|
-
await provider.initialize({
|
|
67
|
-
host: 'sftp.example.com',
|
|
68
|
-
username: 'deploy',
|
|
69
|
-
password: 'secret',
|
|
70
|
-
basePath: '/uploads'
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
describe('initialize', ()=>{
|
|
74
|
-
it('connects and ensures the base directory exists', ()=>{
|
|
75
|
-
expect(mockConnect).toHaveBeenCalledWith(expect.objectContaining({
|
|
76
|
-
host: 'sftp.example.com',
|
|
77
|
-
username: 'deploy',
|
|
78
|
-
password: 'secret'
|
|
79
|
-
}));
|
|
80
|
-
expect(mockMkdir).toHaveBeenCalledWith('/uploads', true);
|
|
81
|
-
});
|
|
82
|
-
it('defaults port to 22 and basePath to /uploads when not provided', async ()=>{
|
|
83
|
-
jest.clearAllMocks();
|
|
84
|
-
mockConnect.mockResolvedValue(undefined);
|
|
85
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
86
|
-
const p = new SftpProvider();
|
|
87
|
-
await p.initialize({
|
|
88
|
-
host: 'h',
|
|
89
|
-
username: 'u'
|
|
90
|
-
});
|
|
91
|
-
expect(mockConnect).toHaveBeenCalledWith(expect.objectContaining({
|
|
92
|
-
port: 22
|
|
93
|
-
}));
|
|
94
|
-
expect(mockMkdir).toHaveBeenCalledWith('/uploads', true);
|
|
95
|
-
});
|
|
96
|
-
it('swallows mkdir errors (directory might already exist)', async ()=>{
|
|
97
|
-
jest.clearAllMocks();
|
|
98
|
-
mockConnect.mockResolvedValue(undefined);
|
|
99
|
-
mockMkdir.mockRejectedValue(new Error('EEXIST'));
|
|
100
|
-
const p = new SftpProvider();
|
|
101
|
-
await expect(p.initialize({
|
|
102
|
-
host: 'h',
|
|
103
|
-
username: 'u',
|
|
104
|
-
basePath: '/data'
|
|
105
|
-
})).resolves.toBeUndefined();
|
|
106
|
-
});
|
|
107
|
-
it('throws InternalServerErrorException when the SFTP client cannot connect during initialize', async ()=>{
|
|
108
|
-
mockConnect.mockRejectedValueOnce(new Error('ECONNREFUSED'));
|
|
109
|
-
const brokenProvider = new SftpProvider();
|
|
110
|
-
await expect(brokenProvider.initialize({
|
|
111
|
-
host: 'unreachable',
|
|
112
|
-
username: 'u'
|
|
113
|
-
})).rejects.toThrow('SFTP client not found');
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
describe('uploadFile', ()=>{
|
|
117
|
-
it('uploads the file under basePath and returns metadata', async ()=>{
|
|
118
|
-
const file = buildMulterFile();
|
|
119
|
-
const result = await provider.uploadFile(file, {});
|
|
120
|
-
expect(mockPut).toHaveBeenCalledWith(file.buffer, expect.stringContaining('/uploads/'));
|
|
121
|
-
expect(result.name).toMatch(/photo\.jpg$/);
|
|
122
|
-
expect(result.key).toContain('/uploads/');
|
|
123
|
-
});
|
|
124
|
-
it('nests the remote path under folderPath when provided', async ()=>{
|
|
125
|
-
const file = buildMulterFile();
|
|
126
|
-
const result = await provider.uploadFile(file, {
|
|
127
|
-
folderPath: 'images'
|
|
128
|
-
});
|
|
129
|
-
expect(result.key).toContain('/uploads/images/');
|
|
130
|
-
});
|
|
131
|
-
it('compresses image files when options.compress is true', async ()=>{
|
|
132
|
-
ImageCompressor.compress.mockResolvedValue({
|
|
133
|
-
buffer: Buffer.from('small'),
|
|
134
|
-
format: 'image/webp'
|
|
135
|
-
});
|
|
136
|
-
const file = buildMulterFile();
|
|
137
|
-
const result = await provider.uploadFile(file, {
|
|
138
|
-
compress: true
|
|
139
|
-
});
|
|
140
|
-
expect(ImageCompressor.compress).toHaveBeenCalled();
|
|
141
|
-
expect(result.contentType).toBe('image/webp');
|
|
142
|
-
});
|
|
143
|
-
it('reconnects if not already connected before uploading', async ()=>{
|
|
144
|
-
const file = buildMulterFile();
|
|
145
|
-
mockConnect.mockClear();
|
|
146
|
-
await provider.uploadFile(file, {});
|
|
147
|
-
// already connected from initialize(); connect() should be a no-op guarded by `connected` flag
|
|
148
|
-
expect(mockConnect).not.toHaveBeenCalled();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
describe('uploadMultipleFiles', ()=>{
|
|
152
|
-
it('uploads all files', async ()=>{
|
|
153
|
-
const files = [
|
|
154
|
-
buildMulterFile({
|
|
155
|
-
originalname: 'a.jpg'
|
|
156
|
-
}),
|
|
157
|
-
buildMulterFile({
|
|
158
|
-
originalname: 'b.jpg'
|
|
159
|
-
})
|
|
160
|
-
];
|
|
161
|
-
const results = await provider.uploadMultipleFiles(files, {});
|
|
162
|
-
expect(results).toHaveLength(2);
|
|
163
|
-
expect(mockPut).toHaveBeenCalledTimes(2);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
describe('deleteFile', ()=>{
|
|
167
|
-
it('deletes the file at the given remote path', async ()=>{
|
|
168
|
-
await provider.deleteFile('/uploads/photo.jpg');
|
|
169
|
-
expect(mockDelete).toHaveBeenCalledWith('/uploads/photo.jpg');
|
|
170
|
-
});
|
|
171
|
-
it('silently swallows delete errors', async ()=>{
|
|
172
|
-
mockDelete.mockRejectedValue(new Error('not found'));
|
|
173
|
-
await expect(provider.deleteFile('/uploads/missing.jpg')).resolves.toBeUndefined();
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
describe('deleteMultipleFiles', ()=>{
|
|
177
|
-
it('deletes every provided key', async ()=>{
|
|
178
|
-
await provider.deleteMultipleFiles([
|
|
179
|
-
'/uploads/a.jpg',
|
|
180
|
-
'/uploads/b.jpg'
|
|
181
|
-
]);
|
|
182
|
-
expect(mockDelete).toHaveBeenCalledTimes(2);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
describe('generatePresignedUrl', ()=>{
|
|
186
|
-
it('returns the key unchanged since SFTP has no presigned URL support', async ()=>{
|
|
187
|
-
const url = await provider.generatePresignedUrl('/uploads/photo.jpg', 3600);
|
|
188
|
-
expect(url).toBe('/uploads/photo.jpg');
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
describe('healthCheck', ()=>{
|
|
192
|
-
it('returns true when connected', async ()=>{
|
|
193
|
-
await expect(provider.healthCheck()).resolves.toBe(true);
|
|
194
|
-
});
|
|
195
|
-
it('returns false when reconnecting fails', async ()=>{
|
|
196
|
-
// Disconnect first so healthCheck() must attempt to reconnect via this.connect()
|
|
197
|
-
await provider.close();
|
|
198
|
-
mockConnect.mockRejectedValueOnce(new Error('unreachable'));
|
|
199
|
-
await expect(provider.healthCheck()).resolves.toBe(false);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
describe('close', ()=>{
|
|
203
|
-
it('ends the connection when connected', async ()=>{
|
|
204
|
-
await provider.close();
|
|
205
|
-
expect(mockEnd).toHaveBeenCalled();
|
|
206
|
-
});
|
|
207
|
-
it('does nothing when not connected', async ()=>{
|
|
208
|
-
const freshProvider = new SftpProvider();
|
|
209
|
-
await expect(freshProvider.close()).resolves.toBeUndefined();
|
|
210
|
-
expect(mockEnd).not.toHaveBeenCalled();
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
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
|
-
}
|
|
14
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
15
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
16
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
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
|
-
}
|
|
28
|
-
import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
|
|
29
|
-
import { Inject, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
|
30
|
-
import * as crypto from 'crypto';
|
|
31
|
-
import { StorageConfigService } from '../services';
|
|
32
|
-
import { StorageProviderRegistry } from './storage-provider.registry';
|
|
33
|
-
export class StorageFactoryService {
|
|
34
|
-
async createProvider(config) {
|
|
35
|
-
const cacheKey = this.generateCacheKey(config);
|
|
36
|
-
const cached = this.cache.get(cacheKey);
|
|
37
|
-
if (cached) return cached;
|
|
38
|
-
const ProviderClass = StorageProviderRegistry.get(config.provider);
|
|
39
|
-
if (!ProviderClass) {
|
|
40
|
-
throw new NotFoundException({
|
|
41
|
-
message: `Storage provider '${config.provider}' not available. Available: ${StorageProviderRegistry.getAll().join(', ')}`,
|
|
42
|
-
messageKey: SYSTEM_MESSAGES.SERVICE_NOT_AVAILABLE,
|
|
43
|
-
messageVariables: {
|
|
44
|
-
provider: config.provider,
|
|
45
|
-
available: StorageProviderRegistry.getAll().join(', ')
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
const instance = new ProviderClass();
|
|
51
|
-
await this.initializeProvider(instance, config);
|
|
52
|
-
this.cache.set(cacheKey, instance);
|
|
53
|
-
return instance;
|
|
54
|
-
} catch (error) {
|
|
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
|
-
messageVariables: {
|
|
60
|
-
provider: config.provider,
|
|
61
|
-
error: errorMessage
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async getProvider(providerName) {
|
|
67
|
-
const cachedKey = Array.from(this.cache.keys()).find((k)=>k.startsWith(providerName));
|
|
68
|
-
if (cachedKey) return this.cache.get(cachedKey);
|
|
69
|
-
return this.createProvider({
|
|
70
|
-
provider: providerName,
|
|
71
|
-
config: {}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
getLocalProviderBasePath() {
|
|
75
|
-
const localKey = Array.from(this.cache.keys()).find((k)=>k.startsWith('local'));
|
|
76
|
-
const provider = localKey ? this.cache.get(localKey) : null;
|
|
77
|
-
return provider && 'basePath' in provider ? provider.basePath : null;
|
|
78
|
-
}
|
|
79
|
-
clearCache() {
|
|
80
|
-
this.cache.clear();
|
|
81
|
-
}
|
|
82
|
-
async onModuleDestroy() {
|
|
83
|
-
const closePromises = Array.from(this.cache.entries()).filter(([, p])=>'close' in p && typeof p.close === 'function').map(([, p])=>p.close().catch(()=>{}));
|
|
84
|
-
await Promise.allSettled(closePromises);
|
|
85
|
-
this.cache.clear();
|
|
86
|
-
}
|
|
87
|
-
// ─── Private Helpers ──────────────────────────────────────────────────────────
|
|
88
|
-
generateCacheKey(config) {
|
|
89
|
-
const sortedKeys = Object.keys(config.config || {}).sort();
|
|
90
|
-
const hash = crypto.createHash('sha256').update(JSON.stringify(config.config, sortedKeys)).digest('hex').substring(0, 16);
|
|
91
|
-
return `${config.provider}-${hash}`;
|
|
92
|
-
}
|
|
93
|
-
async initializeProvider(instance, config) {
|
|
94
|
-
if (!('initialize' in instance) || typeof instance.initialize !== 'function') {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
let initConfig = config.config;
|
|
98
|
-
// Inject appUrl as baseUrl fallback for local provider
|
|
99
|
-
if (config.provider === 'local' && !config.config?.baseUrl) {
|
|
100
|
-
const appUrl = this.configService.getAppUrl();
|
|
101
|
-
if (appUrl) {
|
|
102
|
-
initConfig = {
|
|
103
|
-
...config.config,
|
|
104
|
-
baseUrl: appUrl
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
await instance.initialize(initConfig);
|
|
109
|
-
}
|
|
110
|
-
constructor(configService){
|
|
111
|
-
_define_property(this, "configService", void 0);
|
|
112
|
-
_define_property(this, "cache", void 0);
|
|
113
|
-
this.configService = configService;
|
|
114
|
-
this.cache = new Map();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
StorageFactoryService = _ts_decorate([
|
|
118
|
-
Injectable(),
|
|
119
|
-
_ts_param(0, Inject(StorageConfigService)),
|
|
120
|
-
_ts_metadata("design:type", Function),
|
|
121
|
-
_ts_metadata("design:paramtypes", [
|
|
122
|
-
typeof StorageConfigService === "undefined" ? Object : StorageConfigService
|
|
123
|
-
])
|
|
124
|
-
], StorageFactoryService);
|
|
@@ -1,249 +0,0 @@
|
|
|
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
|
-
}
|
|
14
|
-
import { NotFoundException, InternalServerErrorException } from '@nestjs/common';
|
|
15
|
-
import { StorageFactoryService } from './storage-factory.service';
|
|
16
|
-
import { StorageProviderRegistry } from './storage-provider.registry';
|
|
17
|
-
let FakeProvider = class FakeProvider {
|
|
18
|
-
async initialize(config) {
|
|
19
|
-
this.initializeCalledWith = config;
|
|
20
|
-
this.basePath = config?.basePath;
|
|
21
|
-
}
|
|
22
|
-
async uploadFile() {
|
|
23
|
-
return {};
|
|
24
|
-
}
|
|
25
|
-
async uploadMultipleFiles() {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
async deleteFile() {}
|
|
29
|
-
async deleteMultipleFiles() {}
|
|
30
|
-
async generatePresignedUrl() {
|
|
31
|
-
return '';
|
|
32
|
-
}
|
|
33
|
-
async healthCheck() {
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
constructor(){
|
|
37
|
-
_define_property(this, "initializeCalledWith", undefined);
|
|
38
|
-
_define_property(this, "basePath", void 0);
|
|
39
|
-
_define_property(this, "close", jest.fn().mockResolvedValue(undefined));
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
let NoInitProvider = class NoInitProvider {
|
|
43
|
-
async uploadFile() {
|
|
44
|
-
return {};
|
|
45
|
-
}
|
|
46
|
-
async uploadMultipleFiles() {
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
async deleteFile() {}
|
|
50
|
-
async deleteMultipleFiles() {}
|
|
51
|
-
async generatePresignedUrl() {
|
|
52
|
-
return '';
|
|
53
|
-
}
|
|
54
|
-
async healthCheck() {
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
describe('StorageFactoryService', ()=>{
|
|
59
|
-
let service;
|
|
60
|
-
let mockConfigService;
|
|
61
|
-
beforeEach(()=>{
|
|
62
|
-
StorageProviderRegistry.clear();
|
|
63
|
-
StorageProviderRegistry.register('fake', FakeProvider);
|
|
64
|
-
StorageProviderRegistry.register('local', FakeProvider);
|
|
65
|
-
StorageProviderRegistry.register('noinit', NoInitProvider);
|
|
66
|
-
mockConfigService = {
|
|
67
|
-
getAppUrl: jest.fn().mockReturnValue('http://localhost:2002')
|
|
68
|
-
};
|
|
69
|
-
service = new StorageFactoryService(mockConfigService);
|
|
70
|
-
});
|
|
71
|
-
afterEach(()=>{
|
|
72
|
-
StorageProviderRegistry.clear();
|
|
73
|
-
});
|
|
74
|
-
describe('createProvider', ()=>{
|
|
75
|
-
it('creates and initializes a provider instance from the registry', async ()=>{
|
|
76
|
-
const provider = await service.createProvider({
|
|
77
|
-
provider: 'fake',
|
|
78
|
-
config: {
|
|
79
|
-
bucket: 'x'
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
expect(provider).toBeInstanceOf(FakeProvider);
|
|
83
|
-
expect(provider.initializeCalledWith).toEqual({
|
|
84
|
-
bucket: 'x'
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
it('caches providers by a hash of their config and reuses the cached instance', async ()=>{
|
|
88
|
-
const config = {
|
|
89
|
-
provider: 'fake',
|
|
90
|
-
config: {
|
|
91
|
-
bucket: 'x'
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const first = await service.createProvider(config);
|
|
95
|
-
const second = await service.createProvider(config);
|
|
96
|
-
expect(first).toBe(second);
|
|
97
|
-
});
|
|
98
|
-
it('creates distinct instances for different configs of the same provider', async ()=>{
|
|
99
|
-
const first = await service.createProvider({
|
|
100
|
-
provider: 'fake',
|
|
101
|
-
config: {
|
|
102
|
-
bucket: 'a'
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
const second = await service.createProvider({
|
|
106
|
-
provider: 'fake',
|
|
107
|
-
config: {
|
|
108
|
-
bucket: 'b'
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
expect(first).not.toBe(second);
|
|
112
|
-
});
|
|
113
|
-
it('throws NotFoundException when the requested provider is not registered', async ()=>{
|
|
114
|
-
await expect(service.createProvider({
|
|
115
|
-
provider: 'unregistered',
|
|
116
|
-
config: {}
|
|
117
|
-
})).rejects.toThrow(NotFoundException);
|
|
118
|
-
});
|
|
119
|
-
it('skips calling initialize() when the provider does not implement it', async ()=>{
|
|
120
|
-
const provider = await service.createProvider({
|
|
121
|
-
provider: 'noinit',
|
|
122
|
-
config: {}
|
|
123
|
-
});
|
|
124
|
-
expect(provider).toBeInstanceOf(NoInitProvider);
|
|
125
|
-
});
|
|
126
|
-
it('wraps unexpected initialization errors in InternalServerErrorException', async ()=>{
|
|
127
|
-
let ThrowingProvider = class ThrowingProvider {
|
|
128
|
-
async initialize() {
|
|
129
|
-
throw new Error('connection refused');
|
|
130
|
-
}
|
|
131
|
-
async uploadFile() {
|
|
132
|
-
return {};
|
|
133
|
-
}
|
|
134
|
-
async uploadMultipleFiles() {
|
|
135
|
-
return [];
|
|
136
|
-
}
|
|
137
|
-
async deleteFile() {}
|
|
138
|
-
async deleteMultipleFiles() {}
|
|
139
|
-
async generatePresignedUrl() {
|
|
140
|
-
return '';
|
|
141
|
-
}
|
|
142
|
-
async healthCheck() {
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
StorageProviderRegistry.register('throwing', ThrowingProvider);
|
|
147
|
-
await expect(service.createProvider({
|
|
148
|
-
provider: 'throwing',
|
|
149
|
-
config: {}
|
|
150
|
-
})).rejects.toThrow(InternalServerErrorException);
|
|
151
|
-
});
|
|
152
|
-
it('injects appUrl as baseUrl fallback for the local provider when not explicitly configured', async ()=>{
|
|
153
|
-
const provider = await service.createProvider({
|
|
154
|
-
provider: 'local',
|
|
155
|
-
config: {
|
|
156
|
-
basePath: './uploads'
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
expect(provider.initializeCalledWith).toEqual({
|
|
160
|
-
basePath: './uploads',
|
|
161
|
-
baseUrl: 'http://localhost:2002'
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
it('does not override an explicitly configured baseUrl for the local provider', async ()=>{
|
|
165
|
-
const provider = await service.createProvider({
|
|
166
|
-
provider: 'local',
|
|
167
|
-
config: {
|
|
168
|
-
basePath: './uploads',
|
|
169
|
-
baseUrl: 'https://cdn.example.com'
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
expect(provider.initializeCalledWith.baseUrl).toBe('https://cdn.example.com');
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
describe('getProvider', ()=>{
|
|
176
|
-
it('returns a cached provider whose cache key starts with the given name', async ()=>{
|
|
177
|
-
const created = await service.createProvider({
|
|
178
|
-
provider: 'fake',
|
|
179
|
-
config: {
|
|
180
|
-
bucket: 'x'
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
const result = await service.getProvider('fake');
|
|
184
|
-
expect(result).toBe(created);
|
|
185
|
-
});
|
|
186
|
-
it('creates a new provider with empty config if none is cached yet', async ()=>{
|
|
187
|
-
const result = await service.getProvider('fake');
|
|
188
|
-
expect(result).toBeInstanceOf(FakeProvider);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
describe('getLocalProviderBasePath', ()=>{
|
|
192
|
-
it('returns null when no local provider has been created yet', ()=>{
|
|
193
|
-
expect(service.getLocalProviderBasePath()).toBeNull();
|
|
194
|
-
});
|
|
195
|
-
it("returns the cached local provider's basePath", async ()=>{
|
|
196
|
-
await service.createProvider({
|
|
197
|
-
provider: 'local',
|
|
198
|
-
config: {
|
|
199
|
-
basePath: './my-uploads'
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
expect(service.getLocalProviderBasePath()).toBe('./my-uploads');
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
describe('clearCache', ()=>{
|
|
206
|
-
it('empties the provider cache', async ()=>{
|
|
207
|
-
await service.createProvider({
|
|
208
|
-
provider: 'fake',
|
|
209
|
-
config: {
|
|
210
|
-
bucket: 'x'
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
service.clearCache();
|
|
214
|
-
expect(service.getLocalProviderBasePath()).toBeNull();
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
describe('onModuleDestroy', ()=>{
|
|
218
|
-
it('calls close() on cached providers that implement it', async ()=>{
|
|
219
|
-
const provider = await service.createProvider({
|
|
220
|
-
provider: 'fake',
|
|
221
|
-
config: {
|
|
222
|
-
bucket: 'x'
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
await service.onModuleDestroy();
|
|
226
|
-
expect(provider.close).toHaveBeenCalled();
|
|
227
|
-
});
|
|
228
|
-
it('does not throw when a provider close() call rejects', async ()=>{
|
|
229
|
-
const provider = await service.createProvider({
|
|
230
|
-
provider: 'fake',
|
|
231
|
-
config: {
|
|
232
|
-
bucket: 'y'
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
provider.close.mockRejectedValue(new Error('close failed'));
|
|
236
|
-
await expect(service.onModuleDestroy()).resolves.toBeUndefined();
|
|
237
|
-
});
|
|
238
|
-
it('clears the cache after destroy', async ()=>{
|
|
239
|
-
await service.createProvider({
|
|
240
|
-
provider: 'fake',
|
|
241
|
-
config: {
|
|
242
|
-
bucket: 'x'
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
await service.onModuleDestroy();
|
|
246
|
-
expect(service.getLocalProviderBasePath()).toBeNull();
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
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
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Static registry for storage provider classes
|
|
16
|
-
* Allows dynamic registration at runtime
|
|
17
|
-
*/ export class StorageProviderRegistry {
|
|
18
|
-
static register(name, providerClass) {
|
|
19
|
-
this.providers.set(name.toLowerCase(), providerClass);
|
|
20
|
-
}
|
|
21
|
-
static get(name) {
|
|
22
|
-
return this.providers.get(name.toLowerCase());
|
|
23
|
-
}
|
|
24
|
-
static has(name) {
|
|
25
|
-
return this.providers.has(name.toLowerCase());
|
|
26
|
-
}
|
|
27
|
-
static getAll() {
|
|
28
|
-
return Array.from(this.providers.keys());
|
|
29
|
-
}
|
|
30
|
-
static clear() {
|
|
31
|
-
this.providers.clear();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
_define_property(StorageProviderRegistry, "providers", new Map());
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { StorageProviderRegistry } from './storage-provider.registry';
|
|
2
|
-
let FakeProvider = class FakeProvider {
|
|
3
|
-
async uploadFile() {
|
|
4
|
-
return {};
|
|
5
|
-
}
|
|
6
|
-
async uploadMultipleFiles() {
|
|
7
|
-
return [];
|
|
8
|
-
}
|
|
9
|
-
async deleteFile() {}
|
|
10
|
-
async deleteMultipleFiles() {}
|
|
11
|
-
async generatePresignedUrl() {
|
|
12
|
-
return '';
|
|
13
|
-
}
|
|
14
|
-
async healthCheck() {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
let AnotherFakeProvider = class AnotherFakeProvider extends FakeProvider {
|
|
19
|
-
};
|
|
20
|
-
describe('StorageProviderRegistry', ()=>{
|
|
21
|
-
afterEach(()=>{
|
|
22
|
-
StorageProviderRegistry.clear();
|
|
23
|
-
});
|
|
24
|
-
it('registers and retrieves a provider by name', ()=>{
|
|
25
|
-
StorageProviderRegistry.register('local', FakeProvider);
|
|
26
|
-
expect(StorageProviderRegistry.get('local')).toBe(FakeProvider);
|
|
27
|
-
});
|
|
28
|
-
it('normalizes provider names to lowercase on register and lookup', ()=>{
|
|
29
|
-
StorageProviderRegistry.register('LOCAL', FakeProvider);
|
|
30
|
-
expect(StorageProviderRegistry.get('local')).toBe(FakeProvider);
|
|
31
|
-
expect(StorageProviderRegistry.has('Local')).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
it('returns undefined for a provider that was never registered', ()=>{
|
|
34
|
-
expect(StorageProviderRegistry.get('nonexistent')).toBeUndefined();
|
|
35
|
-
});
|
|
36
|
-
it('has() reports false for an unregistered provider', ()=>{
|
|
37
|
-
expect(StorageProviderRegistry.has('s3')).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
it('has() reports true once a provider is registered', ()=>{
|
|
40
|
-
StorageProviderRegistry.register('s3', FakeProvider);
|
|
41
|
-
expect(StorageProviderRegistry.has('s3')).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
it('getAll() lists every registered provider name', ()=>{
|
|
44
|
-
StorageProviderRegistry.register('local', FakeProvider);
|
|
45
|
-
StorageProviderRegistry.register('s3', AnotherFakeProvider);
|
|
46
|
-
expect(StorageProviderRegistry.getAll().sort()).toEqual([
|
|
47
|
-
'local',
|
|
48
|
-
's3'
|
|
49
|
-
]);
|
|
50
|
-
});
|
|
51
|
-
it('overwrites an existing registration when registering the same name again', ()=>{
|
|
52
|
-
StorageProviderRegistry.register('local', FakeProvider);
|
|
53
|
-
StorageProviderRegistry.register('local', AnotherFakeProvider);
|
|
54
|
-
expect(StorageProviderRegistry.get('local')).toBe(AnotherFakeProvider);
|
|
55
|
-
});
|
|
56
|
-
it('clear() removes all registered providers', ()=>{
|
|
57
|
-
StorageProviderRegistry.register('local', FakeProvider);
|
|
58
|
-
StorageProviderRegistry.register('s3', AnotherFakeProvider);
|
|
59
|
-
StorageProviderRegistry.clear();
|
|
60
|
-
expect(StorageProviderRegistry.getAll()).toEqual([]);
|
|
61
|
-
expect(StorageProviderRegistry.has('local')).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
});
|