@avleon/core 0.0.40 → 0.0.42-rc0.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/README.md +37 -5
- package/dist/cache.test.d.ts +1 -0
- package/dist/cache.test.js +36 -0
- package/dist/controller.d.ts +2 -0
- package/dist/controller.js +13 -0
- package/dist/controller.test.d.ts +1 -0
- package/dist/controller.test.js +111 -0
- package/dist/environment-variables.test.d.ts +1 -0
- package/dist/environment-variables.test.js +70 -0
- package/dist/exceptions/http-exceptions.d.ts +1 -0
- package/dist/exceptions/http-exceptions.js +3 -1
- package/dist/file-storage.d.ts +44 -9
- package/dist/file-storage.js +209 -59
- package/dist/file-storage.test.d.ts +1 -0
- package/dist/file-storage.test.js +104 -0
- package/dist/helpers.test.d.ts +1 -0
- package/dist/helpers.test.js +95 -0
- package/dist/icore.d.ts +7 -5
- package/dist/icore.js +191 -69
- package/dist/icore.test.d.ts +1 -0
- package/dist/icore.test.js +14 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +8 -1
- package/dist/kenx-provider.test.d.ts +1 -0
- package/dist/kenx-provider.test.js +36 -0
- package/dist/logger.test.d.ts +1 -0
- package/dist/logger.test.js +42 -0
- package/dist/middleware.test.d.ts +1 -0
- package/dist/middleware.test.js +121 -0
- package/dist/multipart.test.d.ts +1 -0
- package/dist/multipart.test.js +87 -0
- package/dist/openapi.test.d.ts +1 -0
- package/dist/openapi.test.js +111 -0
- package/dist/params.test.d.ts +1 -0
- package/dist/params.test.js +83 -0
- package/dist/queue.test.d.ts +1 -0
- package/dist/queue.test.js +79 -0
- package/dist/route-methods.test.d.ts +1 -0
- package/dist/route-methods.test.js +129 -0
- package/dist/swagger-schema.d.ts +42 -0
- package/dist/swagger-schema.js +331 -58
- package/dist/swagger-schema.test.d.ts +1 -0
- package/dist/swagger-schema.test.js +105 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.js +2 -0
- package/dist/validation.test.d.ts +1 -0
- package/dist/validation.test.js +61 -0
- package/dist/websocket.test.d.ts +1 -0
- package/dist/websocket.test.js +27 -0
- package/package.json +11 -9
package/dist/file-storage.js
CHANGED
|
@@ -38,111 +38,226 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
41
44
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
45
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
46
|
};
|
|
44
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
48
|
exports.FileStorage = void 0;
|
|
46
|
-
const fs_1 =
|
|
49
|
+
const fs_1 = __importStar(require("fs"));
|
|
47
50
|
const path_1 = __importDefault(require("path"));
|
|
48
51
|
const promises_1 = require("stream/promises");
|
|
49
|
-
const http_exceptions_1 = require("./exceptions/http-exceptions");
|
|
50
52
|
const decorators_1 = require("./decorators");
|
|
51
53
|
const system_exception_1 = require("./exceptions/system-exception");
|
|
54
|
+
const http_exceptions_1 = require("./exceptions/http-exceptions");
|
|
52
55
|
let FileStorage = class FileStorage {
|
|
53
56
|
constructor() {
|
|
54
57
|
this.transformOptions = null;
|
|
58
|
+
this.maxFileSize = 50 * 1024 * 1024; // 50MB default
|
|
59
|
+
this.baseDir = path_1.default.join(process.cwd(), "public");
|
|
60
|
+
this.ensureDirectoryExists(this.baseDir);
|
|
55
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Set transformation options for the next save operation
|
|
64
|
+
*/
|
|
56
65
|
transform(options) {
|
|
57
66
|
this.transformOptions = options;
|
|
58
67
|
return this;
|
|
59
68
|
}
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
async getUploadFile(fliePath) {
|
|
70
|
+
const f = await fs_1.default.promises.readFile(path_1.default.join(this.baseDir, fliePath));
|
|
71
|
+
return f;
|
|
72
|
+
}
|
|
73
|
+
async download(filepath) {
|
|
74
|
+
const filename = filepath
|
|
75
|
+
.toString()
|
|
76
|
+
.split(/[\/\\]/)
|
|
77
|
+
.pop() || "file";
|
|
78
|
+
const s = (0, fs_1.createReadStream)(filepath);
|
|
79
|
+
return {
|
|
80
|
+
download: true,
|
|
81
|
+
stream: s,
|
|
82
|
+
filename,
|
|
83
|
+
};
|
|
62
84
|
}
|
|
85
|
+
async downloadAs(filepath, filename) {
|
|
86
|
+
const s = (0, fs_1.createReadStream)(filepath);
|
|
87
|
+
return {
|
|
88
|
+
download: true,
|
|
89
|
+
stream: s,
|
|
90
|
+
filename,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Save a single file with optional transformations
|
|
95
|
+
*/
|
|
63
96
|
async save(f, options) {
|
|
64
|
-
|
|
65
|
-
|
|
97
|
+
var _a;
|
|
98
|
+
const opts = {
|
|
99
|
+
overwrite: (_a = options === null || options === void 0 ? void 0 : options.overwrite) !== null && _a !== void 0 ? _a : true,
|
|
100
|
+
to: options === null || options === void 0 ? void 0 : options.to,
|
|
101
|
+
saveAs: options === null || options === void 0 ? void 0 : options.saveAs,
|
|
66
102
|
};
|
|
103
|
+
if (f.type !== "file") {
|
|
104
|
+
throw new system_exception_1.SystemUseError("Invalid file type");
|
|
105
|
+
}
|
|
67
106
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
const filename = opts.saveAs || f.filename;
|
|
108
|
+
this.validateFilename(filename);
|
|
109
|
+
const uploadDir = opts.to
|
|
110
|
+
? path_1.default.join(this.baseDir, opts.to)
|
|
111
|
+
: this.baseDir;
|
|
112
|
+
const fullPath = path_1.default.join(uploadDir, filename);
|
|
113
|
+
if (!fullPath.startsWith(this.baseDir)) {
|
|
114
|
+
throw new system_exception_1.SystemUseError("Invalid file path");
|
|
115
|
+
}
|
|
116
|
+
// Check if file exists
|
|
117
|
+
if (!opts.overwrite && this.isFileExists(fullPath)) {
|
|
118
|
+
throw new system_exception_1.SystemUseError("File already exists");
|
|
119
|
+
}
|
|
120
|
+
await this.ensureDirectoryExists(uploadDir);
|
|
121
|
+
let sourceStream;
|
|
122
|
+
const savedFile = f;
|
|
123
|
+
if (savedFile.filepath && !f.file) {
|
|
124
|
+
sourceStream = fs_1.default.createReadStream(savedFile.filepath);
|
|
75
125
|
}
|
|
126
|
+
else if (f.file) {
|
|
127
|
+
sourceStream = f.file;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
throw new system_exception_1.SystemUseError("No file stream or filepath available");
|
|
131
|
+
}
|
|
132
|
+
if (this.transformOptions && this.isImageFile(filename)) {
|
|
133
|
+
await this.processImage(sourceStream, fullPath);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
await (0, promises_1.pipeline)(sourceStream, fs_1.default.createWriteStream(fullPath));
|
|
137
|
+
}
|
|
138
|
+
if (savedFile.filepath && fs_1.default.existsSync(savedFile.filepath)) {
|
|
139
|
+
this.removeFileSync(savedFile.filepath);
|
|
140
|
+
}
|
|
141
|
+
if (opts.saveAs) {
|
|
142
|
+
f.filename = opts.saveAs;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
uploadPath: (options === null || options === void 0 ? void 0 : options.to) ? '/uploads/' + options.to + "/" + f.filename : '/uploads/' + f.filename,
|
|
146
|
+
staticPath: (options === null || options === void 0 ? void 0 : options.to) ? '/static/' + options.to + "/" + f.filename : '/static/' + f.filename
|
|
147
|
+
};
|
|
76
148
|
}
|
|
77
149
|
catch (err) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
throw new system_exception_1.SystemUseError("
|
|
150
|
+
if (err instanceof system_exception_1.SystemUseError ||
|
|
151
|
+
err instanceof http_exceptions_1.InternalErrorException) {
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
console.error("File save error:", err);
|
|
155
|
+
throw new system_exception_1.SystemUseError("Failed to upload file");
|
|
84
156
|
}
|
|
85
|
-
return fs_1.default.unlinkSync(path_1.default.join(process.cwd(), "public/" + filepath));
|
|
86
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Save multiple files
|
|
160
|
+
*/
|
|
87
161
|
async saveAll(files, options) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
162
|
+
var _a;
|
|
163
|
+
if (!files || files.length === 0) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
const opts = {
|
|
167
|
+
overwrite: (_a = options === null || options === void 0 ? void 0 : options.overwrite) !== null && _a !== void 0 ? _a : true,
|
|
168
|
+
to: options === null || options === void 0 ? void 0 : options.to,
|
|
169
|
+
};
|
|
170
|
+
const uploadDir = opts.to ? path_1.default.join(this.baseDir, opts.to) : this.baseDir;
|
|
171
|
+
await this.ensureDirectoryExists(uploadDir);
|
|
172
|
+
const results = [];
|
|
173
|
+
for (const f of files) {
|
|
174
|
+
try {
|
|
175
|
+
this.validateFilename(f.filename);
|
|
176
|
+
const fullPath = path_1.default.join(uploadDir, f.filename);
|
|
177
|
+
if (!fullPath.startsWith(this.baseDir)) {
|
|
178
|
+
throw new system_exception_1.SystemUseError(`Invalid file path for ${f.filename}`);
|
|
179
|
+
}
|
|
180
|
+
if (!opts.overwrite && this.isFileExists(fullPath)) {
|
|
181
|
+
throw new system_exception_1.SystemUseError(`File ${f.filename} already exists`);
|
|
96
182
|
}
|
|
97
|
-
const fname = path_1.default.join(process.cwd(), `${uploadPath}/${f.filename}`);
|
|
98
|
-
await this.ensureDirectoryExists(fname);
|
|
99
183
|
if (f.file) {
|
|
100
|
-
|
|
184
|
+
if (this.transformOptions && this.isImageFile(f.filename)) {
|
|
185
|
+
await this.processImage(f.file, fullPath);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
await (0, promises_1.pipeline)(f.file, fs_1.default.createWriteStream(fullPath));
|
|
189
|
+
}
|
|
101
190
|
}
|
|
102
191
|
else {
|
|
103
192
|
const fp = f;
|
|
104
|
-
|
|
105
|
-
|
|
193
|
+
if (!fp.filepath) {
|
|
194
|
+
throw new system_exception_1.SystemUseError(`No filepath for ${f.filename}`);
|
|
195
|
+
}
|
|
196
|
+
if (this.transformOptions && this.isImageFile(f.filename)) {
|
|
197
|
+
await this.processImage(fs_1.default.createReadStream(fp.filepath), fullPath);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
await (0, promises_1.pipeline)(fs_1.default.createReadStream(fp.filepath), fs_1.default.createWriteStream(fullPath));
|
|
201
|
+
}
|
|
202
|
+
this.removeFileSync(fp.filepath);
|
|
106
203
|
}
|
|
204
|
+
results.push({
|
|
205
|
+
uploadPath: (options === null || options === void 0 ? void 0 : options.to) ? '/uploads/' + options.to + "/" + f.filename : '/uploads/' + f.filename,
|
|
206
|
+
staticPath: (options === null || options === void 0 ? void 0 : options.to) ? '/static/' + options.to + "/" + f.filename : '/static/' + f.filename
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
console.error(`Failed to save file ${f.filename}:`, error);
|
|
211
|
+
throw new system_exception_1.SystemUseError(`Failed to upload file ${f.filename}`);
|
|
107
212
|
}
|
|
108
|
-
|
|
213
|
+
}
|
|
214
|
+
return results;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Remove a file from storage
|
|
218
|
+
*/
|
|
219
|
+
async remove(filepath) {
|
|
220
|
+
const fullPath = path_1.default.join(this.baseDir, filepath);
|
|
221
|
+
// Security check
|
|
222
|
+
if (!fullPath.startsWith(this.baseDir)) {
|
|
223
|
+
throw new system_exception_1.SystemUseError("Invalid file path");
|
|
224
|
+
}
|
|
225
|
+
if (!this.isFileExists(fullPath)) {
|
|
226
|
+
throw new system_exception_1.SystemUseError("File doesn't exist");
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
fs_1.default.unlinkSync(fullPath);
|
|
109
230
|
}
|
|
110
231
|
catch (error) {
|
|
111
|
-
console.error(error);
|
|
112
|
-
throw new system_exception_1.SystemUseError("
|
|
232
|
+
console.error("File removal error:", error);
|
|
233
|
+
throw new system_exception_1.SystemUseError("Failed to remove file");
|
|
113
234
|
}
|
|
114
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Process image with transformations using sharp
|
|
238
|
+
*/
|
|
115
239
|
async processImage(fileStream, outputPath) {
|
|
116
240
|
var _a, _b;
|
|
117
241
|
try {
|
|
118
|
-
const sharp = await Promise.resolve().then(() => __importStar(require("sharp")));
|
|
242
|
+
const sharp = await Promise.resolve().then(() => __importStar(require("sharp")));
|
|
119
243
|
let sharpPipeline = sharp.default();
|
|
120
244
|
if ((_a = this.transformOptions) === null || _a === void 0 ? void 0 : _a.resize) {
|
|
121
|
-
sharpPipeline = sharpPipeline.resize(this.transformOptions.resize.width, this.transformOptions.resize.height);
|
|
245
|
+
sharpPipeline = sharpPipeline.resize(this.transformOptions.resize.width, this.transformOptions.resize.height, { fit: "inside", withoutEnlargement: true });
|
|
122
246
|
}
|
|
123
247
|
if ((_b = this.transformOptions) === null || _b === void 0 ? void 0 : _b.format) {
|
|
248
|
+
const quality = this.transformOptions.quality || 80;
|
|
124
249
|
switch (this.transformOptions.format) {
|
|
125
250
|
case "jpeg":
|
|
126
|
-
sharpPipeline = sharpPipeline.jpeg({
|
|
127
|
-
quality: this.transformOptions.quality || 80,
|
|
128
|
-
});
|
|
251
|
+
sharpPipeline = sharpPipeline.jpeg({ quality });
|
|
129
252
|
break;
|
|
130
253
|
case "png":
|
|
131
|
-
sharpPipeline = sharpPipeline.png({
|
|
132
|
-
quality: this.transformOptions.quality || 80,
|
|
133
|
-
});
|
|
254
|
+
sharpPipeline = sharpPipeline.png({ quality });
|
|
134
255
|
break;
|
|
135
256
|
case "webp":
|
|
136
|
-
sharpPipeline = sharpPipeline.webp({
|
|
137
|
-
quality: this.transformOptions.quality || 80,
|
|
138
|
-
});
|
|
257
|
+
sharpPipeline = sharpPipeline.webp({ quality });
|
|
139
258
|
break;
|
|
140
259
|
case "avif":
|
|
141
|
-
sharpPipeline = sharpPipeline.avif({
|
|
142
|
-
quality: this.transformOptions.quality || 80,
|
|
143
|
-
});
|
|
144
|
-
break;
|
|
145
|
-
default:
|
|
260
|
+
sharpPipeline = sharpPipeline.avif({ quality });
|
|
146
261
|
break;
|
|
147
262
|
}
|
|
148
263
|
}
|
|
@@ -154,20 +269,55 @@ let FileStorage = class FileStorage {
|
|
|
154
269
|
throw new http_exceptions_1.InternalErrorException("sharp module not found. Please install sharp to use image transformations.");
|
|
155
270
|
}
|
|
156
271
|
console.error("Image processing failed:", error);
|
|
157
|
-
throw new http_exceptions_1.InternalErrorException("Image processing failed
|
|
272
|
+
throw new http_exceptions_1.InternalErrorException("Image processing failed");
|
|
158
273
|
}
|
|
159
274
|
finally {
|
|
160
|
-
this.transformOptions = null;
|
|
275
|
+
this.transformOptions = null;
|
|
161
276
|
}
|
|
162
277
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Helper methods
|
|
280
|
+
*/
|
|
281
|
+
isFileExists(fpath) {
|
|
282
|
+
return fs_1.default.existsSync(fpath);
|
|
283
|
+
}
|
|
284
|
+
async ensureDirectoryExists(dirPath) {
|
|
285
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
286
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
removeFileSync(filepath) {
|
|
290
|
+
try {
|
|
291
|
+
if (fs_1.default.existsSync(filepath)) {
|
|
292
|
+
fs_1.default.unlinkSync(filepath);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
console.error("Failed to remove temp file:", error);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
isImageFile(filename) {
|
|
300
|
+
const ext = path_1.default.extname(filename).toLowerCase();
|
|
301
|
+
return [".jpg", ".jpeg", ".png", ".webp", ".avif", ".gif", ".bmp"].includes(ext);
|
|
302
|
+
}
|
|
303
|
+
validateFilename(filename) {
|
|
304
|
+
if (!filename || filename.trim() === "") {
|
|
305
|
+
throw new system_exception_1.SystemUseError("Invalid filename");
|
|
306
|
+
}
|
|
307
|
+
// Check for path traversal attempts
|
|
308
|
+
if (filename.includes("..") ||
|
|
309
|
+
filename.includes("/") ||
|
|
310
|
+
filename.includes("\\")) {
|
|
311
|
+
throw new system_exception_1.SystemUseError("Invalid filename: path traversal detected");
|
|
312
|
+
}
|
|
313
|
+
// Check for null bytes
|
|
314
|
+
if (filename.includes("\0")) {
|
|
315
|
+
throw new system_exception_1.SystemUseError("Invalid filename: null byte detected");
|
|
167
316
|
}
|
|
168
317
|
}
|
|
169
318
|
};
|
|
170
319
|
exports.FileStorage = FileStorage;
|
|
171
320
|
exports.FileStorage = FileStorage = __decorate([
|
|
172
|
-
decorators_1.AppService
|
|
321
|
+
decorators_1.AppService,
|
|
322
|
+
__metadata("design:paramtypes", [])
|
|
173
323
|
], FileStorage);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const stream_1 = require("stream");
|
|
8
|
+
const file_storage_1 = require("./file-storage");
|
|
9
|
+
const system_exception_1 = require("./exceptions/system-exception");
|
|
10
|
+
const promises_1 = require("stream/promises");
|
|
11
|
+
jest.mock("fs");
|
|
12
|
+
jest.mock("stream/promises", () => ({
|
|
13
|
+
pipeline: jest.fn((...args) => Promise.resolve()),
|
|
14
|
+
}));
|
|
15
|
+
const mockFs = fs_1.default;
|
|
16
|
+
describe("FileStorage", () => {
|
|
17
|
+
let fileStorage;
|
|
18
|
+
const testFile = {
|
|
19
|
+
type: "file",
|
|
20
|
+
filename: "test.txt",
|
|
21
|
+
file: new stream_1.Readable({ read() { } }),
|
|
22
|
+
};
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
fileStorage = new file_storage_1.FileStorage();
|
|
26
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
27
|
+
mockFs.createWriteStream.mockReturnValue({});
|
|
28
|
+
mockFs.createReadStream.mockReturnValue({});
|
|
29
|
+
mockFs.unlinkSync.mockImplementation(() => { });
|
|
30
|
+
mockFs.mkdirSync.mockImplementation(() => undefined);
|
|
31
|
+
});
|
|
32
|
+
describe("save", () => {
|
|
33
|
+
it("should save a file successfully", async () => {
|
|
34
|
+
await expect(fileStorage.save(testFile)).resolves.toEqual(testFile);
|
|
35
|
+
});
|
|
36
|
+
it("should throw SystemUseError if file exists and overwrite is false", async () => {
|
|
37
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
38
|
+
await expect(fileStorage.save(testFile, { overwrite: false })).rejects.toThrow(system_exception_1.SystemUseError);
|
|
39
|
+
});
|
|
40
|
+
it("should throw SystemUseError on pipeline error", async () => {
|
|
41
|
+
promises_1.pipeline.mockImplementationOnce(() => {
|
|
42
|
+
throw new Error("Pipeline failed");
|
|
43
|
+
});
|
|
44
|
+
await expect(fileStorage.save(testFile)).rejects.toThrow(system_exception_1.SystemUseError);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("remove", () => {
|
|
48
|
+
it("should remove a file successfully", async () => {
|
|
49
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
50
|
+
await expect(fileStorage.remove("test.txt")).resolves.toBeUndefined();
|
|
51
|
+
expect(mockFs.unlinkSync).toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
it("should throw SystemUseError if file does not exist", async () => {
|
|
54
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
55
|
+
await expect(fileStorage.remove("test.txt")).rejects.toThrow(system_exception_1.SystemUseError);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe("saveAll", () => {
|
|
59
|
+
it("should save multiple files successfully", async () => {
|
|
60
|
+
const files = [
|
|
61
|
+
{ ...testFile, filename: "a.txt" },
|
|
62
|
+
{ ...testFile, filename: "b.txt" },
|
|
63
|
+
];
|
|
64
|
+
await expect(fileStorage.saveAll(files)).resolves.toEqual(files);
|
|
65
|
+
});
|
|
66
|
+
it("should throw SystemUseError on error", async () => {
|
|
67
|
+
promises_1.pipeline.mockImplementationOnce(() => {
|
|
68
|
+
throw new Error("Pipeline failed");
|
|
69
|
+
});
|
|
70
|
+
await expect(fileStorage.saveAll([testFile])).rejects.toThrow(system_exception_1.SystemUseError);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("transform", () => {
|
|
74
|
+
it("should set transform options and return itself", () => {
|
|
75
|
+
const options = { resize: { width: 100, height: 100 }, format: "jpeg" };
|
|
76
|
+
const result = fileStorage.transform(options);
|
|
77
|
+
expect(result).toBe(fileStorage);
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
expect(fileStorage.transformOptions).toEqual(options);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
// describe("processImage", () => {
|
|
83
|
+
// it("should throw InternalErrorException if sharp is not installed", async () => {
|
|
84
|
+
// jest.resetModules();
|
|
85
|
+
// const fileStorage = new (require("./file-storage").FileStorage)();
|
|
86
|
+
// fileStorage.transform({ format: "jpeg" });
|
|
87
|
+
// await expect(
|
|
88
|
+
// fileStorage["processImage"](new Readable({ read() {} }), "output.jpg"),
|
|
89
|
+
// ).rejects.toThrow(InternalErrorException);
|
|
90
|
+
// });
|
|
91
|
+
// });
|
|
92
|
+
describe("ensureDirectoryExists", () => {
|
|
93
|
+
it("should create directory if not exists", async () => {
|
|
94
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
95
|
+
await fileStorage["ensureDirectoryExists"]("public/test.txt");
|
|
96
|
+
expect(mockFs.mkdirSync).toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
it("should not create directory if exists", async () => {
|
|
99
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
100
|
+
await fileStorage["ensureDirectoryExists"]("public/test.txt");
|
|
101
|
+
expect(mockFs.mkdirSync).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const helpers_1 = require("./helpers");
|
|
4
|
+
describe("helpers.ts", () => {
|
|
5
|
+
describe("formatUrl", () => {
|
|
6
|
+
it("should format URLs correctly", () => {
|
|
7
|
+
expect((0, helpers_1.formatUrl)("test")).toBe("/test");
|
|
8
|
+
expect((0, helpers_1.formatUrl)("/test")).toBe("/test");
|
|
9
|
+
expect((0, helpers_1.formatUrl)("///test//")).toBe("/test");
|
|
10
|
+
expect(() => (0, helpers_1.formatUrl)(123)).toThrow();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe("parsedPath", () => {
|
|
14
|
+
it("should ensure path starts with /", () => {
|
|
15
|
+
expect((0, helpers_1.parsedPath)("abc")).toBe("/abc");
|
|
16
|
+
expect((0, helpers_1.parsedPath)("/abc")).toBe("/abc");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe("findDuplicates", () => {
|
|
20
|
+
it("should find duplicates in array", () => {
|
|
21
|
+
expect((0, helpers_1.findDuplicates)(["a", "b", "a", "c", "b"])).toEqual(["a", "b"]);
|
|
22
|
+
expect((0, helpers_1.findDuplicates)(["a", "b", "c"])).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe("getDataType", () => {
|
|
26
|
+
it("should return correct data type", () => {
|
|
27
|
+
expect((0, helpers_1.getDataType)(String)).toBe("string");
|
|
28
|
+
expect((0, helpers_1.getDataType)(Number)).toBe("number");
|
|
29
|
+
expect((0, helpers_1.getDataType)(Boolean)).toBe("boolean");
|
|
30
|
+
expect((0, helpers_1.getDataType)(Object)).toBe("object");
|
|
31
|
+
expect((0, helpers_1.getDataType)(Array)).toBe(Array);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe("isValidType", () => {
|
|
35
|
+
it("should validate types correctly", () => {
|
|
36
|
+
expect((0, helpers_1.isValidType)("abc", String)).toBe(true);
|
|
37
|
+
expect((0, helpers_1.isValidType)(123, Number)).toBe(true);
|
|
38
|
+
expect((0, helpers_1.isValidType)(true, Boolean)).toBe(true);
|
|
39
|
+
expect((0, helpers_1.isValidType)({}, Object)).toBe(true);
|
|
40
|
+
expect((0, helpers_1.isValidType)(undefined, String)).toBe(true);
|
|
41
|
+
expect((0, helpers_1.isValidType)(null, Number)).toBe(true);
|
|
42
|
+
expect((0, helpers_1.isValidType)("123", Number)).toBe(true);
|
|
43
|
+
expect((0, helpers_1.isValidType)("abc", Number)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("isValidJsonString", () => {
|
|
47
|
+
it("should validate JSON strings", () => {
|
|
48
|
+
expect((0, helpers_1.isValidJsonString)('{"a":1}')).toEqual({ a: 1 });
|
|
49
|
+
expect((0, helpers_1.isValidJsonString)("not json")).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe("jsonToJs", () => {
|
|
53
|
+
it("should parse JSON to JS object", () => {
|
|
54
|
+
expect((0, helpers_1.jsonToJs)('{"a":1}')).toEqual({ a: 1 });
|
|
55
|
+
expect((0, helpers_1.jsonToJs)("bad json")).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// describe("normalizePath", () => {
|
|
59
|
+
// it("should normalize paths", () => {
|
|
60
|
+
// expect(normalizePath("api", "v1")).toBe("/api/v1");
|
|
61
|
+
// expect(normalizePath("/", "/")).toBe("/");
|
|
62
|
+
// expect(normalizePath("api/", "/v1/")).toBe("/api/v1");
|
|
63
|
+
// });
|
|
64
|
+
// });
|
|
65
|
+
describe("extrctParamFromUrl", () => {
|
|
66
|
+
it("should extract params from URL", () => {
|
|
67
|
+
expect((0, helpers_1.extrctParamFromUrl)("/user/:id/:name")).toEqual([
|
|
68
|
+
{ key: "id", required: true },
|
|
69
|
+
{ key: "name", required: true },
|
|
70
|
+
]);
|
|
71
|
+
expect((0, helpers_1.extrctParamFromUrl)("/user/?:id")).toEqual([
|
|
72
|
+
{ key: "id", required: false },
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("pick", () => {
|
|
77
|
+
it("should pick properties from object", () => {
|
|
78
|
+
const obj = { a: 1, b: { c: 2, d: 3 }, e: 4 };
|
|
79
|
+
expect((0, helpers_1.pick)(obj, ["a"])).toEqual({ a: 1 });
|
|
80
|
+
expect((0, helpers_1.pick)(obj, ["b.c"])).toEqual({ b: { c: 2 } });
|
|
81
|
+
expect((0, helpers_1.pick)(obj, ["b.d", "e"])).toEqual({ b: { d: 3 }, e: 4 });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe("exclude", () => {
|
|
85
|
+
it("should exclude properties from object", () => {
|
|
86
|
+
const obj = { a: 1, b: { c: 2, d: 3 }, e: 4 };
|
|
87
|
+
expect((0, helpers_1.exclude)(obj, ["a"])).toEqual({ b: { c: 2, d: 3 }, e: 4 });
|
|
88
|
+
expect((0, helpers_1.exclude)(obj, ["b.c"])).toEqual({ a: 1, b: { d: 3 }, e: 4 });
|
|
89
|
+
expect((0, helpers_1.exclude)([obj, obj], ["e"])).toEqual([
|
|
90
|
+
{ a: 1, b: { c: 2, d: 3 } },
|
|
91
|
+
{ a: 1, b: { c: 2, d: 3 } },
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
package/dist/icore.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @email xtrinsic96@gmail.com
|
|
5
5
|
* @url https://github.com/xtareq
|
|
6
6
|
*/
|
|
7
|
-
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction, InjectOptions, LightMyRequestResponse } from "fastify";
|
|
7
|
+
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction, InjectOptions, LightMyRequestResponse, FastifyLoggerOptions } from "fastify";
|
|
8
8
|
import { Constructable } from "typedi";
|
|
9
9
|
import { Constructor } from "./helpers";
|
|
10
10
|
import { PathLike } from "fs";
|
|
@@ -62,6 +62,7 @@ export interface ParamMetaFilesOptions {
|
|
|
62
62
|
fieldName: string;
|
|
63
63
|
}
|
|
64
64
|
export interface MethodParamMeta {
|
|
65
|
+
request: any[];
|
|
65
66
|
params: ParamMetaOptions[];
|
|
66
67
|
query: ParamMetaOptions[];
|
|
67
68
|
body: ParamMetaOptions[];
|
|
@@ -141,6 +142,7 @@ export declare class AvleonApplication {
|
|
|
141
142
|
private constructor();
|
|
142
143
|
static getApp(): AvleonApplication;
|
|
143
144
|
static getInternalApp(buildOptions: any): AvleonApplication;
|
|
145
|
+
useLogger<T = FastifyLoggerOptions>(corsOptions?: ConfigInput<T>): void;
|
|
144
146
|
isDevelopment(): boolean;
|
|
145
147
|
private initSwagger;
|
|
146
148
|
private _isConfigClass;
|
|
@@ -164,10 +166,10 @@ export declare class AvleonApplication {
|
|
|
164
166
|
*/
|
|
165
167
|
private buildController;
|
|
166
168
|
/**
|
|
167
|
-
*
|
|
168
|
-
* @param req
|
|
169
|
-
* @param meta
|
|
170
|
-
* @returns
|
|
169
|
+
* Maps request data to controller method arguments based on decorators
|
|
170
|
+
* @param req - The incoming request object
|
|
171
|
+
* @param meta - Metadata about method parameters
|
|
172
|
+
* @returns Array of arguments to pass to the controller method
|
|
171
173
|
*/
|
|
172
174
|
private _mapArgs;
|
|
173
175
|
/**
|