@flusys/nestjs-storage 7.1.0 → 8.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LOCAL File System Provider
|
|
3
|
-
*
|
|
4
|
-
* This provider uses Node.js built-in 'fs' module - NO external dependencies required!
|
|
5
|
-
* Perfect for same-server storage scenarios.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* import { LocalProvider } from '@flusys/nestjs-storage/providers/local-provider';
|
|
9
|
-
* import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
|
|
10
|
-
*
|
|
11
|
-
* StorageProviderRegistry.register(FileLocationEnum.LOCAL, LocalProvider);
|
|
12
|
-
*/ "use strict";
|
|
13
|
-
Object.defineProperty(exports, "__esModule", {
|
|
14
|
-
value: true
|
|
15
|
-
});
|
|
16
|
-
Object.defineProperty(exports, "LocalProvider", {
|
|
17
|
-
enumerable: true,
|
|
18
|
-
get: function() {
|
|
19
|
-
return LocalProvider;
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
const _common = require("@nestjs/common");
|
|
23
|
-
const _constants = require("@flusys/nestjs-shared/constants");
|
|
24
|
-
const _imagecompressorutil = require("../utils/image-compressor.util");
|
|
25
|
-
const _promises = /*#__PURE__*/ _interop_require_wildcard(require("fs/promises"));
|
|
26
|
-
const _path = /*#__PURE__*/ _interop_require_wildcard(require("path"));
|
|
27
|
-
const _uuid = require("uuid");
|
|
28
|
-
function _define_property(obj, key, value) {
|
|
29
|
-
if (key in obj) {
|
|
30
|
-
Object.defineProperty(obj, key, {
|
|
31
|
-
value: value,
|
|
32
|
-
enumerable: true,
|
|
33
|
-
configurable: true,
|
|
34
|
-
writable: true
|
|
35
|
-
});
|
|
36
|
-
} else {
|
|
37
|
-
obj[key] = value;
|
|
38
|
-
}
|
|
39
|
-
return obj;
|
|
40
|
-
}
|
|
41
|
-
function _getRequireWildcardCache(nodeInterop) {
|
|
42
|
-
if (typeof WeakMap !== "function") return null;
|
|
43
|
-
var cacheBabelInterop = new WeakMap();
|
|
44
|
-
var cacheNodeInterop = new WeakMap();
|
|
45
|
-
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
46
|
-
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
47
|
-
})(nodeInterop);
|
|
48
|
-
}
|
|
49
|
-
function _interop_require_wildcard(obj, nodeInterop) {
|
|
50
|
-
if (!nodeInterop && obj && obj.__esModule) {
|
|
51
|
-
return obj;
|
|
52
|
-
}
|
|
53
|
-
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
54
|
-
return {
|
|
55
|
-
default: obj
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
var cache = _getRequireWildcardCache(nodeInterop);
|
|
59
|
-
if (cache && cache.has(obj)) {
|
|
60
|
-
return cache.get(obj);
|
|
61
|
-
}
|
|
62
|
-
var newObj = {
|
|
63
|
-
__proto__: null
|
|
64
|
-
};
|
|
65
|
-
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
66
|
-
for(var key in obj){
|
|
67
|
-
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
68
|
-
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
69
|
-
if (desc && (desc.get || desc.set)) {
|
|
70
|
-
Object.defineProperty(newObj, key, desc);
|
|
71
|
-
} else {
|
|
72
|
-
newObj[key] = obj[key];
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
newObj.default = obj;
|
|
77
|
-
if (cache) {
|
|
78
|
-
cache.set(obj, newObj);
|
|
79
|
-
}
|
|
80
|
-
return newObj;
|
|
81
|
-
}
|
|
82
|
-
let LocalProvider = class LocalProvider {
|
|
83
|
-
/**
|
|
84
|
-
* SECURITY: Validates that a target path does not escape the base directory
|
|
85
|
-
* Prevents path traversal attacks using ../ sequences
|
|
86
|
-
* @throws Error if path traversal is detected
|
|
87
|
-
*/ validatePathWithinBase(targetPath) {
|
|
88
|
-
const normalizedBasePath = _path.resolve(this.basePath);
|
|
89
|
-
const normalizedTargetPath = _path.resolve(targetPath);
|
|
90
|
-
if (!normalizedTargetPath.startsWith(normalizedBasePath + _path.sep) && normalizedTargetPath !== normalizedBasePath) {
|
|
91
|
-
throw new _common.BadRequestException({
|
|
92
|
-
message: 'Invalid path: Path traversal attempt detected',
|
|
93
|
-
messageKey: _constants.SYSTEM_MESSAGES.PATH_TRAVERSAL_DETECTED
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* SECURITY: Validates file key format to prevent malicious input
|
|
99
|
-
* @throws Error if key contains suspicious patterns
|
|
100
|
-
*/ validateKeyFormat(key) {
|
|
101
|
-
if (!key || typeof key !== 'string' || key.trim().length === 0) {
|
|
102
|
-
throw new _common.BadRequestException({
|
|
103
|
-
message: 'Invalid file key: empty or invalid',
|
|
104
|
-
messageKey: _constants.SYSTEM_MESSAGES.INVALID_FILE_KEY
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
if (key.includes('\0')) {
|
|
108
|
-
throw new _common.BadRequestException({
|
|
109
|
-
message: 'Invalid file key: contains null bytes',
|
|
110
|
-
messageKey: _constants.SYSTEM_MESSAGES.INVALID_FILE_KEY
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Initialize Local File System provider with configuration
|
|
116
|
-
* @param config.basePath - Base path for file storage (default: './uploads')
|
|
117
|
-
* @param config.baseUrl - Optional base URL for generating file URLs
|
|
118
|
-
*/ async initialize(config) {
|
|
119
|
-
// Store original relative path for key generation
|
|
120
|
-
this.relativeBasePath = config?.basePath || './uploads';
|
|
121
|
-
// Resolve to absolute path for file operations
|
|
122
|
-
this.basePath = _path.resolve(this.relativeBasePath);
|
|
123
|
-
this.baseUrl = config?.baseUrl || '';
|
|
124
|
-
// Ensure base directory exists
|
|
125
|
-
await _promises.mkdir(this.basePath, {
|
|
126
|
-
recursive: true
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
async uploadFile(file, options) {
|
|
130
|
-
let processedBuffer = file.buffer;
|
|
131
|
-
let contentType = file.mimetype;
|
|
132
|
-
const fileName = `${(0, _uuid.v4)()}-${file.originalname}`;
|
|
133
|
-
// Compress image if needed
|
|
134
|
-
if (options.compress && file.mimetype.startsWith('image/')) {
|
|
135
|
-
const result = await _imagecompressorutil.ImageCompressor.compress(file.buffer, file.mimetype, {
|
|
136
|
-
maxWidth: options.maxWidth,
|
|
137
|
-
maxHeight: options.maxHeight,
|
|
138
|
-
quality: options.quality,
|
|
139
|
-
format: options.format
|
|
140
|
-
});
|
|
141
|
-
processedBuffer = result.buffer;
|
|
142
|
-
contentType = result.format;
|
|
143
|
-
}
|
|
144
|
-
// Build file path
|
|
145
|
-
const folderPath = options.folderPath ? _path.join(this.basePath, options.folderPath) : this.basePath;
|
|
146
|
-
// SECURITY: Validate path does not escape base directory
|
|
147
|
-
this.validatePathWithinBase(folderPath);
|
|
148
|
-
const filePath = _path.join(folderPath, fileName);
|
|
149
|
-
// SECURITY: Double-check final file path
|
|
150
|
-
this.validatePathWithinBase(filePath);
|
|
151
|
-
// Ensure directory exists
|
|
152
|
-
await _promises.mkdir(folderPath, {
|
|
153
|
-
recursive: true
|
|
154
|
-
});
|
|
155
|
-
// Write file to disk
|
|
156
|
-
await _promises.writeFile(filePath, processedBuffer);
|
|
157
|
-
// Generate key that includes the base path (relative to cwd)
|
|
158
|
-
// This makes the key self-contained for serving files
|
|
159
|
-
const relativeKey = _path.join(this.relativeBasePath, options.folderPath || '', fileName).replace(/\\/g, '/'); // Normalize path separators
|
|
160
|
-
return {
|
|
161
|
-
name: fileName,
|
|
162
|
-
key: relativeKey,
|
|
163
|
-
size: processedBuffer.length,
|
|
164
|
-
contentType
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
async uploadMultipleFiles(files, options) {
|
|
168
|
-
return Promise.all(files.map((file)=>this.uploadFile(file, options)));
|
|
169
|
-
}
|
|
170
|
-
async deleteFile(key) {
|
|
171
|
-
try {
|
|
172
|
-
// SECURITY: Validate key format first
|
|
173
|
-
this.validateKeyFormat(key);
|
|
174
|
-
// Key now includes the basePath, resolve from cwd
|
|
175
|
-
let filePath = _path.resolve(key);
|
|
176
|
-
// SECURITY: Validate resolved path is within base directory
|
|
177
|
-
this.validatePathWithinBase(filePath);
|
|
178
|
-
// Check if file exists at the resolved path
|
|
179
|
-
try {
|
|
180
|
-
await _promises.access(filePath);
|
|
181
|
-
} catch {
|
|
182
|
-
// Fallback: try with basePath prefix (for old keys without basePath)
|
|
183
|
-
const fallbackPath = _path.join(this.basePath, key);
|
|
184
|
-
// SECURITY: Validate fallback path as well
|
|
185
|
-
this.validatePathWithinBase(fallbackPath);
|
|
186
|
-
try {
|
|
187
|
-
await _promises.access(fallbackPath);
|
|
188
|
-
filePath = fallbackPath;
|
|
189
|
-
} catch {
|
|
190
|
-
// File doesn't exist at either location - silent return
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
await _promises.unlink(filePath);
|
|
195
|
-
} catch (error) {
|
|
196
|
-
// Re-throw security errors
|
|
197
|
-
if (error instanceof Error && error.message.includes('Invalid')) {
|
|
198
|
-
throw error;
|
|
199
|
-
}
|
|
200
|
-
// Silent failure for other errors
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
async deleteMultipleFiles(keys) {
|
|
204
|
-
await Promise.all(keys.map((key)=>this.deleteFile(key)));
|
|
205
|
-
}
|
|
206
|
-
async generatePresignedUrl(key, _expiresInSeconds) {
|
|
207
|
-
// Return public URL or relative path
|
|
208
|
-
// Note: Local storage doesn't support true presigned URLs
|
|
209
|
-
// If baseUrl is provided, return full URL with file serving endpoint, otherwise return relative path
|
|
210
|
-
if (this.baseUrl) {
|
|
211
|
-
// Construct URL with the file serving endpoint
|
|
212
|
-
const baseUrlWithoutTrailingSlash = this.baseUrl.replace(/\/$/, '');
|
|
213
|
-
return `${baseUrlWithoutTrailingSlash}/storage/upload/file/${key}`;
|
|
214
|
-
}
|
|
215
|
-
return `/storage/upload/file/${key}`;
|
|
216
|
-
}
|
|
217
|
-
async healthCheck() {
|
|
218
|
-
try {
|
|
219
|
-
await _promises.access(this.basePath);
|
|
220
|
-
return true;
|
|
221
|
-
} catch {
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
constructor(){
|
|
226
|
-
_define_property(this, "basePath", '');
|
|
227
|
-
_define_property(this, "baseUrl", '');
|
|
228
|
-
_define_property(this, "relativeBasePath", ''); // Path relative to cwd for key generation
|
|
229
|
-
}
|
|
230
|
-
};
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
const _path = /*#__PURE__*/ _interop_require_wildcard(require("path"));
|
|
6
|
-
const _localprovider = require("./local-provider");
|
|
7
|
-
const _imagecompressorutil = require("../utils/image-compressor.util");
|
|
8
|
-
function _getRequireWildcardCache(nodeInterop) {
|
|
9
|
-
if (typeof WeakMap !== "function") return null;
|
|
10
|
-
var cacheBabelInterop = new WeakMap();
|
|
11
|
-
var cacheNodeInterop = new WeakMap();
|
|
12
|
-
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
13
|
-
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
14
|
-
})(nodeInterop);
|
|
15
|
-
}
|
|
16
|
-
function _interop_require_wildcard(obj, nodeInterop) {
|
|
17
|
-
if (!nodeInterop && obj && obj.__esModule) {
|
|
18
|
-
return obj;
|
|
19
|
-
}
|
|
20
|
-
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
21
|
-
return {
|
|
22
|
-
default: obj
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
var cache = _getRequireWildcardCache(nodeInterop);
|
|
26
|
-
if (cache && cache.has(obj)) {
|
|
27
|
-
return cache.get(obj);
|
|
28
|
-
}
|
|
29
|
-
var newObj = {
|
|
30
|
-
__proto__: null
|
|
31
|
-
};
|
|
32
|
-
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
33
|
-
for(var key in obj){
|
|
34
|
-
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
35
|
-
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
36
|
-
if (desc && (desc.get || desc.set)) {
|
|
37
|
-
Object.defineProperty(newObj, key, desc);
|
|
38
|
-
} else {
|
|
39
|
-
newObj[key] = obj[key];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
newObj.default = obj;
|
|
44
|
-
if (cache) {
|
|
45
|
-
cache.set(obj, newObj);
|
|
46
|
-
}
|
|
47
|
-
return newObj;
|
|
48
|
-
}
|
|
49
|
-
const mockMkdir = jest.fn();
|
|
50
|
-
const mockWriteFile = jest.fn();
|
|
51
|
-
const mockAccess = jest.fn();
|
|
52
|
-
const mockUnlink = jest.fn();
|
|
53
|
-
jest.mock('fs/promises', ()=>({
|
|
54
|
-
mkdir: (...args)=>mockMkdir(...args),
|
|
55
|
-
writeFile: (...args)=>mockWriteFile(...args),
|
|
56
|
-
access: (...args)=>mockAccess(...args),
|
|
57
|
-
unlink: (...args)=>mockUnlink(...args)
|
|
58
|
-
}));
|
|
59
|
-
jest.mock('../utils/image-compressor.util', ()=>({
|
|
60
|
-
ImageCompressor: {
|
|
61
|
-
compress: jest.fn()
|
|
62
|
-
}
|
|
63
|
-
}));
|
|
64
|
-
function buildMulterFile(overrides = {}) {
|
|
65
|
-
return {
|
|
66
|
-
fieldname: 'file',
|
|
67
|
-
originalname: 'photo.jpg',
|
|
68
|
-
encoding: '7bit',
|
|
69
|
-
mimetype: 'image/jpeg',
|
|
70
|
-
buffer: Buffer.from('file-bytes'),
|
|
71
|
-
size: 10,
|
|
72
|
-
stream: undefined,
|
|
73
|
-
destination: '',
|
|
74
|
-
filename: '',
|
|
75
|
-
path: '',
|
|
76
|
-
...overrides
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
describe('LocalProvider', ()=>{
|
|
80
|
-
let provider;
|
|
81
|
-
beforeEach(async ()=>{
|
|
82
|
-
jest.clearAllMocks();
|
|
83
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
84
|
-
mockWriteFile.mockResolvedValue(undefined);
|
|
85
|
-
mockAccess.mockResolvedValue(undefined);
|
|
86
|
-
mockUnlink.mockResolvedValue(undefined);
|
|
87
|
-
provider = new _localprovider.LocalProvider();
|
|
88
|
-
await provider.initialize({
|
|
89
|
-
basePath: './uploads-test',
|
|
90
|
-
baseUrl: 'http://localhost:2002'
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
describe('initialize', ()=>{
|
|
94
|
-
it('resolves the base path and ensures the directory exists', async ()=>{
|
|
95
|
-
expect(mockMkdir).toHaveBeenCalledWith(_path.resolve('./uploads-test'), expect.objectContaining({
|
|
96
|
-
recursive: true
|
|
97
|
-
}));
|
|
98
|
-
});
|
|
99
|
-
it('defaults basePath to ./uploads when not provided', async ()=>{
|
|
100
|
-
mockMkdir.mockClear();
|
|
101
|
-
const defaultProvider = new _localprovider.LocalProvider();
|
|
102
|
-
await defaultProvider.initialize({});
|
|
103
|
-
expect(mockMkdir).toHaveBeenCalledWith(_path.resolve('./uploads'), expect.objectContaining({
|
|
104
|
-
recursive: true
|
|
105
|
-
}));
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
describe('uploadFile', ()=>{
|
|
109
|
-
it('writes the file to disk and returns metadata with a self-contained key', async ()=>{
|
|
110
|
-
const file = buildMulterFile();
|
|
111
|
-
const result = await provider.uploadFile(file, {});
|
|
112
|
-
expect(mockWriteFile).toHaveBeenCalled();
|
|
113
|
-
expect(result.name).toMatch(/photo\.jpg$/);
|
|
114
|
-
expect(result.key).toContain('uploads-test');
|
|
115
|
-
expect(result.size).toBe(file.buffer.length);
|
|
116
|
-
expect(result.contentType).toBe('image/jpeg');
|
|
117
|
-
});
|
|
118
|
-
it('nests the file under folderPath when provided', async ()=>{
|
|
119
|
-
const file = buildMulterFile();
|
|
120
|
-
const result = await provider.uploadFile(file, {
|
|
121
|
-
folderPath: 'avatars'
|
|
122
|
-
});
|
|
123
|
-
expect(result.key).toContain('avatars');
|
|
124
|
-
expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('avatars'), expect.objectContaining({
|
|
125
|
-
recursive: true
|
|
126
|
-
}));
|
|
127
|
-
});
|
|
128
|
-
it('compresses image files when options.compress is true', async ()=>{
|
|
129
|
-
_imagecompressorutil.ImageCompressor.compress.mockResolvedValue({
|
|
130
|
-
buffer: Buffer.from('compressed'),
|
|
131
|
-
format: 'image/webp'
|
|
132
|
-
});
|
|
133
|
-
const file = buildMulterFile();
|
|
134
|
-
const result = await provider.uploadFile(file, {
|
|
135
|
-
compress: true,
|
|
136
|
-
maxWidth: 800,
|
|
137
|
-
maxHeight: 600,
|
|
138
|
-
quality: 70
|
|
139
|
-
});
|
|
140
|
-
expect(_imagecompressorutil.ImageCompressor.compress).toHaveBeenCalledWith(file.buffer, 'image/jpeg', expect.objectContaining({
|
|
141
|
-
maxWidth: 800,
|
|
142
|
-
maxHeight: 600,
|
|
143
|
-
quality: 70
|
|
144
|
-
}));
|
|
145
|
-
expect(result.contentType).toBe('image/webp');
|
|
146
|
-
expect(result.size).toBe(Buffer.from('compressed').length);
|
|
147
|
-
});
|
|
148
|
-
it('does not compress non-image mimetypes even when compress=true', async ()=>{
|
|
149
|
-
const file = buildMulterFile({
|
|
150
|
-
mimetype: 'application/pdf',
|
|
151
|
-
originalname: 'doc.pdf'
|
|
152
|
-
});
|
|
153
|
-
await provider.uploadFile(file, {
|
|
154
|
-
compress: true
|
|
155
|
-
});
|
|
156
|
-
expect(_imagecompressorutil.ImageCompressor.compress).not.toHaveBeenCalled();
|
|
157
|
-
});
|
|
158
|
-
it('rejects a folderPath that attempts to escape the base directory via path traversal', async ()=>{
|
|
159
|
-
const file = buildMulterFile();
|
|
160
|
-
await expect(provider.uploadFile(file, {
|
|
161
|
-
folderPath: '../../../etc'
|
|
162
|
-
})).rejects.toThrow();
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
describe('uploadMultipleFiles', ()=>{
|
|
166
|
-
it('uploads all files and returns their metadata', async ()=>{
|
|
167
|
-
const files = [
|
|
168
|
-
buildMulterFile({
|
|
169
|
-
originalname: 'a.jpg'
|
|
170
|
-
}),
|
|
171
|
-
buildMulterFile({
|
|
172
|
-
originalname: 'b.jpg'
|
|
173
|
-
})
|
|
174
|
-
];
|
|
175
|
-
const results = await provider.uploadMultipleFiles(files, {});
|
|
176
|
-
expect(results).toHaveLength(2);
|
|
177
|
-
expect(mockWriteFile).toHaveBeenCalledTimes(2);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
describe('deleteFile', ()=>{
|
|
181
|
-
it('deletes a file that exists at the resolved key path', async ()=>{
|
|
182
|
-
const key = _path.join('uploads-test', 'some-uuid-photo.jpg');
|
|
183
|
-
await provider.deleteFile(key);
|
|
184
|
-
expect(mockUnlink).toHaveBeenCalled();
|
|
185
|
-
});
|
|
186
|
-
it('falls back to basePath-prefixed lookup when the direct key path does not exist', async ()=>{
|
|
187
|
-
mockAccess.mockRejectedValueOnce(new Error('ENOENT')).mockResolvedValueOnce(undefined);
|
|
188
|
-
await provider.deleteFile('uploads-test/some-uuid-photo.jpg');
|
|
189
|
-
expect(mockUnlink).toHaveBeenCalled();
|
|
190
|
-
});
|
|
191
|
-
it('silently returns (no throw, no unlink) when the file does not exist at either location', async ()=>{
|
|
192
|
-
mockAccess.mockRejectedValue(new Error('ENOENT'));
|
|
193
|
-
await expect(provider.deleteFile('uploads-test/missing-file.jpg')).resolves.toBeUndefined();
|
|
194
|
-
expect(mockUnlink).not.toHaveBeenCalled();
|
|
195
|
-
});
|
|
196
|
-
it('rejects an empty key', async ()=>{
|
|
197
|
-
await expect(provider.deleteFile('')).rejects.toThrow();
|
|
198
|
-
});
|
|
199
|
-
it('rejects a key containing a null byte', async ()=>{
|
|
200
|
-
await expect(provider.deleteFile('evil\0.txt')).rejects.toThrow();
|
|
201
|
-
});
|
|
202
|
-
it('re-throws path traversal security errors rather than silently swallowing them', async ()=>{
|
|
203
|
-
await expect(provider.deleteFile('../../../etc/passwd')).rejects.toThrow(/Invalid/);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
describe('deleteMultipleFiles', ()=>{
|
|
207
|
-
it('deletes every provided key', async ()=>{
|
|
208
|
-
await provider.deleteMultipleFiles([
|
|
209
|
-
'uploads-test/a.jpg',
|
|
210
|
-
'uploads-test/b.jpg'
|
|
211
|
-
]);
|
|
212
|
-
expect(mockUnlink).toHaveBeenCalledTimes(2);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
describe('generatePresignedUrl', ()=>{
|
|
216
|
-
it('builds a full URL using the configured baseUrl', async ()=>{
|
|
217
|
-
const url = await provider.generatePresignedUrl('uploads-test/photo.jpg');
|
|
218
|
-
expect(url).toBe('http://localhost:2002/storage/upload/file/uploads-test/photo.jpg');
|
|
219
|
-
});
|
|
220
|
-
it('returns a relative path when no baseUrl was configured', async ()=>{
|
|
221
|
-
const noUrlProvider = new _localprovider.LocalProvider();
|
|
222
|
-
await noUrlProvider.initialize({
|
|
223
|
-
basePath: './uploads-test2'
|
|
224
|
-
});
|
|
225
|
-
const url = await noUrlProvider.generatePresignedUrl('uploads-test2/photo.jpg');
|
|
226
|
-
expect(url).toBe('/storage/upload/file/uploads-test2/photo.jpg');
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
describe('healthCheck', ()=>{
|
|
230
|
-
it('returns true when the base path is accessible', async ()=>{
|
|
231
|
-
mockAccess.mockResolvedValue(undefined);
|
|
232
|
-
await expect(provider.healthCheck()).resolves.toBe(true);
|
|
233
|
-
});
|
|
234
|
-
it('returns false when the base path is not accessible', async ()=>{
|
|
235
|
-
mockAccess.mockRejectedValue(new Error('ENOENT'));
|
|
236
|
-
await expect(provider.healthCheck()).resolves.toBe(false);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Optional AWS S3 Storage Provider
|
|
3
|
-
* Requires: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
|
|
4
|
-
*/ "use strict";
|
|
5
|
-
Object.defineProperty(exports, "__esModule", {
|
|
6
|
-
value: true
|
|
7
|
-
});
|
|
8
|
-
Object.defineProperty(exports, "S3Provider", {
|
|
9
|
-
enumerable: true,
|
|
10
|
-
get: function() {
|
|
11
|
-
return S3Provider;
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
const _common = require("@nestjs/common");
|
|
15
|
-
const _constants = require("@flusys/nestjs-shared/constants");
|
|
16
|
-
const _uuid = require("uuid");
|
|
17
|
-
const _imagecompressorutil = require("../utils/image-compressor.util");
|
|
18
|
-
function _define_property(obj, key, value) {
|
|
19
|
-
if (key in obj) {
|
|
20
|
-
Object.defineProperty(obj, key, {
|
|
21
|
-
value: value,
|
|
22
|
-
enumerable: true,
|
|
23
|
-
configurable: true,
|
|
24
|
-
writable: true
|
|
25
|
-
});
|
|
26
|
-
} else {
|
|
27
|
-
obj[key] = value;
|
|
28
|
-
}
|
|
29
|
-
return obj;
|
|
30
|
-
}
|
|
31
|
-
function _getRequireWildcardCache(nodeInterop) {
|
|
32
|
-
if (typeof WeakMap !== "function") return null;
|
|
33
|
-
var cacheBabelInterop = new WeakMap();
|
|
34
|
-
var cacheNodeInterop = new WeakMap();
|
|
35
|
-
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
36
|
-
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
37
|
-
})(nodeInterop);
|
|
38
|
-
}
|
|
39
|
-
function _interop_require_wildcard(obj, nodeInterop) {
|
|
40
|
-
if (!nodeInterop && obj && obj.__esModule) {
|
|
41
|
-
return obj;
|
|
42
|
-
}
|
|
43
|
-
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
44
|
-
return {
|
|
45
|
-
default: obj
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
var cache = _getRequireWildcardCache(nodeInterop);
|
|
49
|
-
if (cache && cache.has(obj)) {
|
|
50
|
-
return cache.get(obj);
|
|
51
|
-
}
|
|
52
|
-
var newObj = {
|
|
53
|
-
__proto__: null
|
|
54
|
-
};
|
|
55
|
-
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
56
|
-
for(var key in obj){
|
|
57
|
-
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
58
|
-
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
59
|
-
if (desc && (desc.get || desc.set)) {
|
|
60
|
-
Object.defineProperty(newObj, key, desc);
|
|
61
|
-
} else {
|
|
62
|
-
newObj[key] = obj[key];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
newObj.default = obj;
|
|
67
|
-
if (cache) {
|
|
68
|
-
cache.set(obj, newObj);
|
|
69
|
-
}
|
|
70
|
-
return newObj;
|
|
71
|
-
}
|
|
72
|
-
let S3Provider = class S3Provider {
|
|
73
|
-
async initialize(config) {
|
|
74
|
-
try {
|
|
75
|
-
const { S3Client } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
|
|
76
|
-
this.region = config.region;
|
|
77
|
-
this.bucketName = config.bucket;
|
|
78
|
-
this.s3 = new S3Client({
|
|
79
|
-
region: config.region,
|
|
80
|
-
endpoint: config.endpoint,
|
|
81
|
-
credentials: config.accessKeyId && config.secretAccessKey ? {
|
|
82
|
-
accessKeyId: config.accessKeyId,
|
|
83
|
-
secretAccessKey: config.secretAccessKey
|
|
84
|
-
} : undefined
|
|
85
|
-
});
|
|
86
|
-
} catch {
|
|
87
|
-
throw new _common.InternalServerErrorException({
|
|
88
|
-
message: 'AWS SDK not found',
|
|
89
|
-
messageKey: _constants.SYSTEM_MESSAGES.SDK_NOT_INSTALLED,
|
|
90
|
-
messageVariables: {
|
|
91
|
-
sdk: '@aws-sdk/client-s3'
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async uploadFile(file, options) {
|
|
97
|
-
const { Upload } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/lib-storage")));
|
|
98
|
-
let processedBuffer = file.buffer;
|
|
99
|
-
let contentType = file.mimetype;
|
|
100
|
-
const fileName = `${(0, _uuid.v4)()}-${file.originalname}`;
|
|
101
|
-
if (options.compress && file.mimetype.startsWith('image/')) {
|
|
102
|
-
const result = await _imagecompressorutil.ImageCompressor.compress(file.buffer, file.mimetype, {
|
|
103
|
-
maxWidth: options.maxWidth,
|
|
104
|
-
maxHeight: options.maxHeight,
|
|
105
|
-
quality: options.quality,
|
|
106
|
-
format: options.format
|
|
107
|
-
});
|
|
108
|
-
processedBuffer = result.buffer;
|
|
109
|
-
contentType = result.format;
|
|
110
|
-
}
|
|
111
|
-
const key = options.folderPath ? `${options.folderPath}/${fileName}` : fileName;
|
|
112
|
-
await new Upload({
|
|
113
|
-
client: this.s3,
|
|
114
|
-
params: {
|
|
115
|
-
Bucket: this.bucketName,
|
|
116
|
-
Key: key,
|
|
117
|
-
Body: processedBuffer,
|
|
118
|
-
ContentType: contentType
|
|
119
|
-
}
|
|
120
|
-
}).done();
|
|
121
|
-
return {
|
|
122
|
-
name: fileName,
|
|
123
|
-
key,
|
|
124
|
-
size: processedBuffer.length,
|
|
125
|
-
contentType
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
async uploadMultipleFiles(files, options) {
|
|
129
|
-
return Promise.all(files.map((file)=>this.uploadFile(file, options)));
|
|
130
|
-
}
|
|
131
|
-
async deleteFile(key) {
|
|
132
|
-
const { DeleteObjectCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
|
|
133
|
-
await this.s3.send(new DeleteObjectCommand({
|
|
134
|
-
Bucket: this.bucketName,
|
|
135
|
-
Key: key
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
async deleteMultipleFiles(keys) {
|
|
139
|
-
const { DeleteObjectsCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
|
|
140
|
-
await this.s3.send(new DeleteObjectsCommand({
|
|
141
|
-
Bucket: this.bucketName,
|
|
142
|
-
Delete: {
|
|
143
|
-
Objects: keys.map((key)=>({
|
|
144
|
-
Key: key
|
|
145
|
-
}))
|
|
146
|
-
}
|
|
147
|
-
}));
|
|
148
|
-
}
|
|
149
|
-
async generatePresignedUrl(key, expiresInSeconds = 3600) {
|
|
150
|
-
const { GetObjectCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
|
|
151
|
-
const { getSignedUrl } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/s3-request-presigner")));
|
|
152
|
-
return getSignedUrl(this.s3, new GetObjectCommand({
|
|
153
|
-
Bucket: this.bucketName,
|
|
154
|
-
Key: key
|
|
155
|
-
}), {
|
|
156
|
-
expiresIn: expiresInSeconds
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
async healthCheck() {
|
|
160
|
-
try {
|
|
161
|
-
const { HeadBucketCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
|
|
162
|
-
await this.s3.send(new HeadBucketCommand({
|
|
163
|
-
Bucket: this.bucketName
|
|
164
|
-
}));
|
|
165
|
-
return true;
|
|
166
|
-
} catch {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
constructor(){
|
|
171
|
-
_define_property(this, "s3", void 0);
|
|
172
|
-
_define_property(this, "bucketName", '');
|
|
173
|
-
_define_property(this, "region", '');
|
|
174
|
-
}
|
|
175
|
-
};
|