@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,243 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
const _s3provideroptional = require("./s3-provider.optional");
|
|
6
|
-
const _imagecompressorutil = require("../utils/image-compressor.util");
|
|
7
|
-
function _define_property(obj, key, value) {
|
|
8
|
-
if (key in obj) {
|
|
9
|
-
Object.defineProperty(obj, key, {
|
|
10
|
-
value: value,
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
writable: true
|
|
14
|
-
});
|
|
15
|
-
} else {
|
|
16
|
-
obj[key] = value;
|
|
17
|
-
}
|
|
18
|
-
return obj;
|
|
19
|
-
}
|
|
20
|
-
const mockS3ClientSend = jest.fn();
|
|
21
|
-
const mockS3ClientCtor = jest.fn().mockImplementation(()=>({
|
|
22
|
-
send: mockS3ClientSend
|
|
23
|
-
}));
|
|
24
|
-
let MockDeleteObjectCommand = class MockDeleteObjectCommand {
|
|
25
|
-
constructor(input){
|
|
26
|
-
_define_property(this, "input", void 0);
|
|
27
|
-
this.input = input;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
let MockDeleteObjectsCommand = class MockDeleteObjectsCommand {
|
|
31
|
-
constructor(input){
|
|
32
|
-
_define_property(this, "input", void 0);
|
|
33
|
-
this.input = input;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
let MockGetObjectCommand = class MockGetObjectCommand {
|
|
37
|
-
constructor(input){
|
|
38
|
-
_define_property(this, "input", void 0);
|
|
39
|
-
this.input = input;
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
let MockHeadBucketCommand = class MockHeadBucketCommand {
|
|
43
|
-
constructor(input){
|
|
44
|
-
_define_property(this, "input", void 0);
|
|
45
|
-
this.input = input;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
jest.mock('@aws-sdk/client-s3', ()=>({
|
|
49
|
-
S3Client: mockS3ClientCtor,
|
|
50
|
-
DeleteObjectCommand: MockDeleteObjectCommand,
|
|
51
|
-
DeleteObjectsCommand: MockDeleteObjectsCommand,
|
|
52
|
-
GetObjectCommand: MockGetObjectCommand,
|
|
53
|
-
HeadBucketCommand: MockHeadBucketCommand
|
|
54
|
-
}));
|
|
55
|
-
const mockUploadDone = jest.fn();
|
|
56
|
-
const mockUploadCtor = jest.fn().mockImplementation(()=>({
|
|
57
|
-
done: mockUploadDone
|
|
58
|
-
}));
|
|
59
|
-
jest.mock('@aws-sdk/lib-storage', ()=>({
|
|
60
|
-
Upload: mockUploadCtor
|
|
61
|
-
}));
|
|
62
|
-
const mockGetSignedUrl = jest.fn();
|
|
63
|
-
jest.mock('@aws-sdk/s3-request-presigner', ()=>({
|
|
64
|
-
getSignedUrl: (...args)=>mockGetSignedUrl(...args)
|
|
65
|
-
}));
|
|
66
|
-
jest.mock('../utils/image-compressor.util', ()=>({
|
|
67
|
-
ImageCompressor: {
|
|
68
|
-
compress: jest.fn()
|
|
69
|
-
}
|
|
70
|
-
}));
|
|
71
|
-
function buildMulterFile(overrides = {}) {
|
|
72
|
-
return {
|
|
73
|
-
fieldname: 'file',
|
|
74
|
-
originalname: 'photo.jpg',
|
|
75
|
-
encoding: '7bit',
|
|
76
|
-
mimetype: 'image/jpeg',
|
|
77
|
-
buffer: Buffer.from('file-bytes'),
|
|
78
|
-
size: 10,
|
|
79
|
-
stream: undefined,
|
|
80
|
-
destination: '',
|
|
81
|
-
filename: '',
|
|
82
|
-
path: '',
|
|
83
|
-
...overrides
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
describe('S3Provider', ()=>{
|
|
87
|
-
let provider;
|
|
88
|
-
beforeEach(async ()=>{
|
|
89
|
-
jest.clearAllMocks();
|
|
90
|
-
mockUploadDone.mockResolvedValue(undefined);
|
|
91
|
-
provider = new _s3provideroptional.S3Provider();
|
|
92
|
-
await provider.initialize({
|
|
93
|
-
region: 'us-east-1',
|
|
94
|
-
bucket: 'my-bucket'
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
describe('initialize', ()=>{
|
|
98
|
-
it('creates an S3Client with region/endpoint/credentials', async ()=>{
|
|
99
|
-
jest.clearAllMocks();
|
|
100
|
-
const p = new _s3provideroptional.S3Provider();
|
|
101
|
-
await p.initialize({
|
|
102
|
-
region: 'eu-west-1',
|
|
103
|
-
bucket: 'bucket-2',
|
|
104
|
-
accessKeyId: 'AKIA...',
|
|
105
|
-
secretAccessKey: 'secret',
|
|
106
|
-
endpoint: 'https://s3.custom.example.com'
|
|
107
|
-
});
|
|
108
|
-
expect(mockS3ClientCtor).toHaveBeenCalledWith(expect.objectContaining({
|
|
109
|
-
region: 'eu-west-1',
|
|
110
|
-
endpoint: 'https://s3.custom.example.com',
|
|
111
|
-
credentials: {
|
|
112
|
-
accessKeyId: 'AKIA...',
|
|
113
|
-
secretAccessKey: 'secret'
|
|
114
|
-
}
|
|
115
|
-
}));
|
|
116
|
-
});
|
|
117
|
-
it('omits credentials when access key / secret are not provided', async ()=>{
|
|
118
|
-
jest.clearAllMocks();
|
|
119
|
-
const p = new _s3provideroptional.S3Provider();
|
|
120
|
-
await p.initialize({
|
|
121
|
-
region: 'us-east-1',
|
|
122
|
-
bucket: 'bucket-3'
|
|
123
|
-
});
|
|
124
|
-
expect(mockS3ClientCtor).toHaveBeenCalledWith(expect.objectContaining({
|
|
125
|
-
credentials: undefined
|
|
126
|
-
}));
|
|
127
|
-
});
|
|
128
|
-
it('throws InternalServerErrorException when the AWS SDK cannot be initialized', async ()=>{
|
|
129
|
-
mockS3ClientCtor.mockImplementationOnce(()=>{
|
|
130
|
-
throw new Error('module not found');
|
|
131
|
-
});
|
|
132
|
-
const brokenProvider = new _s3provideroptional.S3Provider();
|
|
133
|
-
await expect(brokenProvider.initialize({
|
|
134
|
-
region: 'us-east-1',
|
|
135
|
-
bucket: 'x'
|
|
136
|
-
})).rejects.toThrow('AWS SDK not found');
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
describe('uploadFile', ()=>{
|
|
140
|
-
it('uploads via the S3 Upload helper and returns file metadata', async ()=>{
|
|
141
|
-
const file = buildMulterFile();
|
|
142
|
-
const result = await provider.uploadFile(file, {});
|
|
143
|
-
expect(mockUploadCtor).toHaveBeenCalledWith(expect.objectContaining({
|
|
144
|
-
params: expect.objectContaining({
|
|
145
|
-
Bucket: 'my-bucket',
|
|
146
|
-
ContentType: 'image/jpeg'
|
|
147
|
-
})
|
|
148
|
-
}));
|
|
149
|
-
expect(mockUploadDone).toHaveBeenCalled();
|
|
150
|
-
expect(result.name).toMatch(/photo\.jpg$/);
|
|
151
|
-
expect(result.key).toBe(result.name);
|
|
152
|
-
});
|
|
153
|
-
it('prefixes the key with folderPath when provided', async ()=>{
|
|
154
|
-
const file = buildMulterFile();
|
|
155
|
-
const result = await provider.uploadFile(file, {
|
|
156
|
-
folderPath: 'avatars'
|
|
157
|
-
});
|
|
158
|
-
expect(result.key).toMatch(/^avatars\//);
|
|
159
|
-
});
|
|
160
|
-
it('compresses image files before upload when options.compress is true', async ()=>{
|
|
161
|
-
_imagecompressorutil.ImageCompressor.compress.mockResolvedValue({
|
|
162
|
-
buffer: Buffer.from('compressed'),
|
|
163
|
-
format: 'image/webp'
|
|
164
|
-
});
|
|
165
|
-
const file = buildMulterFile();
|
|
166
|
-
const result = await provider.uploadFile(file, {
|
|
167
|
-
compress: true
|
|
168
|
-
});
|
|
169
|
-
expect(_imagecompressorutil.ImageCompressor.compress).toHaveBeenCalled();
|
|
170
|
-
expect(result.contentType).toBe('image/webp');
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
describe('uploadMultipleFiles', ()=>{
|
|
174
|
-
it('uploads each file and returns metadata for all', async ()=>{
|
|
175
|
-
const files = [
|
|
176
|
-
buildMulterFile({
|
|
177
|
-
originalname: 'a.jpg'
|
|
178
|
-
}),
|
|
179
|
-
buildMulterFile({
|
|
180
|
-
originalname: 'b.jpg'
|
|
181
|
-
})
|
|
182
|
-
];
|
|
183
|
-
const results = await provider.uploadMultipleFiles(files, {});
|
|
184
|
-
expect(results).toHaveLength(2);
|
|
185
|
-
expect(mockUploadCtor).toHaveBeenCalledTimes(2);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
describe('deleteFile', ()=>{
|
|
189
|
-
it('sends a DeleteObjectCommand with bucket and key', async ()=>{
|
|
190
|
-
await provider.deleteFile('some/key.jpg');
|
|
191
|
-
expect(mockS3ClientSend).toHaveBeenCalledWith(expect.any(MockDeleteObjectCommand));
|
|
192
|
-
const sentCommand = mockS3ClientSend.mock.calls[0][0];
|
|
193
|
-
expect(sentCommand.input).toEqual({
|
|
194
|
-
Bucket: 'my-bucket',
|
|
195
|
-
Key: 'some/key.jpg'
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
describe('deleteMultipleFiles', ()=>{
|
|
200
|
-
it('sends a DeleteObjectsCommand containing all keys', async ()=>{
|
|
201
|
-
await provider.deleteMultipleFiles([
|
|
202
|
-
'a.jpg',
|
|
203
|
-
'b.jpg'
|
|
204
|
-
]);
|
|
205
|
-
const sentCommand = mockS3ClientSend.mock.calls[0][0];
|
|
206
|
-
expect(sentCommand.input.Delete.Objects).toEqual([
|
|
207
|
-
{
|
|
208
|
-
Key: 'a.jpg'
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
Key: 'b.jpg'
|
|
212
|
-
}
|
|
213
|
-
]);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
describe('generatePresignedUrl', ()=>{
|
|
217
|
-
it('delegates to getSignedUrl with the configured expiry', async ()=>{
|
|
218
|
-
mockGetSignedUrl.mockResolvedValue('https://signed.example.com/some/key.jpg');
|
|
219
|
-
const url = await provider.generatePresignedUrl('some/key.jpg', 900);
|
|
220
|
-
expect(mockGetSignedUrl).toHaveBeenCalledWith(expect.anything(), expect.any(MockGetObjectCommand), {
|
|
221
|
-
expiresIn: 900
|
|
222
|
-
});
|
|
223
|
-
expect(url).toBe('https://signed.example.com/some/key.jpg');
|
|
224
|
-
});
|
|
225
|
-
it('defaults expiry to 3600 seconds when not provided', async ()=>{
|
|
226
|
-
mockGetSignedUrl.mockResolvedValue('https://signed.example.com/x');
|
|
227
|
-
await provider.generatePresignedUrl('x');
|
|
228
|
-
expect(mockGetSignedUrl).toHaveBeenCalledWith(expect.anything(), expect.anything(), {
|
|
229
|
-
expiresIn: 3600
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
describe('healthCheck', ()=>{
|
|
234
|
-
it('returns true when HeadBucketCommand succeeds', async ()=>{
|
|
235
|
-
mockS3ClientSend.mockResolvedValue({});
|
|
236
|
-
await expect(provider.healthCheck()).resolves.toBe(true);
|
|
237
|
-
});
|
|
238
|
-
it('returns false when HeadBucketCommand rejects (bucket missing/unauthorized)', async ()=>{
|
|
239
|
-
mockS3ClientSend.mockRejectedValue(new Error('Forbidden'));
|
|
240
|
-
await expect(provider.healthCheck()).resolves.toBe(false);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
});
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OPTIONAL SFTP Provider
|
|
3
|
-
*
|
|
4
|
-
* This provider requires ssh2-sftp-client package to be installed.
|
|
5
|
-
* Only import this if you need SFTP storage.
|
|
6
|
-
*
|
|
7
|
-
* Installation:
|
|
8
|
-
* npm install ssh2-sftp-client
|
|
9
|
-
* npm install -D @types/ssh2-sftp-client
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* import { SftpProvider } from '@flusys/nestjs-storage/providers/sftp-provider.optional';
|
|
13
|
-
* import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
|
|
14
|
-
*
|
|
15
|
-
* StorageProviderRegistry.register(FileLocationEnum.SFTP, SftpProvider);
|
|
16
|
-
*/ "use strict";
|
|
17
|
-
Object.defineProperty(exports, "__esModule", {
|
|
18
|
-
value: true
|
|
19
|
-
});
|
|
20
|
-
Object.defineProperty(exports, "SftpProvider", {
|
|
21
|
-
enumerable: true,
|
|
22
|
-
get: function() {
|
|
23
|
-
return SftpProvider;
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
const _common = require("@nestjs/common");
|
|
27
|
-
const _constants = require("@flusys/nestjs-shared/constants");
|
|
28
|
-
const _imagecompressorutil = require("../utils/image-compressor.util");
|
|
29
|
-
const _path = /*#__PURE__*/ _interop_require_wildcard(require("path"));
|
|
30
|
-
const _uuid = require("uuid");
|
|
31
|
-
function _define_property(obj, key, value) {
|
|
32
|
-
if (key in obj) {
|
|
33
|
-
Object.defineProperty(obj, key, {
|
|
34
|
-
value: value,
|
|
35
|
-
enumerable: true,
|
|
36
|
-
configurable: true,
|
|
37
|
-
writable: true
|
|
38
|
-
});
|
|
39
|
-
} else {
|
|
40
|
-
obj[key] = value;
|
|
41
|
-
}
|
|
42
|
-
return obj;
|
|
43
|
-
}
|
|
44
|
-
function _getRequireWildcardCache(nodeInterop) {
|
|
45
|
-
if (typeof WeakMap !== "function") return null;
|
|
46
|
-
var cacheBabelInterop = new WeakMap();
|
|
47
|
-
var cacheNodeInterop = new WeakMap();
|
|
48
|
-
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
49
|
-
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
50
|
-
})(nodeInterop);
|
|
51
|
-
}
|
|
52
|
-
function _interop_require_wildcard(obj, nodeInterop) {
|
|
53
|
-
if (!nodeInterop && obj && obj.__esModule) {
|
|
54
|
-
return obj;
|
|
55
|
-
}
|
|
56
|
-
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
57
|
-
return {
|
|
58
|
-
default: obj
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
var cache = _getRequireWildcardCache(nodeInterop);
|
|
62
|
-
if (cache && cache.has(obj)) {
|
|
63
|
-
return cache.get(obj);
|
|
64
|
-
}
|
|
65
|
-
var newObj = {
|
|
66
|
-
__proto__: null
|
|
67
|
-
};
|
|
68
|
-
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
69
|
-
for(var key in obj){
|
|
70
|
-
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
71
|
-
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
72
|
-
if (desc && (desc.get || desc.set)) {
|
|
73
|
-
Object.defineProperty(newObj, key, desc);
|
|
74
|
-
} else {
|
|
75
|
-
newObj[key] = obj[key];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
newObj.default = obj;
|
|
80
|
-
if (cache) {
|
|
81
|
-
cache.set(obj, newObj);
|
|
82
|
-
}
|
|
83
|
-
return newObj;
|
|
84
|
-
}
|
|
85
|
-
let SftpProvider = class SftpProvider {
|
|
86
|
-
/**
|
|
87
|
-
* Initialize SFTP provider with configuration
|
|
88
|
-
*/ async initialize(config) {
|
|
89
|
-
try {
|
|
90
|
-
// Dynamic import - only loads SFTP SDK if this provider is used
|
|
91
|
-
const sftp = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("ssh2-sftp-client")));
|
|
92
|
-
const SftpClient = sftp.default || sftp;
|
|
93
|
-
this.sftp = new SftpClient();
|
|
94
|
-
this.config = {
|
|
95
|
-
host: config.host,
|
|
96
|
-
port: config.port || 22,
|
|
97
|
-
username: config.username,
|
|
98
|
-
password: config.password,
|
|
99
|
-
privateKey: config.privateKey,
|
|
100
|
-
basePath: config.basePath || '/uploads'
|
|
101
|
-
};
|
|
102
|
-
// Connect
|
|
103
|
-
await this.connect();
|
|
104
|
-
// Ensure base directory exists
|
|
105
|
-
await this.ensureDirectory(this.config.basePath);
|
|
106
|
-
} catch {
|
|
107
|
-
throw new _common.InternalServerErrorException({
|
|
108
|
-
message: 'SFTP client not found',
|
|
109
|
-
messageKey: _constants.SYSTEM_MESSAGES.SDK_NOT_INSTALLED,
|
|
110
|
-
messageVariables: {
|
|
111
|
-
sdk: 'ssh2-sftp-client'
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
async connect() {
|
|
117
|
-
if (!this.connected) {
|
|
118
|
-
await this.sftp.connect({
|
|
119
|
-
host: this.config.host,
|
|
120
|
-
port: this.config.port,
|
|
121
|
-
username: this.config.username,
|
|
122
|
-
password: this.config.password,
|
|
123
|
-
privateKey: this.config.privateKey
|
|
124
|
-
});
|
|
125
|
-
this.connected = true;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
async ensureDirectory(dirPath) {
|
|
129
|
-
try {
|
|
130
|
-
await this.sftp.mkdir(dirPath, true);
|
|
131
|
-
} catch {
|
|
132
|
-
// Directory might already exist
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async uploadFile(file, options) {
|
|
136
|
-
await this.connect();
|
|
137
|
-
let processedBuffer = file.buffer;
|
|
138
|
-
let contentType = file.mimetype;
|
|
139
|
-
const fileName = `${(0, _uuid.v4)()}-${file.originalname}`;
|
|
140
|
-
// Compress image if needed
|
|
141
|
-
if (options.compress && file.mimetype.startsWith('image/')) {
|
|
142
|
-
const result = await _imagecompressorutil.ImageCompressor.compress(file.buffer, file.mimetype, {
|
|
143
|
-
maxWidth: options.maxWidth,
|
|
144
|
-
maxHeight: options.maxHeight,
|
|
145
|
-
quality: options.quality,
|
|
146
|
-
format: options.format
|
|
147
|
-
});
|
|
148
|
-
processedBuffer = result.buffer;
|
|
149
|
-
contentType = result.format;
|
|
150
|
-
}
|
|
151
|
-
const remotePath = options.folderPath ? _path.join(this.config.basePath, options.folderPath, fileName) : _path.join(this.config.basePath, fileName);
|
|
152
|
-
// Ensure parent directory exists
|
|
153
|
-
const remoteDir = _path.dirname(remotePath);
|
|
154
|
-
await this.ensureDirectory(remoteDir);
|
|
155
|
-
// Upload file
|
|
156
|
-
await this.sftp.put(processedBuffer, remotePath);
|
|
157
|
-
return {
|
|
158
|
-
name: fileName,
|
|
159
|
-
key: remotePath,
|
|
160
|
-
size: processedBuffer.length,
|
|
161
|
-
contentType
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
async uploadMultipleFiles(files, options) {
|
|
165
|
-
return Promise.all(files.map((file)=>this.uploadFile(file, options)));
|
|
166
|
-
}
|
|
167
|
-
async deleteFile(key) {
|
|
168
|
-
await this.connect();
|
|
169
|
-
try {
|
|
170
|
-
await this.sftp.delete(key);
|
|
171
|
-
} catch {
|
|
172
|
-
// Silent failure
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
async deleteMultipleFiles(keys) {
|
|
176
|
-
await Promise.all(keys.map((key)=>this.deleteFile(key)));
|
|
177
|
-
}
|
|
178
|
-
async generatePresignedUrl(key, _expiresInSeconds) {
|
|
179
|
-
// SFTP doesn't support presigned URLs, return the key
|
|
180
|
-
return key;
|
|
181
|
-
}
|
|
182
|
-
async healthCheck() {
|
|
183
|
-
try {
|
|
184
|
-
await this.connect();
|
|
185
|
-
return this.connected;
|
|
186
|
-
} catch {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Close SFTP connection
|
|
192
|
-
*/ async close() {
|
|
193
|
-
if (this.connected) {
|
|
194
|
-
await this.sftp.end();
|
|
195
|
-
this.connected = false;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
constructor(){
|
|
199
|
-
_define_property(this, "sftp", void 0); // SftpClient
|
|
200
|
-
_define_property(this, "config", void 0);
|
|
201
|
-
_define_property(this, "connected", false);
|
|
202
|
-
}
|
|
203
|
-
};
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
const _sftpprovideroptional = require("./sftp-provider.optional");
|
|
6
|
-
const _imagecompressorutil = require("../utils/image-compressor.util");
|
|
7
|
-
function _define_property(obj, key, value) {
|
|
8
|
-
if (key in obj) {
|
|
9
|
-
Object.defineProperty(obj, key, {
|
|
10
|
-
value: value,
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
writable: true
|
|
14
|
-
});
|
|
15
|
-
} else {
|
|
16
|
-
obj[key] = value;
|
|
17
|
-
}
|
|
18
|
-
return obj;
|
|
19
|
-
}
|
|
20
|
-
const mockConnect = jest.fn();
|
|
21
|
-
const mockMkdir = jest.fn();
|
|
22
|
-
const mockPut = jest.fn();
|
|
23
|
-
const mockDelete = jest.fn();
|
|
24
|
-
const mockEnd = jest.fn();
|
|
25
|
-
let MockSftpClient = class MockSftpClient {
|
|
26
|
-
constructor(){
|
|
27
|
-
_define_property(this, "connect", mockConnect);
|
|
28
|
-
_define_property(this, "mkdir", mockMkdir);
|
|
29
|
-
_define_property(this, "put", mockPut);
|
|
30
|
-
_define_property(this, "delete", mockDelete);
|
|
31
|
-
_define_property(this, "end", mockEnd);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
jest.mock('ssh2-sftp-client', ()=>({
|
|
35
|
-
__esModule: true,
|
|
36
|
-
default: MockSftpClient
|
|
37
|
-
}), {
|
|
38
|
-
virtual: true
|
|
39
|
-
});
|
|
40
|
-
jest.mock('../utils/image-compressor.util', ()=>({
|
|
41
|
-
ImageCompressor: {
|
|
42
|
-
compress: jest.fn()
|
|
43
|
-
}
|
|
44
|
-
}));
|
|
45
|
-
function buildMulterFile(overrides = {}) {
|
|
46
|
-
return {
|
|
47
|
-
fieldname: 'file',
|
|
48
|
-
originalname: 'photo.jpg',
|
|
49
|
-
encoding: '7bit',
|
|
50
|
-
mimetype: 'image/jpeg',
|
|
51
|
-
buffer: Buffer.from('file-bytes'),
|
|
52
|
-
size: 10,
|
|
53
|
-
stream: undefined,
|
|
54
|
-
destination: '',
|
|
55
|
-
filename: '',
|
|
56
|
-
path: '',
|
|
57
|
-
...overrides
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
describe('SftpProvider', ()=>{
|
|
61
|
-
let provider;
|
|
62
|
-
beforeEach(async ()=>{
|
|
63
|
-
jest.clearAllMocks();
|
|
64
|
-
mockConnect.mockResolvedValue(undefined);
|
|
65
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
66
|
-
mockPut.mockResolvedValue(undefined);
|
|
67
|
-
mockDelete.mockResolvedValue(undefined);
|
|
68
|
-
mockEnd.mockResolvedValue(undefined);
|
|
69
|
-
provider = new _sftpprovideroptional.SftpProvider();
|
|
70
|
-
await provider.initialize({
|
|
71
|
-
host: 'sftp.example.com',
|
|
72
|
-
username: 'deploy',
|
|
73
|
-
password: 'secret',
|
|
74
|
-
basePath: '/uploads'
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
describe('initialize', ()=>{
|
|
78
|
-
it('connects and ensures the base directory exists', ()=>{
|
|
79
|
-
expect(mockConnect).toHaveBeenCalledWith(expect.objectContaining({
|
|
80
|
-
host: 'sftp.example.com',
|
|
81
|
-
username: 'deploy',
|
|
82
|
-
password: 'secret'
|
|
83
|
-
}));
|
|
84
|
-
expect(mockMkdir).toHaveBeenCalledWith('/uploads', true);
|
|
85
|
-
});
|
|
86
|
-
it('defaults port to 22 and basePath to /uploads when not provided', async ()=>{
|
|
87
|
-
jest.clearAllMocks();
|
|
88
|
-
mockConnect.mockResolvedValue(undefined);
|
|
89
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
90
|
-
const p = new _sftpprovideroptional.SftpProvider();
|
|
91
|
-
await p.initialize({
|
|
92
|
-
host: 'h',
|
|
93
|
-
username: 'u'
|
|
94
|
-
});
|
|
95
|
-
expect(mockConnect).toHaveBeenCalledWith(expect.objectContaining({
|
|
96
|
-
port: 22
|
|
97
|
-
}));
|
|
98
|
-
expect(mockMkdir).toHaveBeenCalledWith('/uploads', true);
|
|
99
|
-
});
|
|
100
|
-
it('swallows mkdir errors (directory might already exist)', async ()=>{
|
|
101
|
-
jest.clearAllMocks();
|
|
102
|
-
mockConnect.mockResolvedValue(undefined);
|
|
103
|
-
mockMkdir.mockRejectedValue(new Error('EEXIST'));
|
|
104
|
-
const p = new _sftpprovideroptional.SftpProvider();
|
|
105
|
-
await expect(p.initialize({
|
|
106
|
-
host: 'h',
|
|
107
|
-
username: 'u',
|
|
108
|
-
basePath: '/data'
|
|
109
|
-
})).resolves.toBeUndefined();
|
|
110
|
-
});
|
|
111
|
-
it('throws InternalServerErrorException when the SFTP client cannot connect during initialize', async ()=>{
|
|
112
|
-
mockConnect.mockRejectedValueOnce(new Error('ECONNREFUSED'));
|
|
113
|
-
const brokenProvider = new _sftpprovideroptional.SftpProvider();
|
|
114
|
-
await expect(brokenProvider.initialize({
|
|
115
|
-
host: 'unreachable',
|
|
116
|
-
username: 'u'
|
|
117
|
-
})).rejects.toThrow('SFTP client not found');
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe('uploadFile', ()=>{
|
|
121
|
-
it('uploads the file under basePath and returns metadata', async ()=>{
|
|
122
|
-
const file = buildMulterFile();
|
|
123
|
-
const result = await provider.uploadFile(file, {});
|
|
124
|
-
expect(mockPut).toHaveBeenCalledWith(file.buffer, expect.stringContaining('/uploads/'));
|
|
125
|
-
expect(result.name).toMatch(/photo\.jpg$/);
|
|
126
|
-
expect(result.key).toContain('/uploads/');
|
|
127
|
-
});
|
|
128
|
-
it('nests the remote path under folderPath when provided', async ()=>{
|
|
129
|
-
const file = buildMulterFile();
|
|
130
|
-
const result = await provider.uploadFile(file, {
|
|
131
|
-
folderPath: 'images'
|
|
132
|
-
});
|
|
133
|
-
expect(result.key).toContain('/uploads/images/');
|
|
134
|
-
});
|
|
135
|
-
it('compresses image files when options.compress is true', async ()=>{
|
|
136
|
-
_imagecompressorutil.ImageCompressor.compress.mockResolvedValue({
|
|
137
|
-
buffer: Buffer.from('small'),
|
|
138
|
-
format: 'image/webp'
|
|
139
|
-
});
|
|
140
|
-
const file = buildMulterFile();
|
|
141
|
-
const result = await provider.uploadFile(file, {
|
|
142
|
-
compress: true
|
|
143
|
-
});
|
|
144
|
-
expect(_imagecompressorutil.ImageCompressor.compress).toHaveBeenCalled();
|
|
145
|
-
expect(result.contentType).toBe('image/webp');
|
|
146
|
-
});
|
|
147
|
-
it('reconnects if not already connected before uploading', async ()=>{
|
|
148
|
-
const file = buildMulterFile();
|
|
149
|
-
mockConnect.mockClear();
|
|
150
|
-
await provider.uploadFile(file, {});
|
|
151
|
-
// already connected from initialize(); connect() should be a no-op guarded by `connected` flag
|
|
152
|
-
expect(mockConnect).not.toHaveBeenCalled();
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
describe('uploadMultipleFiles', ()=>{
|
|
156
|
-
it('uploads all files', async ()=>{
|
|
157
|
-
const files = [
|
|
158
|
-
buildMulterFile({
|
|
159
|
-
originalname: 'a.jpg'
|
|
160
|
-
}),
|
|
161
|
-
buildMulterFile({
|
|
162
|
-
originalname: 'b.jpg'
|
|
163
|
-
})
|
|
164
|
-
];
|
|
165
|
-
const results = await provider.uploadMultipleFiles(files, {});
|
|
166
|
-
expect(results).toHaveLength(2);
|
|
167
|
-
expect(mockPut).toHaveBeenCalledTimes(2);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
describe('deleteFile', ()=>{
|
|
171
|
-
it('deletes the file at the given remote path', async ()=>{
|
|
172
|
-
await provider.deleteFile('/uploads/photo.jpg');
|
|
173
|
-
expect(mockDelete).toHaveBeenCalledWith('/uploads/photo.jpg');
|
|
174
|
-
});
|
|
175
|
-
it('silently swallows delete errors', async ()=>{
|
|
176
|
-
mockDelete.mockRejectedValue(new Error('not found'));
|
|
177
|
-
await expect(provider.deleteFile('/uploads/missing.jpg')).resolves.toBeUndefined();
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
describe('deleteMultipleFiles', ()=>{
|
|
181
|
-
it('deletes every provided key', async ()=>{
|
|
182
|
-
await provider.deleteMultipleFiles([
|
|
183
|
-
'/uploads/a.jpg',
|
|
184
|
-
'/uploads/b.jpg'
|
|
185
|
-
]);
|
|
186
|
-
expect(mockDelete).toHaveBeenCalledTimes(2);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
describe('generatePresignedUrl', ()=>{
|
|
190
|
-
it('returns the key unchanged since SFTP has no presigned URL support', async ()=>{
|
|
191
|
-
const url = await provider.generatePresignedUrl('/uploads/photo.jpg', 3600);
|
|
192
|
-
expect(url).toBe('/uploads/photo.jpg');
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
describe('healthCheck', ()=>{
|
|
196
|
-
it('returns true when connected', async ()=>{
|
|
197
|
-
await expect(provider.healthCheck()).resolves.toBe(true);
|
|
198
|
-
});
|
|
199
|
-
it('returns false when reconnecting fails', async ()=>{
|
|
200
|
-
// Disconnect first so healthCheck() must attempt to reconnect via this.connect()
|
|
201
|
-
await provider.close();
|
|
202
|
-
mockConnect.mockRejectedValueOnce(new Error('unreachable'));
|
|
203
|
-
await expect(provider.healthCheck()).resolves.toBe(false);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
describe('close', ()=>{
|
|
207
|
-
it('ends the connection when connected', async ()=>{
|
|
208
|
-
await provider.close();
|
|
209
|
-
expect(mockEnd).toHaveBeenCalled();
|
|
210
|
-
});
|
|
211
|
-
it('does nothing when not connected', async ()=>{
|
|
212
|
-
const freshProvider = new _sftpprovideroptional.SftpProvider();
|
|
213
|
-
await expect(freshProvider.close()).resolves.toBeUndefined();
|
|
214
|
-
expect(mockEnd).not.toHaveBeenCalled();
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
});
|