@avleon/core 0.0.20 → 0.0.25
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 +559 -2
- package/dist/application.d.ts +47 -0
- package/dist/application.js +50 -0
- package/dist/cache.d.ts +12 -0
- package/dist/cache.js +78 -0
- package/dist/collection.js +3 -2
- package/dist/container.d.ts +1 -0
- package/dist/container.js +2 -1
- package/dist/environment-variables.js +1 -1
- package/dist/file-storage.js +0 -128
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.js +41 -4
- package/dist/icore.d.ts +93 -42
- package/dist/icore.js +319 -251
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -2
- package/dist/middleware.js +0 -8
- package/dist/multipart.js +4 -1
- package/dist/openapi.d.ts +37 -37
- package/dist/params.js +2 -2
- package/dist/response.js +6 -2
- package/dist/route-methods.js +1 -1
- package/dist/swagger-schema.js +4 -1
- package/dist/testing.js +6 -3
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/optional-require.d.ts +8 -0
- package/dist/utils/optional-require.js +70 -0
- package/dist/validation.d.ts +4 -1
- package/dist/validation.js +10 -5
- package/package.json +26 -9
- package/src/application.ts +125 -0
- package/src/authentication.ts +16 -0
- package/src/cache.ts +91 -0
- package/src/collection.ts +254 -0
- package/src/config.ts +42 -0
- package/src/constants.ts +1 -0
- package/src/container.ts +54 -0
- package/src/controller.ts +127 -0
- package/src/decorators.ts +27 -0
- package/src/environment-variables.ts +46 -0
- package/src/exceptions/http-exceptions.ts +86 -0
- package/src/exceptions/index.ts +1 -0
- package/src/exceptions/system-exception.ts +34 -0
- package/src/file-storage.ts +206 -0
- package/src/helpers.ts +328 -0
- package/src/icore.ts +1140 -0
- package/src/index.ts +30 -0
- package/src/logger.ts +72 -0
- package/src/map-types.ts +159 -0
- package/src/middleware.ts +98 -0
- package/src/multipart.ts +116 -0
- package/src/openapi.ts +372 -0
- package/src/params.ts +111 -0
- package/src/queue.ts +126 -0
- package/src/response.ts +117 -0
- package/src/results.ts +30 -0
- package/src/route-methods.ts +186 -0
- package/src/swagger-schema.ts +213 -0
- package/src/testing.ts +220 -0
- package/src/types/app-builder.interface.ts +19 -0
- package/src/types/application.interface.ts +9 -0
- package/src/utils/hash.ts +5 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/optional-require.ts +50 -0
- package/src/validation.ts +156 -0
- package/src/validator-extend.ts +25 -0
- package/dist/classToOpenapi.d.ts +0 -0
- package/dist/classToOpenapi.js +0 -1
- package/dist/render.d.ts +0 -1
- package/dist/render.js +0 -8
- package/jest.config.ts +0 -9
- package/tsconfig.json +0 -25
- /package/dist/{security.d.ts → utils/hash.d.ts} +0 -0
- /package/dist/{security.js → utils/hash.js} +0 -0
package/dist/icore.js
CHANGED
|
@@ -32,29 +32,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
36
|
-
var t = {};
|
|
37
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
38
|
-
t[p] = s[p];
|
|
39
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
40
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
41
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
42
|
-
t[p[i]] = s[p[i]];
|
|
43
|
-
}
|
|
44
|
-
return t;
|
|
45
|
-
};
|
|
46
|
-
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
47
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
48
|
-
var m = o[Symbol.asyncIterator], i;
|
|
49
|
-
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
50
|
-
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
51
|
-
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
52
|
-
};
|
|
53
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
54
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
55
37
|
};
|
|
56
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
-
exports.
|
|
39
|
+
exports.Avleon = exports.AvleonTest = exports.AvleonApplication = void 0;
|
|
58
40
|
/**
|
|
59
41
|
* @copyright 2024
|
|
60
42
|
* @author Tareq Hossain
|
|
@@ -68,13 +50,14 @@ const path_1 = __importDefault(require("path"));
|
|
|
68
50
|
const container_1 = __importStar(require("./container"));
|
|
69
51
|
const helpers_1 = require("./helpers");
|
|
70
52
|
const system_exception_1 = require("./exceptions/system-exception");
|
|
71
|
-
const fs_1 = require("fs");
|
|
72
53
|
const exceptions_1 = require("./exceptions");
|
|
73
54
|
const swagger_1 = __importDefault(require("@fastify/swagger"));
|
|
74
55
|
const config_1 = require("./config");
|
|
75
56
|
const environment_variables_1 = require("./environment-variables");
|
|
76
57
|
const cors_1 = __importDefault(require("@fastify/cors"));
|
|
77
58
|
const multipart_1 = __importDefault(require("@fastify/multipart"));
|
|
59
|
+
const validation_1 = require("./validation");
|
|
60
|
+
const utils_1 = require("./utils");
|
|
78
61
|
const isTsNode = process.env.TS_NODE_DEV ||
|
|
79
62
|
process.env.TS_NODE_PROJECT ||
|
|
80
63
|
process[Symbol.for("ts-node.register.instance")];
|
|
@@ -93,12 +76,21 @@ class AvleonApplication {
|
|
|
93
76
|
this.controllers = [];
|
|
94
77
|
this.authorizeMiddleware = undefined;
|
|
95
78
|
this.dataSource = undefined;
|
|
79
|
+
this.isMapFeatures = false;
|
|
80
|
+
this.registerControllerAuto = false;
|
|
81
|
+
this.registerControllerPath = './src';
|
|
96
82
|
this.metaCache = new Map();
|
|
97
83
|
this.app = (0, fastify_1.default)();
|
|
98
84
|
this.appConfig = new config_1.AppConfig();
|
|
99
|
-
// this.app.setValidatorCompiler(() => () => true);
|
|
100
85
|
}
|
|
101
86
|
isTest() { }
|
|
87
|
+
static getApp() {
|
|
88
|
+
let isTestEnv = process.env.NODE_ENV == "test";
|
|
89
|
+
if (!AvleonApplication.instance) {
|
|
90
|
+
AvleonApplication.instance = new AvleonApplication();
|
|
91
|
+
}
|
|
92
|
+
return AvleonApplication.instance;
|
|
93
|
+
}
|
|
102
94
|
static getInternalApp(buildOptions) {
|
|
103
95
|
let isTestEnv = process.env.NODE_ENV == "test";
|
|
104
96
|
if (!AvleonApplication.instance) {
|
|
@@ -110,7 +102,7 @@ class AvleonApplication {
|
|
|
110
102
|
buildOptions.dataSourceOptions;
|
|
111
103
|
const typeorm = require("typeorm");
|
|
112
104
|
const datasource = new typeorm.DataSource(buildOptions.dataSourceOptions);
|
|
113
|
-
typedi_1.default.set(
|
|
105
|
+
typedi_1.default.set("idatasource", datasource);
|
|
114
106
|
AvleonApplication.instance.dataSource = datasource;
|
|
115
107
|
}
|
|
116
108
|
return AvleonApplication.instance;
|
|
@@ -120,13 +112,20 @@ class AvleonApplication {
|
|
|
120
112
|
return env.get("NODE_ENV") == "development";
|
|
121
113
|
}
|
|
122
114
|
async initSwagger(options) {
|
|
123
|
-
const { routePrefix, logo, ui, theme, configuration
|
|
115
|
+
const { routePrefix, logo, ui, theme, configuration, ...restOptions } = options;
|
|
124
116
|
this.app.register(swagger_1.default, {
|
|
125
|
-
openapi:
|
|
117
|
+
openapi: {
|
|
118
|
+
openapi: "3.0.0",
|
|
119
|
+
...restOptions,
|
|
120
|
+
},
|
|
126
121
|
});
|
|
127
122
|
const rPrefix = routePrefix ? routePrefix : "/docs";
|
|
128
123
|
if (options.ui && options.ui == "scalar") {
|
|
129
|
-
|
|
124
|
+
const scalarPlugin = (0, utils_1.optionalRequire)("@scalar/fastify-api-reference", {
|
|
125
|
+
failOnMissing: true,
|
|
126
|
+
customMessage: 'Install "@scalar/fastify-api-reference" to enable API docs.\n\n npm install @scalar/fastify-api-reference',
|
|
127
|
+
});
|
|
128
|
+
await this.app.register(scalarPlugin, {
|
|
130
129
|
routePrefix: rPrefix,
|
|
131
130
|
configuration: configuration
|
|
132
131
|
? configuration
|
|
@@ -141,29 +140,38 @@ class AvleonApplication {
|
|
|
141
140
|
});
|
|
142
141
|
}
|
|
143
142
|
else {
|
|
144
|
-
|
|
143
|
+
const fastifySwaggerUi = (0, utils_1.optionalRequire)("@fastify/swagger-ui", {
|
|
144
|
+
failOnMissing: true,
|
|
145
|
+
customMessage: 'Install "@fastify/swagger-ui" to enable API docs.\n\n npm install @fastify/swagger-ui',
|
|
146
|
+
});
|
|
147
|
+
await this.app.register(fastifySwaggerUi, {
|
|
145
148
|
logo: logo ? logo : null,
|
|
146
149
|
theme: theme ? theme : {},
|
|
147
150
|
routePrefix: rPrefix,
|
|
148
|
-
configuration: {
|
|
149
|
-
metaData: {
|
|
150
|
-
title: "Avleon Api",
|
|
151
|
-
ogTitle: "Avleon",
|
|
152
|
-
},
|
|
153
|
-
theme: "kepler",
|
|
154
|
-
favicon: "/static/favicon.png",
|
|
155
|
-
},
|
|
156
151
|
});
|
|
157
152
|
}
|
|
158
153
|
}
|
|
159
154
|
useCors(corsOptions = {}) {
|
|
160
155
|
this.app.register(cors_1.default, corsOptions);
|
|
161
156
|
}
|
|
162
|
-
useOpenApi(
|
|
163
|
-
|
|
157
|
+
useOpenApi(configOrClass, modifyConfig) {
|
|
158
|
+
let openApiConfig;
|
|
159
|
+
const isClass = (input) => {
|
|
160
|
+
var _a;
|
|
161
|
+
return (typeof input === 'function' &&
|
|
162
|
+
typeof input.prototype === 'object' &&
|
|
163
|
+
((_a = input.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === input);
|
|
164
|
+
};
|
|
165
|
+
if (isClass(configOrClass)) {
|
|
166
|
+
// It's a class constructor
|
|
167
|
+
openApiConfig = this.appConfig.get(configOrClass);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// It's a plain object
|
|
171
|
+
openApiConfig = configOrClass;
|
|
172
|
+
}
|
|
164
173
|
if (modifyConfig) {
|
|
165
|
-
|
|
166
|
-
this.globalSwaggerOptions = modifiedConfig;
|
|
174
|
+
this.globalSwaggerOptions = modifyConfig(openApiConfig);
|
|
167
175
|
}
|
|
168
176
|
else {
|
|
169
177
|
this.globalSwaggerOptions = openApiConfig;
|
|
@@ -171,16 +179,96 @@ class AvleonApplication {
|
|
|
171
179
|
this.hasSwagger = true;
|
|
172
180
|
}
|
|
173
181
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
182
|
+
* Registers the fastify-multipart plugin with the Fastify instance.
|
|
183
|
+
* This enables handling of multipart/form-data requests, typically used for file uploads.
|
|
184
|
+
*
|
|
185
|
+
* @param {MultipartOptions} options - Options to configure the fastify-multipart plugin.
|
|
186
|
+
* @param {FastifyInstance} this.app - The Fastify instance to register the plugin with.
|
|
187
|
+
* @property {MultipartOptions} this.multipartOptions - Stores the provided multipart options.
|
|
188
|
+
* @see {@link https://github.com/fastify/fastify-multipart} for more details on available options.
|
|
176
189
|
*/
|
|
177
|
-
async useSwagger(options) {
|
|
178
|
-
this.hasSwagger = true;
|
|
179
|
-
this.globalSwaggerOptions = options;
|
|
180
|
-
}
|
|
181
190
|
useMultipart(options) {
|
|
182
191
|
this.multipartOptions = options;
|
|
183
|
-
this.app.register(multipart_1.default,
|
|
192
|
+
this.app.register(multipart_1.default, {
|
|
193
|
+
...this.multipartOptions,
|
|
194
|
+
attachFieldsToBody: true,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Configures and initializes a TypeORM DataSource based on the provided configuration class.
|
|
199
|
+
* It retrieves the configuration from the application's configuration service and allows for optional modification.
|
|
200
|
+
* The initialized DataSource is then stored and registered within a dependency injection container.
|
|
201
|
+
*
|
|
202
|
+
* @template T - A generic type extending the `IConfig` interface, representing the configuration class.
|
|
203
|
+
* @template R - A generic type representing the return type of the configuration method of the `ConfigClass`.
|
|
204
|
+
* @param {Constructable<T>} ConfigClass - The constructor of the configuration class to be used for creating the DataSource.
|
|
205
|
+
* @param {(config: R) => R} [modifyConfig] - An optional function that takes the initial configuration and returns a modified configuration.
|
|
206
|
+
* @returns {void}
|
|
207
|
+
* @property {DataSourceOptions} this.dataSourceOptions - Stores the final DataSource options after potential modification.
|
|
208
|
+
* @property {DataSource} this.dataSource - Stores the initialized TypeORM DataSource instance.
|
|
209
|
+
* @see {@link https://typeorm.io/} for more information about TypeORM.
|
|
210
|
+
*/
|
|
211
|
+
useDataSource(ConfigClass, modifyConfig) {
|
|
212
|
+
const dsConfig = this.appConfig.get(ConfigClass);
|
|
213
|
+
if (modifyConfig) {
|
|
214
|
+
const modifiedConfig = modifyConfig(dsConfig);
|
|
215
|
+
this.dataSourceOptions = modifiedConfig;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.dataSourceOptions = dsConfig;
|
|
219
|
+
}
|
|
220
|
+
const typeorm = require("typeorm");
|
|
221
|
+
const datasource = new typeorm.DataSource(dsConfig);
|
|
222
|
+
this.dataSource = datasource;
|
|
223
|
+
typedi_1.default.set("idatasource", datasource);
|
|
224
|
+
}
|
|
225
|
+
useCache(options) {
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Registers an array of middleware classes to be executed before request handlers.
|
|
229
|
+
* It retrieves instances of the middleware classes from the dependency injection container
|
|
230
|
+
* and adds them as 'preHandler' hooks to the Fastify application.
|
|
231
|
+
*
|
|
232
|
+
* @template T - A generic type extending the `AppMiddleware` interface, representing the middleware class.
|
|
233
|
+
* @param {Constructor<T>[]} mclasses - An array of middleware class constructors to be registered.
|
|
234
|
+
* @returns {void}
|
|
235
|
+
* @property {Map<string, T>} this.middlewares - Stores the registered middleware instances, keyed by their class names.
|
|
236
|
+
* @see {@link https://www.fastify.io/docs/latest/Reference/Hooks/#prehandler} for more information about Fastify preHandler hooks.
|
|
237
|
+
*/
|
|
238
|
+
useMiddlewares(mclasses) {
|
|
239
|
+
for (const mclass of mclasses) {
|
|
240
|
+
const cls = typedi_1.default.get(mclass);
|
|
241
|
+
this.middlewares.set(mclass.name, cls);
|
|
242
|
+
this.app.addHook("preHandler", cls.invoke);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Registers a middleware constructor to be used for authorization purposes.
|
|
247
|
+
* The specific implementation and usage of this middleware will depend on the application's authorization logic.
|
|
248
|
+
*
|
|
249
|
+
* @template T - A generic type representing the constructor of the authorization middleware.
|
|
250
|
+
* @param {Constructor<T>} middleware - The constructor of the middleware to be used for authorization.
|
|
251
|
+
* @returns {void}
|
|
252
|
+
* @property {any} this.authorizeMiddleware - Stores the constructor of the authorization middleware.
|
|
253
|
+
*/
|
|
254
|
+
useAuthoriztion(middleware) {
|
|
255
|
+
this.authorizeMiddleware = middleware;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Registers the `@fastify/static` plugin to serve static files.
|
|
259
|
+
* It configures the root directory and URL prefix for serving static assets.
|
|
260
|
+
*
|
|
261
|
+
* @param {StaticFileOptions} [options={ path: undefined, prefix: undefined }] - Optional configuration for serving static files.
|
|
262
|
+
* @param {string} [options.path] - The absolute path to the static files directory. Defaults to 'process.cwd()/public'.
|
|
263
|
+
* @param {string} [options.prefix] - The URL prefix for serving static files. Defaults to '/static/'.
|
|
264
|
+
* @returns {void}
|
|
265
|
+
* @see {@link https://github.com/fastify/fastify-static} for more details on available options.
|
|
266
|
+
*/
|
|
267
|
+
useStaticFiles(options = { path: undefined, prefix: undefined }) {
|
|
268
|
+
this.app.register(require("@fastify/static"), {
|
|
269
|
+
root: options.path ? options.path : path_1.default.join(process.cwd(), "public"),
|
|
270
|
+
prefix: options.prefix ? options.prefix : "/static/",
|
|
271
|
+
});
|
|
184
272
|
}
|
|
185
273
|
handleMiddlewares(mclasses) {
|
|
186
274
|
for (const mclass of mclasses) {
|
|
@@ -202,7 +290,6 @@ class AvleonApplication {
|
|
|
202
290
|
* @returns void
|
|
203
291
|
*/
|
|
204
292
|
async buildController(controller) {
|
|
205
|
-
var _a, e_1, _b, _c;
|
|
206
293
|
const ctrl = typedi_1.default.get(controller);
|
|
207
294
|
const controllerMeta = Reflect.getMetadata(container_1.CONTROLLER_META_KEY, ctrl.constructor);
|
|
208
295
|
if (!controllerMeta)
|
|
@@ -212,89 +299,101 @@ class AvleonApplication {
|
|
|
212
299
|
const tag = ctrl.constructor.name.replace("Controller", "");
|
|
213
300
|
const swaggerControllerMeta = Reflect.getMetadata("controller:openapi", ctrl.constructor) || {};
|
|
214
301
|
const authClsMeata = Reflect.getMetadata(container_1.AUTHORIZATION_META_KEY, ctrl.constructor) || { authorize: false, options: undefined };
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
302
|
+
for await (const method of methods) {
|
|
303
|
+
const methodMeta = Reflect.getMetadata(container_1.ROUTE_META_KEY, prototype, method);
|
|
304
|
+
if (!methodMeta)
|
|
305
|
+
continue;
|
|
306
|
+
const methodmetaOptions = {
|
|
307
|
+
method: methodMeta.method.toLowerCase(),
|
|
308
|
+
path: (0, helpers_1.formatUrl)(controllerMeta.path + methodMeta.path),
|
|
309
|
+
};
|
|
310
|
+
const routeKey = `${methodmetaOptions.method}:${methodmetaOptions.path}`;
|
|
311
|
+
if (!this.routeSet.has(routeKey)) {
|
|
312
|
+
this.routeSet.add(routeKey);
|
|
313
|
+
}
|
|
314
|
+
const classMiddlewares = this.executeMiddlewares(ctrl, method);
|
|
315
|
+
// handle openapi data
|
|
316
|
+
const swaggerMeta = Reflect.getMetadata("route:openapi", prototype, method) || {};
|
|
317
|
+
const authClsMethodMeata = Reflect.getMetadata(container_1.AUTHORIZATION_META_KEY, ctrl.constructor, method) || { authorize: false, options: undefined };
|
|
318
|
+
const allMeta = this._processMeta(prototype, method);
|
|
319
|
+
let bodySchema = null;
|
|
320
|
+
allMeta.body.forEach((r) => {
|
|
321
|
+
if (r.schema) {
|
|
322
|
+
bodySchema = { ...r.schema };
|
|
235
323
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
324
|
+
});
|
|
325
|
+
const routePath = methodmetaOptions.path == "" ? "/" : methodmetaOptions.path;
|
|
326
|
+
let schema = { ...swaggerControllerMeta, ...swaggerMeta, tags: [tag] };
|
|
327
|
+
if (!swaggerMeta.body && bodySchema) {
|
|
328
|
+
schema = { ...schema, body: bodySchema };
|
|
329
|
+
}
|
|
330
|
+
this.app.route({
|
|
331
|
+
url: routePath,
|
|
332
|
+
method: methodmetaOptions.method.toUpperCase(),
|
|
333
|
+
schema: { ...schema },
|
|
334
|
+
handler: async (req, res) => {
|
|
335
|
+
let reqClone = req;
|
|
336
|
+
// class level authrization
|
|
337
|
+
if (authClsMeata.authorize && this.authorizeMiddleware) {
|
|
338
|
+
const cls = container_1.default.get(this.authorizeMiddleware);
|
|
339
|
+
await cls.authorize(reqClone, authClsMeata.options);
|
|
340
|
+
if (res.sent)
|
|
341
|
+
return;
|
|
245
342
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
let reqClone = req;
|
|
258
|
-
if (authClsMethodMeata.authorize && this.authorizeMiddleware) {
|
|
259
|
-
const cls = container_1.default.get(this.authorizeMiddleware);
|
|
260
|
-
await cls.authorize(reqClone, authClsMethodMeata.options);
|
|
343
|
+
// method level authorization
|
|
344
|
+
if (authClsMethodMeata.authorize && this.authorizeMiddleware) {
|
|
345
|
+
const cls = container_1.default.get(this.authorizeMiddleware);
|
|
346
|
+
await cls.authorize(reqClone, authClsMethodMeata.options);
|
|
347
|
+
if (res.sent)
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (classMiddlewares.length > 0) {
|
|
351
|
+
for (let m of classMiddlewares) {
|
|
352
|
+
const cls = typedi_1.default.get(m.constructor);
|
|
353
|
+
reqClone = (await cls.invoke(reqClone, res));
|
|
261
354
|
if (res.sent)
|
|
262
355
|
return;
|
|
263
356
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
357
|
+
}
|
|
358
|
+
const args = await this._mapArgs(reqClone, allMeta);
|
|
359
|
+
for (let paramMeta of allMeta.params) {
|
|
360
|
+
if (paramMeta.required) {
|
|
361
|
+
(0, validation_1.validateOrThrow)({ [paramMeta.key]: args[paramMeta.index] }, { [paramMeta.key]: { type: paramMeta.dataType } }, { location: 'param' });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (let queryMeta of allMeta.query) {
|
|
365
|
+
if (queryMeta.validatorClass) {
|
|
366
|
+
const err = await (0, helpers_1.validateObjectByInstance)(queryMeta.dataType, args[queryMeta.index]);
|
|
367
|
+
if (err) {
|
|
368
|
+
return await res.code(400).send({
|
|
369
|
+
code: 400,
|
|
370
|
+
error: "ValidationError",
|
|
371
|
+
errors: err,
|
|
372
|
+
message: err.message,
|
|
373
|
+
});
|
|
270
374
|
}
|
|
271
375
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
376
|
+
if (queryMeta.required) {
|
|
377
|
+
(0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: 'queryparam' });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
for (let bodyMeta of allMeta.body) {
|
|
381
|
+
if (bodyMeta.validatorClass) {
|
|
382
|
+
const err = await (0, helpers_1.validateObjectByInstance)(bodyMeta.dataType, args[bodyMeta.index]);
|
|
383
|
+
if (err) {
|
|
384
|
+
return await res.code(400).send({
|
|
385
|
+
code: 400,
|
|
386
|
+
error: "ValidationError",
|
|
387
|
+
errors: err,
|
|
388
|
+
message: err.message,
|
|
389
|
+
});
|
|
284
390
|
}
|
|
285
391
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
293
|
-
finally {
|
|
294
|
-
try {
|
|
295
|
-
if (!_d && !_a && (_b = methods_1.return)) await _b.call(methods_1);
|
|
296
|
-
}
|
|
297
|
-
finally { if (e_1) throw e_1.error; }
|
|
392
|
+
}
|
|
393
|
+
const result = await prototype[method].apply(ctrl, args);
|
|
394
|
+
return result;
|
|
395
|
+
},
|
|
396
|
+
});
|
|
298
397
|
}
|
|
299
398
|
}
|
|
300
399
|
/**
|
|
@@ -304,8 +403,7 @@ class AvleonApplication {
|
|
|
304
403
|
* @returns
|
|
305
404
|
*/
|
|
306
405
|
async _mapArgs(req, meta) {
|
|
307
|
-
var _a
|
|
308
|
-
var _g;
|
|
406
|
+
var _a;
|
|
309
407
|
if (!req.hasOwnProperty("_argsCache")) {
|
|
310
408
|
Object.defineProperty(req, "_argsCache", {
|
|
311
409
|
value: new Map(),
|
|
@@ -319,29 +417,17 @@ class AvleonApplication {
|
|
|
319
417
|
}
|
|
320
418
|
const args = meta.params.map((p) => req.params[p.key] || null);
|
|
321
419
|
meta.query.forEach((q) => (args[q.index] = q.key === "all" ? req.query : req.query[q.key]));
|
|
322
|
-
meta.body.forEach((body) => (args[body.index] =
|
|
420
|
+
meta.body.forEach((body) => (args[body.index] = { ...req.body, ...req.formData }));
|
|
323
421
|
meta.currentUser.forEach((user) => (args[user.index] = req.user));
|
|
324
422
|
meta.headers.forEach((header) => (args[header.index] =
|
|
325
423
|
header.key === "all" ? req.headers : req.headers[header.key]));
|
|
326
424
|
if (meta.file) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
_c = _k.value;
|
|
330
|
-
_h = false;
|
|
331
|
-
let f = _c;
|
|
332
|
-
args[f.index] = await req.file();
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
336
|
-
finally {
|
|
337
|
-
try {
|
|
338
|
-
if (!_h && !_a && (_b = _j.return)) await _b.call(_j);
|
|
339
|
-
}
|
|
340
|
-
finally { if (e_2) throw e_2.error; }
|
|
425
|
+
for await (let f of meta.file) {
|
|
426
|
+
args[f.index] = await req.file();
|
|
341
427
|
}
|
|
342
428
|
}
|
|
343
429
|
if (meta.files &&
|
|
344
|
-
((
|
|
430
|
+
((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.startsWith("multipart/form-data")) === true) {
|
|
345
431
|
const files = await req.saveRequestFiles();
|
|
346
432
|
if (!files || files.length === 0) {
|
|
347
433
|
throw new exceptions_1.BadRequestException({ error: "No files uploaded" });
|
|
@@ -355,27 +441,15 @@ class AvleonApplication {
|
|
|
355
441
|
mimetype: file.mimetype,
|
|
356
442
|
fields: file.fields,
|
|
357
443
|
}));
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
let f = _f;
|
|
363
|
-
const findex = fileInfo.findIndex((x) => x.fieldname == f.fieldName);
|
|
364
|
-
if (f.fieldName != "all" && findex == -1) {
|
|
365
|
-
throw new exceptions_1.BadRequestException(`${f.fieldName} doesn't exists in request files tree.`);
|
|
366
|
-
}
|
|
367
|
-
args[f.index] =
|
|
368
|
-
f.fieldName == "all"
|
|
369
|
-
? fileInfo
|
|
370
|
-
: fileInfo.filter((x) => x.fieldname == f.fieldName);
|
|
444
|
+
for await (let f of meta.files) {
|
|
445
|
+
const findex = fileInfo.findIndex((x) => x.fieldname == f.fieldName);
|
|
446
|
+
if (f.fieldName != "all" && findex == -1) {
|
|
447
|
+
throw new exceptions_1.BadRequestException(`${f.fieldName} doesn't exists in request files tree.`);
|
|
371
448
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (!_l && !_d && (_e = _m.return)) await _e.call(_m);
|
|
377
|
-
}
|
|
378
|
-
finally { if (e_3) throw e_3.error; }
|
|
449
|
+
args[f.index] =
|
|
450
|
+
f.fieldName == "all"
|
|
451
|
+
? fileInfo
|
|
452
|
+
: fileInfo.filter((x) => x.fieldname == f.fieldName);
|
|
379
453
|
}
|
|
380
454
|
}
|
|
381
455
|
cache.set(cacheKey, args);
|
|
@@ -405,25 +479,59 @@ class AvleonApplication {
|
|
|
405
479
|
this.metaCache.set(cacheKey, meta);
|
|
406
480
|
return meta;
|
|
407
481
|
}
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
482
|
+
_resolveControllerDir(dir) {
|
|
483
|
+
const isTsNode = process.env.TS_NODE_DEV ||
|
|
484
|
+
process.env.TS_NODE_PROJECT ||
|
|
485
|
+
process[Symbol.for("ts-node.register.instance")];
|
|
486
|
+
const controllerDir = path_1.default.join(process.cwd(), this.registerControllerPath);
|
|
487
|
+
return isTsNode ? controllerDir : controllerDir.replace('src', 'dist');
|
|
488
|
+
}
|
|
489
|
+
async autoControllers(controllersPath) {
|
|
490
|
+
const conDir = this._resolveControllerDir(controllersPath);
|
|
491
|
+
const files = await promises_1.default.readdir(conDir, { recursive: true });
|
|
411
492
|
for (const file of files) {
|
|
493
|
+
const isTestFile = /\.(test|spec|e2e-spec)\.ts$/.test(file);
|
|
494
|
+
if (isTestFile)
|
|
495
|
+
continue;
|
|
412
496
|
if (isTsNode ? file.endsWith(".ts") : file.endsWith(".js")) {
|
|
413
|
-
const filePath = path_1.default.join(
|
|
497
|
+
const filePath = path_1.default.join(conDir, file);
|
|
414
498
|
const module = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
|
|
415
499
|
for (const exported of Object.values(module)) {
|
|
416
500
|
if (typeof exported === "function" && (0, container_1.isApiController)(exported)) {
|
|
417
|
-
|
|
418
|
-
this.
|
|
501
|
+
console.log('adding', exported.name);
|
|
502
|
+
if (!this.controllers.some(con => exported.name == con.name)) {
|
|
503
|
+
this.controllers.push(exported);
|
|
504
|
+
}
|
|
505
|
+
//this.buildController(exported);
|
|
419
506
|
}
|
|
420
507
|
}
|
|
421
508
|
}
|
|
422
509
|
}
|
|
423
510
|
}
|
|
424
|
-
|
|
425
|
-
|
|
511
|
+
useControllers(controllers) {
|
|
512
|
+
if (Array.isArray(controllers)) {
|
|
513
|
+
this.controllers = controllers;
|
|
514
|
+
controllers.forEach(controller => {
|
|
515
|
+
if (!this.controllers.includes(controller)) {
|
|
516
|
+
this.controllers.push(controller);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
this.registerControllerAuto = true;
|
|
522
|
+
if (controllers.path) {
|
|
523
|
+
this.registerControllerPath = controllers.path;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
426
526
|
}
|
|
527
|
+
// addFeature(feature:{controllers:Function[]}){
|
|
528
|
+
// feature.controllers.forEach(c=> this.controllers.push(c))
|
|
529
|
+
// }
|
|
530
|
+
// mapFeature(){
|
|
531
|
+
// if(!this.isMapFeatures){
|
|
532
|
+
// this.isMapFeatures = true;
|
|
533
|
+
// }
|
|
534
|
+
// }
|
|
427
535
|
async _mapControllers() {
|
|
428
536
|
if (this.controllers.length > 0) {
|
|
429
537
|
for (let controller of this.controllers) {
|
|
@@ -436,28 +544,15 @@ class AvleonApplication {
|
|
|
436
544
|
}
|
|
437
545
|
}
|
|
438
546
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
async handleRoute(args) { }
|
|
547
|
+
// useControllersAuto(controllerPath?:string) {
|
|
548
|
+
// this.registerControllerAuto = true;
|
|
549
|
+
// //this.autoControllers();
|
|
550
|
+
// }
|
|
446
551
|
async mapFn(fn) {
|
|
447
552
|
const original = fn;
|
|
448
553
|
fn = function () { };
|
|
449
554
|
return fn;
|
|
450
555
|
}
|
|
451
|
-
useMiddlewares(mclasses) {
|
|
452
|
-
for (const mclass of mclasses) {
|
|
453
|
-
const cls = typedi_1.default.get(mclass);
|
|
454
|
-
this.middlewares.set(mclass.name, cls);
|
|
455
|
-
this.app.addHook("preHandler", cls.invoke);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
useAuthoriztion(middleware) {
|
|
459
|
-
this.authorizeMiddleware = middleware;
|
|
460
|
-
}
|
|
461
556
|
_handleError(error) {
|
|
462
557
|
if (error instanceof exceptions_1.BaseHttpException) {
|
|
463
558
|
return {
|
|
@@ -501,7 +596,7 @@ class AvleonApplication {
|
|
|
501
596
|
middlewares: [],
|
|
502
597
|
schema: {},
|
|
503
598
|
});
|
|
504
|
-
this.mapFn(fn);
|
|
599
|
+
// this.mapFn(fn);
|
|
505
600
|
const route = {
|
|
506
601
|
useMiddleware: (middlewares) => {
|
|
507
602
|
const midds = Array.isArray(middlewares) ? middlewares : [middlewares];
|
|
@@ -516,7 +611,7 @@ class AvleonApplication {
|
|
|
516
611
|
}
|
|
517
612
|
return route;
|
|
518
613
|
},
|
|
519
|
-
|
|
614
|
+
useOpenApi: (options) => {
|
|
520
615
|
const r = this.rMap.get(routeKey);
|
|
521
616
|
if (r) {
|
|
522
617
|
r.schema = options;
|
|
@@ -538,18 +633,16 @@ class AvleonApplication {
|
|
|
538
633
|
mapDelete(path = "", fn) {
|
|
539
634
|
return this._routeHandler(path, "DELETE", fn);
|
|
540
635
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
prefix: options.prefix ? options.prefix : "/static/",
|
|
545
|
-
});
|
|
636
|
+
_mapFeatures() {
|
|
637
|
+
const features = typedi_1.default.get('features');
|
|
638
|
+
console.log('Features', features);
|
|
546
639
|
}
|
|
547
640
|
async initializeDatabase() {
|
|
548
641
|
if (this.dataSourceOptions && this.dataSource) {
|
|
549
642
|
await this.dataSource.initialize();
|
|
550
643
|
}
|
|
551
644
|
}
|
|
552
|
-
async run(port = 4000) {
|
|
645
|
+
async run(port = 4000, fn) {
|
|
553
646
|
if (this.alreadyRun)
|
|
554
647
|
throw new system_exception_1.SystemUseError("App already running");
|
|
555
648
|
this.alreadyRun = true;
|
|
@@ -557,6 +650,12 @@ class AvleonApplication {
|
|
|
557
650
|
await this.initSwagger(this.globalSwaggerOptions);
|
|
558
651
|
}
|
|
559
652
|
await this.initializeDatabase();
|
|
653
|
+
if (this.isMapFeatures) {
|
|
654
|
+
this._mapFeatures();
|
|
655
|
+
}
|
|
656
|
+
if (this.registerControllerAuto) {
|
|
657
|
+
await this.autoControllers();
|
|
658
|
+
}
|
|
560
659
|
await this._mapControllers();
|
|
561
660
|
this.rMap.forEach((value, key) => {
|
|
562
661
|
const [m, r] = key.split(":");
|
|
@@ -573,7 +672,7 @@ class AvleonApplication {
|
|
|
573
672
|
});
|
|
574
673
|
this.app.setErrorHandler(async (error, req, res) => {
|
|
575
674
|
const handledErr = this._handleError(error);
|
|
576
|
-
if (error instanceof exceptions_1.ValidationErrorException) {
|
|
675
|
+
if (error instanceof exceptions_1.ValidationErrorException || error instanceof exceptions_1.BadRequestException) {
|
|
577
676
|
return res.status(handledErr.code).send({
|
|
578
677
|
code: handledErr.code,
|
|
579
678
|
error: handledErr.error,
|
|
@@ -618,13 +717,17 @@ class AvleonApplication {
|
|
|
618
717
|
// return this.app as any;
|
|
619
718
|
//
|
|
620
719
|
return {
|
|
621
|
-
get: async (url, options) => this.app.inject(
|
|
622
|
-
post: async (url, options) => this.app.inject(
|
|
623
|
-
put: async (url, options) => this.app.inject(
|
|
624
|
-
patch: async (url, options) => this.app.inject(
|
|
625
|
-
delete: async (url, options) => this.app.inject(
|
|
626
|
-
options: async (url, options) => this.app.inject(
|
|
627
|
-
getController: (controller) => {
|
|
720
|
+
get: async (url, options) => this.app.inject({ method: "GET", url, ...options }),
|
|
721
|
+
post: async (url, options) => this.app.inject({ method: "POST", url, ...options }),
|
|
722
|
+
put: async (url, options) => this.app.inject({ method: "PUT", url, ...options }),
|
|
723
|
+
patch: async (url, options) => this.app.inject({ method: "PATCH", url, ...options }),
|
|
724
|
+
delete: async (url, options) => this.app.inject({ method: "DELETE", url, ...options }),
|
|
725
|
+
options: async (url, options) => this.app.inject({ method: "OPTIONS", url, ...options }),
|
|
726
|
+
getController: (controller, deps = []) => {
|
|
727
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
|
|
728
|
+
deps.forEach((dep, i) => {
|
|
729
|
+
typedi_1.default.set(paramTypes[i], dep);
|
|
730
|
+
});
|
|
628
731
|
return typedi_1.default.get(controller);
|
|
629
732
|
},
|
|
630
733
|
};
|
|
@@ -637,77 +740,42 @@ class AvleonApplication {
|
|
|
637
740
|
}
|
|
638
741
|
exports.AvleonApplication = AvleonApplication;
|
|
639
742
|
AvleonApplication.buildOptions = {};
|
|
640
|
-
class
|
|
743
|
+
class AvleonTest {
|
|
641
744
|
constructor() {
|
|
642
745
|
process.env.NODE_ENV = "test";
|
|
643
746
|
}
|
|
644
|
-
static createBuilder() {
|
|
645
|
-
if (!TestBuilder.instance) {
|
|
646
|
-
TestBuilder.instance = new TestBuilder();
|
|
647
|
-
}
|
|
648
|
-
return TestBuilder.instance;
|
|
649
|
-
}
|
|
650
747
|
addDatasource(options) {
|
|
651
748
|
this.dataSourceOptions = options;
|
|
652
749
|
}
|
|
653
|
-
getController(controller) {
|
|
750
|
+
getController(controller, deps = []) {
|
|
751
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
|
|
752
|
+
deps.forEach((dep, i) => {
|
|
753
|
+
typedi_1.default.set(paramTypes[i], dep);
|
|
754
|
+
});
|
|
654
755
|
return typedi_1.default.get(controller);
|
|
655
756
|
}
|
|
656
757
|
getService(service) {
|
|
657
758
|
return typedi_1.default.get(service);
|
|
658
759
|
}
|
|
659
|
-
|
|
760
|
+
static createTestApplication(options) {
|
|
660
761
|
const app = AvleonApplication.getInternalApp({
|
|
661
|
-
dataSourceOptions:
|
|
762
|
+
dataSourceOptions: options.dataSource ? options.dataSource : null,
|
|
662
763
|
});
|
|
663
|
-
app.
|
|
764
|
+
app.useControllers([...options.controllers]);
|
|
664
765
|
return app.getTestApp();
|
|
665
766
|
}
|
|
666
|
-
|
|
767
|
+
static from(app) {
|
|
667
768
|
return app.getTestApp();
|
|
668
769
|
}
|
|
669
|
-
|
|
670
|
-
|
|
770
|
+
static clean() {
|
|
771
|
+
typedi_1.default.reset();
|
|
671
772
|
}
|
|
672
773
|
}
|
|
673
|
-
exports.
|
|
674
|
-
class
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
this.database = false;
|
|
678
|
-
this.testBuilder = false;
|
|
679
|
-
this.appConfig = new config_1.AppConfig();
|
|
680
|
-
}
|
|
681
|
-
static createAppBuilder() {
|
|
682
|
-
if (!Builder.instance) {
|
|
683
|
-
Builder.instance = new Builder();
|
|
684
|
-
}
|
|
685
|
-
return Builder.instance;
|
|
686
|
-
}
|
|
687
|
-
async registerPlugin(plugin, options) {
|
|
688
|
-
container_1.default.set(plugin, plugin.prototype);
|
|
689
|
-
}
|
|
690
|
-
addDataSource(ConfigClass, modifyConfig) {
|
|
691
|
-
const openApiConfig = this.appConfig.get(ConfigClass);
|
|
692
|
-
if (modifyConfig) {
|
|
693
|
-
const modifiedConfig = modifyConfig(openApiConfig);
|
|
694
|
-
this.dataSourceOptions = modifiedConfig;
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
this.dataSourceOptions = openApiConfig;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
build() {
|
|
701
|
-
if (this.alreadyBuilt) {
|
|
702
|
-
throw new Error("Already built");
|
|
703
|
-
}
|
|
704
|
-
this.alreadyBuilt = true;
|
|
705
|
-
const app = AvleonApplication.getInternalApp({
|
|
706
|
-
database: this.database,
|
|
707
|
-
multipartOptions: this.multipartOptions,
|
|
708
|
-
dataSourceOptions: this.dataSourceOptions,
|
|
709
|
-
});
|
|
774
|
+
exports.AvleonTest = AvleonTest;
|
|
775
|
+
class Avleon {
|
|
776
|
+
static createApplication() {
|
|
777
|
+
const app = AvleonApplication.getApp();
|
|
710
778
|
return app;
|
|
711
779
|
}
|
|
712
780
|
}
|
|
713
|
-
exports.
|
|
781
|
+
exports.Avleon = Avleon;
|