@avleon/core 0.0.29 → 0.0.31
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/package.json +2 -4
- package/src/application.ts +0 -104
- package/src/authentication.ts +0 -16
- package/src/cache.ts +0 -91
- package/src/collection.test.ts +0 -71
- package/src/collection.ts +0 -344
- package/src/config.test.ts +0 -35
- package/src/config.ts +0 -85
- package/src/constants.ts +0 -1
- package/src/container.ts +0 -54
- package/src/controller.ts +0 -125
- package/src/decorators.ts +0 -27
- package/src/environment-variables.ts +0 -53
- package/src/event-dispatcher.ts +0 -100
- package/src/event-subscriber.ts +0 -79
- package/src/exceptions/http-exceptions.ts +0 -86
- package/src/exceptions/index.ts +0 -1
- package/src/exceptions/system-exception.ts +0 -35
- package/src/file-storage.ts +0 -206
- package/src/helpers.ts +0 -324
- package/src/icore.ts +0 -1106
- package/src/index.ts +0 -32
- package/src/interfaces/avleon-application.ts +0 -32
- package/src/logger.ts +0 -72
- package/src/map-types.ts +0 -159
- package/src/middleware.ts +0 -121
- package/src/multipart.ts +0 -116
- package/src/openapi.ts +0 -372
- package/src/params.ts +0 -111
- package/src/queue.ts +0 -126
- package/src/response.ts +0 -74
- package/src/results.ts +0 -30
- package/src/route-methods.ts +0 -186
- package/src/swagger-schema.ts +0 -213
- package/src/testing.ts +0 -220
- package/src/types/app-builder.interface.ts +0 -18
- package/src/types/application.interface.ts +0 -7
- package/src/utils/hash.ts +0 -8
- package/src/utils/index.ts +0 -2
- package/src/utils/optional-require.ts +0 -50
- package/src/validation.ts +0 -160
- package/src/validator-extend.ts +0 -25
- package/src/websocket.ts +0 -47
package/src/icore.ts
DELETED
|
@@ -1,1106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright 2024
|
|
3
|
-
* @author Tareq Hossain
|
|
4
|
-
* @email xtrinsic96@gmail.com
|
|
5
|
-
* @url https://github.com/xtareq
|
|
6
|
-
*/
|
|
7
|
-
import fastify, {
|
|
8
|
-
FastifyInstance,
|
|
9
|
-
FastifyReply,
|
|
10
|
-
FastifyRequest,
|
|
11
|
-
HookHandlerDoneFunction,
|
|
12
|
-
preHandlerHookHandler,
|
|
13
|
-
RouteGenericInterface,
|
|
14
|
-
InjectOptions,
|
|
15
|
-
LightMyRequestResponse,
|
|
16
|
-
} from "fastify";
|
|
17
|
-
import Container, { Constructable } from "typedi";
|
|
18
|
-
import fs from "fs/promises";
|
|
19
|
-
import path from "path";
|
|
20
|
-
import container, {
|
|
21
|
-
CONTROLLER_META_KEY,
|
|
22
|
-
ROUTE_META_KEY,
|
|
23
|
-
PARAM_META_KEY,
|
|
24
|
-
QUERY_META_KEY,
|
|
25
|
-
REQUEST_BODY_META_KEY,
|
|
26
|
-
REQUEST_HEADER_META_KEY,
|
|
27
|
-
getRegisteredControllers,
|
|
28
|
-
isApiController,
|
|
29
|
-
REQUEST_USER_META_KEY,
|
|
30
|
-
AUTHORIZATION_META_KEY,
|
|
31
|
-
REQUEST_BODY_FILES_KEY,
|
|
32
|
-
REQUEST_BODY_FILE_KEY,
|
|
33
|
-
FEATURE_KEY,
|
|
34
|
-
} from "./container";
|
|
35
|
-
import {
|
|
36
|
-
Constructor,
|
|
37
|
-
formatUrl,
|
|
38
|
-
isValidJsonString,
|
|
39
|
-
validateObjectByInstance,
|
|
40
|
-
} from "./helpers";
|
|
41
|
-
import { SystemUseError } from "./exceptions/system-exception";
|
|
42
|
-
import { existsSync, PathLike } from "fs";
|
|
43
|
-
import { DataSource, DataSourceOptions } from "typeorm";
|
|
44
|
-
import { AvleonMiddleware } from "./middleware";
|
|
45
|
-
import {
|
|
46
|
-
BadRequestException,
|
|
47
|
-
BaseHttpException,
|
|
48
|
-
ValidationErrorException,
|
|
49
|
-
} from "./exceptions";
|
|
50
|
-
import { OpenApiOptions, OpenApiUiOptions } from "./openapi";
|
|
51
|
-
import swagger from "@fastify/swagger";
|
|
52
|
-
import { AvleonConfig, IConfig } from "./config";
|
|
53
|
-
import { Environment } from "./environment-variables";
|
|
54
|
-
import cors, { FastifyCorsOptions } from "@fastify/cors";
|
|
55
|
-
import fastifyMultipart, { FastifyMultipartOptions } from "@fastify/multipart";
|
|
56
|
-
import { MultipartFile } from "./multipart";
|
|
57
|
-
import { validateOrThrow } from "./validation";
|
|
58
|
-
import { optionalRequire } from "./utils";
|
|
59
|
-
|
|
60
|
-
import { ServerOptions, Server as SocketIoServer } from "socket.io";
|
|
61
|
-
import { SocketContextService } from "./event-dispatcher";
|
|
62
|
-
import { EventSubscriberRegistry } from "./event-subscriber";
|
|
63
|
-
|
|
64
|
-
export type FuncRoute = {
|
|
65
|
-
handler: any;
|
|
66
|
-
middlewares?: any[];
|
|
67
|
-
schema?: {};
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// IRequest
|
|
71
|
-
export interface IRequest extends FastifyRequest {
|
|
72
|
-
params: any;
|
|
73
|
-
query: any;
|
|
74
|
-
body: any;
|
|
75
|
-
headers: any;
|
|
76
|
-
user?: any;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface DoneFunction extends HookHandlerDoneFunction { }
|
|
80
|
-
// IResponse
|
|
81
|
-
export interface IResponse extends FastifyReply { }
|
|
82
|
-
|
|
83
|
-
export type TestResponseType = LightMyRequestResponse;
|
|
84
|
-
export type TestResponse = TestResponseType | Promise<TestResponseType>;
|
|
85
|
-
|
|
86
|
-
export interface TestApplication {
|
|
87
|
-
get: (url: string, options?: InjectOptions) => TestResponse;
|
|
88
|
-
post: (url: string, options?: InjectOptions) => TestResponse;
|
|
89
|
-
put: (url: string, options?: InjectOptions) => TestResponse;
|
|
90
|
-
patch: (url: string, options?: InjectOptions) => TestResponse;
|
|
91
|
-
delete: (url: string, options?: InjectOptions) => TestResponse;
|
|
92
|
-
options: (url: string, options?: InjectOptions) => TestResponse;
|
|
93
|
-
getController?: <T>(controller: Constructor<T>) => T;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ParamMetaOptions
|
|
97
|
-
export interface ParamMetaOptions {
|
|
98
|
-
index: number;
|
|
99
|
-
key: string;
|
|
100
|
-
name: string;
|
|
101
|
-
required: boolean;
|
|
102
|
-
validate: boolean;
|
|
103
|
-
dataType: any;
|
|
104
|
-
validatorClass: boolean;
|
|
105
|
-
schema?: any;
|
|
106
|
-
type:
|
|
107
|
-
| "route:param"
|
|
108
|
-
| "route:query"
|
|
109
|
-
| "route:body"
|
|
110
|
-
| "route:header"
|
|
111
|
-
| "route:user"
|
|
112
|
-
| "route:file"
|
|
113
|
-
| "route:files";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export interface ParamMetaFilesOptions {
|
|
117
|
-
index: number;
|
|
118
|
-
type: "route:files";
|
|
119
|
-
files: MultipartFile[];
|
|
120
|
-
fieldName: string;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Method Param Meta options
|
|
124
|
-
export interface MethodParamMeta {
|
|
125
|
-
params: ParamMetaOptions[];
|
|
126
|
-
query: ParamMetaOptions[];
|
|
127
|
-
body: ParamMetaOptions[];
|
|
128
|
-
headers: ParamMetaOptions[];
|
|
129
|
-
currentUser: ParamMetaOptions[];
|
|
130
|
-
swagger?: OpenApiUiOptions;
|
|
131
|
-
file?: any[];
|
|
132
|
-
files?: ParamMetaFilesOptions[];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
interface IRoute {
|
|
136
|
-
url: string;
|
|
137
|
-
method: string;
|
|
138
|
-
controller: string;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const isTsNode =
|
|
142
|
-
process.env.TS_NODE_DEV ||
|
|
143
|
-
process.env.TS_NODE_PROJECT ||
|
|
144
|
-
(process as any)[Symbol.for("ts-node.register.instance")];
|
|
145
|
-
const controllerDir = path.join(
|
|
146
|
-
process.cwd(),
|
|
147
|
-
isTsNode ? "./src/controllers" : "./dist/cotrollers",
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
type StaticFileOptions = {
|
|
151
|
-
path?: PathLike;
|
|
152
|
-
prefix?: string;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
type MultipartOptions = {
|
|
156
|
-
destination: PathLike;
|
|
157
|
-
} & FastifyMultipartOptions;
|
|
158
|
-
|
|
159
|
-
type AvleonApp = FastifyInstance;
|
|
160
|
-
|
|
161
|
-
export type TestAppOptions = {
|
|
162
|
-
controllers: Constructor[];
|
|
163
|
-
dataSource?: DataSource;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export interface AvleonTestAppliction {
|
|
167
|
-
addDataSource: (dataSourceOptions: DataSourceOptions) => void;
|
|
168
|
-
getApp: (options?: TestAppOptions) => any;
|
|
169
|
-
getController: <T>(controller: Constructor<T>, deps: any[]) => T;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export type AutoControllerOptions = {
|
|
173
|
-
auto: true;
|
|
174
|
-
path?: string;
|
|
175
|
-
};
|
|
176
|
-
export interface IAvleonApplication {
|
|
177
|
-
isDevelopment(): boolean;
|
|
178
|
-
useCors(corsOptions?: FastifyCorsOptions): void;
|
|
179
|
-
useDataSource<
|
|
180
|
-
T extends IConfig<R>,
|
|
181
|
-
R = ReturnType<InstanceType<Constructable<T>>["config"]>,
|
|
182
|
-
>(
|
|
183
|
-
ConfigClass: Constructable<T>,
|
|
184
|
-
modifyConfig?: (config: R) => R,
|
|
185
|
-
): void;
|
|
186
|
-
useOpenApi<
|
|
187
|
-
T extends IConfig<R>,
|
|
188
|
-
R = ReturnType<InstanceType<Constructable<T>>["config"]>,
|
|
189
|
-
>(
|
|
190
|
-
ConfigClass: Constructable<T>,
|
|
191
|
-
modifyConfig?: (config: R) => R,
|
|
192
|
-
): void;
|
|
193
|
-
|
|
194
|
-
useMultipart(options: MultipartOptions): void;
|
|
195
|
-
useCache(options: any): void;
|
|
196
|
-
|
|
197
|
-
useMiddlewares<T extends AvleonMiddleware>(mclasses: Constructor<T>[]): void;
|
|
198
|
-
useAuthoriztion<T extends any>(middleware: Constructor<T>): void;
|
|
199
|
-
mapRoute<T extends (...args: any[]) => any>(
|
|
200
|
-
method: "get" | "post" | "put" | "delete",
|
|
201
|
-
path: string,
|
|
202
|
-
fn: T,
|
|
203
|
-
): Promise<void>;
|
|
204
|
-
mapGet<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
205
|
-
mapPost<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
206
|
-
mapPut<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
207
|
-
mapDelete<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
208
|
-
useControllers(controllers: any[]): any;
|
|
209
|
-
useControllers(controllersOptions: AutoControllerOptions): any;
|
|
210
|
-
useControllers(controllersOrOptions: any[] | AutoControllerOptions): any;
|
|
211
|
-
useStaticFiles(options?: StaticFileOptions): void;
|
|
212
|
-
run(port?: number): Promise<void>;
|
|
213
|
-
getTestApp(): TestApplication;
|
|
214
|
-
}
|
|
215
|
-
type OpenApiConfigClass<T = any> = Constructable<IConfig<T>>;
|
|
216
|
-
type OpenApiConfigInput<T = any> = OpenApiConfigClass<T> | T;
|
|
217
|
-
|
|
218
|
-
type ConfigClass<T = any> = Constructable<IConfig<T>>;
|
|
219
|
-
type ConfigInput<T = any> = ConfigClass<T> | T;
|
|
220
|
-
|
|
221
|
-
interface FastifyWithIO extends FastifyInstance {
|
|
222
|
-
io?: any
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const subscriberRegistry = Container.get(EventSubscriberRegistry);
|
|
226
|
-
// InternalApplication
|
|
227
|
-
export class AvleonApplication {
|
|
228
|
-
private static instance: AvleonApplication;
|
|
229
|
-
private static buildOptions: any = {};
|
|
230
|
-
private app!: FastifyWithIO;
|
|
231
|
-
private routeSet = new Set<string>(); // Use Set for fast duplicate detection
|
|
232
|
-
private alreadyRun = false;
|
|
233
|
-
private routes: Map<string, Function> = new Map();
|
|
234
|
-
private middlewares: Map<string, AvleonMiddleware> = new Map();
|
|
235
|
-
private rMap = new Map<string, FuncRoute>();
|
|
236
|
-
private hasSwagger = false;
|
|
237
|
-
private _hasWebsocket = false;
|
|
238
|
-
private globalSwaggerOptions: any = {};
|
|
239
|
-
private dataSourceOptions?: DataSourceOptions = undefined;
|
|
240
|
-
private controllers: any[] = [];
|
|
241
|
-
private authorizeMiddleware?: any = undefined;
|
|
242
|
-
private appConfig: AvleonConfig;
|
|
243
|
-
private dataSource?: DataSource = undefined;
|
|
244
|
-
private isMapFeatures = false;
|
|
245
|
-
private registerControllerAuto = false;
|
|
246
|
-
private registerControllerPath = "./src";
|
|
247
|
-
|
|
248
|
-
private metaCache = new Map<string, MethodParamMeta>();
|
|
249
|
-
private multipartOptions: FastifyMultipartOptions | undefined;
|
|
250
|
-
private constructor() {
|
|
251
|
-
this.app = fastify();
|
|
252
|
-
this.appConfig = new AvleonConfig();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
static getApp(): AvleonApplication {
|
|
256
|
-
let isTestEnv = process.env.NODE_ENV == "test";
|
|
257
|
-
if (!AvleonApplication.instance) {
|
|
258
|
-
AvleonApplication.instance = new AvleonApplication();
|
|
259
|
-
}
|
|
260
|
-
return AvleonApplication.instance;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
static getInternalApp(buildOptions: any): AvleonApplication {
|
|
264
|
-
let isTestEnv = process.env.NODE_ENV == "test";
|
|
265
|
-
if (!AvleonApplication.instance) {
|
|
266
|
-
AvleonApplication.instance = new AvleonApplication();
|
|
267
|
-
}
|
|
268
|
-
AvleonApplication.buildOptions = buildOptions;
|
|
269
|
-
if (buildOptions.dataSourceOptions) {
|
|
270
|
-
AvleonApplication.instance.dataSourceOptions =
|
|
271
|
-
buildOptions.dataSourceOptions;
|
|
272
|
-
const typeorm = require("typeorm");
|
|
273
|
-
const datasource = new typeorm.DataSource(
|
|
274
|
-
buildOptions.dataSourceOptions,
|
|
275
|
-
) as DataSource;
|
|
276
|
-
|
|
277
|
-
Container.set<DataSource>("idatasource", datasource);
|
|
278
|
-
AvleonApplication.instance.dataSource = datasource;
|
|
279
|
-
}
|
|
280
|
-
return AvleonApplication.instance;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
isDevelopment() {
|
|
284
|
-
const env = container.get(Environment);
|
|
285
|
-
return env.get("NODE_ENV") == "development";
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
private async initSwagger(options: OpenApiUiOptions) {
|
|
289
|
-
const { routePrefix, logo, ui, theme, configuration, ...restOptions } =
|
|
290
|
-
options;
|
|
291
|
-
|
|
292
|
-
this.app.register(swagger, {
|
|
293
|
-
openapi: {
|
|
294
|
-
openapi: "3.0.0",
|
|
295
|
-
...restOptions,
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
const rPrefix = routePrefix ? routePrefix : "/docs";
|
|
299
|
-
|
|
300
|
-
if (options.ui && options.ui == "scalar") {
|
|
301
|
-
const scalarPlugin = optionalRequire("@scalar/fastify-api-reference", {
|
|
302
|
-
failOnMissing: true,
|
|
303
|
-
customMessage:
|
|
304
|
-
"Install \"@scalar/fastify-api-reference\" to enable API docs.\n\n npm install @scalar/fastify-api-reference",
|
|
305
|
-
});
|
|
306
|
-
await this.app.register(scalarPlugin, {
|
|
307
|
-
routePrefix: rPrefix as any,
|
|
308
|
-
configuration: configuration
|
|
309
|
-
? configuration
|
|
310
|
-
: {
|
|
311
|
-
metaData: {
|
|
312
|
-
title: "Avleon Api",
|
|
313
|
-
ogTitle: "Avleon",
|
|
314
|
-
},
|
|
315
|
-
theme: options.theme ? options.theme : "kepler",
|
|
316
|
-
favicon: "/static/favicon.png",
|
|
317
|
-
},
|
|
318
|
-
});
|
|
319
|
-
} else {
|
|
320
|
-
const fastifySwaggerUi = optionalRequire("@fastify/swagger-ui", {
|
|
321
|
-
failOnMissing: true,
|
|
322
|
-
customMessage:
|
|
323
|
-
"Install \"@fastify/swagger-ui\" to enable API docs.\n\n npm install @fastify/swagger-ui",
|
|
324
|
-
});
|
|
325
|
-
await this.app.register(fastifySwaggerUi, {
|
|
326
|
-
logo: logo ? logo : null,
|
|
327
|
-
theme: theme ? theme : {},
|
|
328
|
-
routePrefix: rPrefix as any,
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
private _isConfigClass<T>(input: any): input is ConfigClass<T> {
|
|
334
|
-
return (
|
|
335
|
-
typeof input === "function" &&
|
|
336
|
-
typeof input.prototype === "object" &&
|
|
337
|
-
input.prototype?.constructor === input
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
useCors<T = FastifyCorsOptions>(corsOptions?: ConfigInput<T>) {
|
|
341
|
-
let coptions: any = {};
|
|
342
|
-
if (corsOptions) {
|
|
343
|
-
if (this._isConfigClass<T>(corsOptions)) {
|
|
344
|
-
coptions = this.appConfig.get(corsOptions) as T;
|
|
345
|
-
} else {
|
|
346
|
-
coptions = corsOptions as T;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
this.app.register(cors, coptions);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
useWebSocket<T = Partial<ServerOptions>>(socketOptions: ConfigInput<T>) {
|
|
353
|
-
this._hasWebsocket = true;
|
|
354
|
-
this._initWebSocket(socketOptions);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private async _initWebSocket(options:any) {
|
|
358
|
-
const fsSocketIO = optionalRequire("fastify-socket.io", {
|
|
359
|
-
failOnMissing: true,
|
|
360
|
-
customMessage:
|
|
361
|
-
"Install \"fastify-socket.io\" to enable socket.io.\n\n run pnpm install fastify-socket.io",
|
|
362
|
-
});
|
|
363
|
-
await this.app.register(fsSocketIO, options);
|
|
364
|
-
const socketIO = await this.app.io;
|
|
365
|
-
Container.set(SocketIoServer, socketIO);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
useOpenApi<T = OpenApiUiOptions>(configOrClass: OpenApiConfigInput<T>) {
|
|
369
|
-
let openApiConfig: T;
|
|
370
|
-
if (this._isConfigClass(configOrClass)) {
|
|
371
|
-
openApiConfig = this.appConfig.get(configOrClass);
|
|
372
|
-
} else {
|
|
373
|
-
openApiConfig = configOrClass as T;
|
|
374
|
-
}
|
|
375
|
-
this.globalSwaggerOptions = openApiConfig;
|
|
376
|
-
this.hasSwagger = true;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
useMultipart<T extends MultipartOptions>(options: ConfigInput<T>) {
|
|
380
|
-
let multipartOptions: T;
|
|
381
|
-
if (this._isConfigClass(options)) {
|
|
382
|
-
multipartOptions = this.appConfig.get(options);
|
|
383
|
-
} else {
|
|
384
|
-
multipartOptions = options as T;
|
|
385
|
-
}
|
|
386
|
-
if (multipartOptions) {
|
|
387
|
-
this.multipartOptions = multipartOptions;
|
|
388
|
-
this.app.register(fastifyMultipart, {
|
|
389
|
-
...this.multipartOptions,
|
|
390
|
-
attachFieldsToBody: true,
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
useDataSource<T extends DataSourceOptions>(options: ConfigInput<T>) {
|
|
396
|
-
let dataSourceOptions: T;
|
|
397
|
-
if (this._isConfigClass(options)) {
|
|
398
|
-
dataSourceOptions = this.appConfig.get(options);
|
|
399
|
-
} else {
|
|
400
|
-
dataSourceOptions = options as T;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (!dataSourceOptions)
|
|
404
|
-
throw new SystemUseError("Invlaid datasource options.");
|
|
405
|
-
|
|
406
|
-
this.dataSourceOptions = dataSourceOptions;
|
|
407
|
-
const typeorm = require("typeorm");
|
|
408
|
-
const datasource = new typeorm.DataSource(dataSourceOptions) as DataSource;
|
|
409
|
-
|
|
410
|
-
this.dataSource = datasource;
|
|
411
|
-
|
|
412
|
-
Container.set<DataSource>("idatasource", datasource);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
private _useCache(options: any) { }
|
|
416
|
-
|
|
417
|
-
useMiddlewares<T extends AvleonMiddleware>(mclasses: Constructor<T>[]) {
|
|
418
|
-
for (const mclass of mclasses) {
|
|
419
|
-
const cls = Container.get<T>(mclass);
|
|
420
|
-
this.middlewares.set(mclass.name, cls);
|
|
421
|
-
this.app.addHook("preHandler", cls.invoke);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
useAuthoriztion<T extends any>(middleware: Constructor<T>) {
|
|
426
|
-
this.authorizeMiddleware = middleware as any;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
useStaticFiles(
|
|
430
|
-
options: StaticFileOptions = { path: undefined, prefix: undefined },
|
|
431
|
-
) {
|
|
432
|
-
this.app.register(require("@fastify/static"), {
|
|
433
|
-
root: options.path ? options.path : path.join(process.cwd(), "public"),
|
|
434
|
-
prefix: options.prefix ? options.prefix : "/static/",
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
private handleMiddlewares<T extends AvleonMiddleware>(
|
|
439
|
-
mclasses: Constructor<T>[],
|
|
440
|
-
) {
|
|
441
|
-
for (const mclass of mclasses) {
|
|
442
|
-
const cls = Container.get<T>(mclass.constructor);
|
|
443
|
-
this.middlewares.set(mclass.name, cls);
|
|
444
|
-
this.app.addHook("preHandler", cls.invoke);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
private executeMiddlewares(target: any, propertyKey?: string) {
|
|
449
|
-
const classMiddlewares =
|
|
450
|
-
Reflect.getMetadata("controller:middleware", target.constructor) || [];
|
|
451
|
-
const methodMiddlewares = propertyKey
|
|
452
|
-
? Reflect.getMetadata("route:middleware", target, propertyKey) || []
|
|
453
|
-
: [];
|
|
454
|
-
|
|
455
|
-
return [...classMiddlewares, ...methodMiddlewares];
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* build controller
|
|
460
|
-
* @param controller
|
|
461
|
-
* @returns void
|
|
462
|
-
*/
|
|
463
|
-
private async buildController(controller: any) {
|
|
464
|
-
const ctrl: any = Container.get(controller);
|
|
465
|
-
const controllerMeta = Reflect.getMetadata(
|
|
466
|
-
CONTROLLER_META_KEY,
|
|
467
|
-
ctrl.constructor,
|
|
468
|
-
);
|
|
469
|
-
if (!controllerMeta) return;
|
|
470
|
-
const prototype = Object.getPrototypeOf(ctrl);
|
|
471
|
-
const methods = Object.getOwnPropertyNames(prototype).filter(
|
|
472
|
-
(name) => name !== "constructor",
|
|
473
|
-
);
|
|
474
|
-
const tag = ctrl.constructor.name.replace("Controller", "");
|
|
475
|
-
const swaggerControllerMeta =
|
|
476
|
-
Reflect.getMetadata("controller:openapi", ctrl.constructor) || {};
|
|
477
|
-
const authClsMeata = Reflect.getMetadata(
|
|
478
|
-
AUTHORIZATION_META_KEY,
|
|
479
|
-
ctrl.constructor,
|
|
480
|
-
) || { authorize: false, options: undefined };
|
|
481
|
-
|
|
482
|
-
for await (const method of methods) {
|
|
483
|
-
const methodMeta = Reflect.getMetadata(ROUTE_META_KEY, prototype, method);
|
|
484
|
-
if (!methodMeta) continue;
|
|
485
|
-
const methodmetaOptions = {
|
|
486
|
-
method: methodMeta.method.toLowerCase(),
|
|
487
|
-
path: formatUrl(controllerMeta.path + methodMeta.path),
|
|
488
|
-
};
|
|
489
|
-
const routeKey = `${methodmetaOptions.method}:${methodmetaOptions.path}`;
|
|
490
|
-
if (!this.routeSet.has(routeKey)) {
|
|
491
|
-
this.routeSet.add(routeKey);
|
|
492
|
-
}
|
|
493
|
-
const classMiddlewares = this.executeMiddlewares(ctrl, method);
|
|
494
|
-
|
|
495
|
-
// handle openapi data
|
|
496
|
-
const swaggerMeta =
|
|
497
|
-
Reflect.getMetadata("route:openapi", prototype, method) || {};
|
|
498
|
-
|
|
499
|
-
const authClsMethodMeata = Reflect.getMetadata(
|
|
500
|
-
AUTHORIZATION_META_KEY,
|
|
501
|
-
ctrl.constructor,
|
|
502
|
-
method,
|
|
503
|
-
) || { authorize: false, options: undefined };
|
|
504
|
-
const allMeta = this._processMeta(prototype, method);
|
|
505
|
-
|
|
506
|
-
let bodySchema: any = null;
|
|
507
|
-
allMeta.body.forEach((r) => {
|
|
508
|
-
if (r.schema) {
|
|
509
|
-
bodySchema = { ...r.schema };
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
const routePath =
|
|
514
|
-
methodmetaOptions.path == "" ? "/" : methodmetaOptions.path;
|
|
515
|
-
|
|
516
|
-
let schema = { ...swaggerControllerMeta, ...swaggerMeta, tags: [tag] };
|
|
517
|
-
if (!swaggerMeta.body && bodySchema) {
|
|
518
|
-
schema = { ...schema, body: bodySchema };
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
this.app.route({
|
|
522
|
-
url: routePath,
|
|
523
|
-
method: methodmetaOptions.method.toUpperCase(),
|
|
524
|
-
schema: { ...schema },
|
|
525
|
-
handler: async (req, res) => {
|
|
526
|
-
let reqClone = req as IRequest;
|
|
527
|
-
|
|
528
|
-
// class level authrization
|
|
529
|
-
if (authClsMeata.authorize && this.authorizeMiddleware) {
|
|
530
|
-
const cls = container.get(this.authorizeMiddleware) as any;
|
|
531
|
-
await cls.authorize(reqClone, authClsMeata.options);
|
|
532
|
-
if (res.sent) return;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// method level authorization
|
|
536
|
-
if (authClsMethodMeata.authorize && this.authorizeMiddleware) {
|
|
537
|
-
const cls = container.get(this.authorizeMiddleware) as any;
|
|
538
|
-
await cls.authorize(reqClone, authClsMethodMeata.options);
|
|
539
|
-
if (res.sent) return;
|
|
540
|
-
}
|
|
541
|
-
if (classMiddlewares.length > 0) {
|
|
542
|
-
for (let m of classMiddlewares) {
|
|
543
|
-
const cls = Container.get<AvleonMiddleware>(m.constructor);
|
|
544
|
-
reqClone = (await cls.invoke(reqClone, res)) as IRequest;
|
|
545
|
-
if (res.sent) return;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
const args = await this._mapArgs(reqClone, allMeta);
|
|
549
|
-
|
|
550
|
-
for (let paramMeta of allMeta.params) {
|
|
551
|
-
if (paramMeta.required) {
|
|
552
|
-
validateOrThrow(
|
|
553
|
-
{ [paramMeta.key]: args[paramMeta.index] },
|
|
554
|
-
{ [paramMeta.key]: { type: paramMeta.dataType } },
|
|
555
|
-
{ location: "param" },
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
for (let queryMeta of allMeta.query) {
|
|
561
|
-
if (queryMeta.validatorClass) {
|
|
562
|
-
const err = await validateObjectByInstance(
|
|
563
|
-
queryMeta.dataType,
|
|
564
|
-
args[queryMeta.index],
|
|
565
|
-
);
|
|
566
|
-
if (err) {
|
|
567
|
-
return await res.code(400).send({
|
|
568
|
-
code: 400,
|
|
569
|
-
error: "ValidationError",
|
|
570
|
-
errors: err,
|
|
571
|
-
message: err.message,
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
if (queryMeta.required) {
|
|
576
|
-
validateOrThrow(
|
|
577
|
-
{ [queryMeta.key]: args[queryMeta.index] },
|
|
578
|
-
{ [queryMeta.key]: { type: queryMeta.dataType } },
|
|
579
|
-
{ location: "queryparam" },
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
for (let bodyMeta of allMeta.body) {
|
|
585
|
-
if (bodyMeta.validatorClass) {
|
|
586
|
-
const err = await validateObjectByInstance(
|
|
587
|
-
bodyMeta.dataType,
|
|
588
|
-
args[bodyMeta.index],
|
|
589
|
-
);
|
|
590
|
-
if (err) {
|
|
591
|
-
return await res.code(400).send({
|
|
592
|
-
code: 400,
|
|
593
|
-
error: "ValidationError",
|
|
594
|
-
errors: err,
|
|
595
|
-
message: err.message,
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
const result = await prototype[method].apply(ctrl, args);
|
|
601
|
-
return result;
|
|
602
|
-
},
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* map all request parameters
|
|
609
|
-
* @param req
|
|
610
|
-
* @param meta
|
|
611
|
-
* @returns
|
|
612
|
-
*/
|
|
613
|
-
private async _mapArgs(req: IRequest, meta: MethodParamMeta): Promise<any[]> {
|
|
614
|
-
if (!req.hasOwnProperty("_argsCache")) {
|
|
615
|
-
Object.defineProperty(req, "_argsCache", {
|
|
616
|
-
value: new Map<string, any[]>(),
|
|
617
|
-
enumerable: false,
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const cache: Map<string, any[]> = (req as any)._argsCache;
|
|
622
|
-
const cacheKey = JSON.stringify(meta); // Faster key-based lookup
|
|
623
|
-
|
|
624
|
-
if (cache.has(cacheKey)) {
|
|
625
|
-
return cache.get(cacheKey)!;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const args: any[] = meta.params.map((p) => req.params[p.key] || null);
|
|
629
|
-
meta.query.forEach(
|
|
630
|
-
(q) => (args[q.index] = q.key === "all" ? req.query : req.query[q.key]),
|
|
631
|
-
);
|
|
632
|
-
meta.body.forEach(
|
|
633
|
-
(body) => (args[body.index] = { ...req.body, ...req.formData }),
|
|
634
|
-
);
|
|
635
|
-
meta.currentUser.forEach((user) => (args[user.index] = req.user));
|
|
636
|
-
meta.headers.forEach(
|
|
637
|
-
(header) =>
|
|
638
|
-
(args[header.index] =
|
|
639
|
-
header.key === "all" ? req.headers : req.headers[header.key]),
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
if (meta.file) {
|
|
643
|
-
for await (let f of meta.file) {
|
|
644
|
-
args[f.index] = await req.file();
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
if (
|
|
649
|
-
meta.files &&
|
|
650
|
-
req.headers["content-type"]?.startsWith("multipart/form-data") === true
|
|
651
|
-
) {
|
|
652
|
-
const files = await req.saveRequestFiles();
|
|
653
|
-
if (!files || files.length === 0) {
|
|
654
|
-
throw new BadRequestException({ error: "No files uploaded" });
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
const fileInfo = files.map((file) => ({
|
|
658
|
-
type: file.type,
|
|
659
|
-
filepath: file.filepath,
|
|
660
|
-
fieldname: file.fieldname,
|
|
661
|
-
filename: file.filename,
|
|
662
|
-
encoding: file.encoding,
|
|
663
|
-
mimetype: file.mimetype,
|
|
664
|
-
fields: file.fields,
|
|
665
|
-
}));
|
|
666
|
-
for await (let f of meta.files) {
|
|
667
|
-
const findex = fileInfo.findIndex((x) => x.fieldname == f.fieldName);
|
|
668
|
-
if (f.fieldName != "all" && findex == -1) {
|
|
669
|
-
throw new BadRequestException(
|
|
670
|
-
`${f.fieldName} doesn't exists in request files tree.`,
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
args[f.index] =
|
|
674
|
-
f.fieldName == "all"
|
|
675
|
-
? fileInfo
|
|
676
|
-
: fileInfo.filter((x) => x.fieldname == f.fieldName);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
cache.set(cacheKey, args);
|
|
681
|
-
return args;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Process Meta for controlelr class methods
|
|
686
|
-
* @param prototype
|
|
687
|
-
* @param method
|
|
688
|
-
* @returns
|
|
689
|
-
*/
|
|
690
|
-
private _processMeta(prototype: any, method: string): MethodParamMeta {
|
|
691
|
-
const cacheKey = `${prototype.constructor.name}_${method}`;
|
|
692
|
-
if (this.metaCache.has(cacheKey)) {
|
|
693
|
-
return this.metaCache.get(cacheKey)!;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
const meta: MethodParamMeta = {
|
|
697
|
-
params: Reflect.getMetadata(PARAM_META_KEY, prototype, method) || [],
|
|
698
|
-
query: Reflect.getMetadata(QUERY_META_KEY, prototype, method) || [],
|
|
699
|
-
body: Reflect.getMetadata(REQUEST_BODY_META_KEY, prototype, method) || [],
|
|
700
|
-
file: Reflect.getMetadata(REQUEST_BODY_FILE_KEY, prototype, method) || [],
|
|
701
|
-
files:
|
|
702
|
-
Reflect.getMetadata(REQUEST_BODY_FILES_KEY, prototype, method) || [],
|
|
703
|
-
headers:
|
|
704
|
-
Reflect.getMetadata(REQUEST_HEADER_META_KEY, prototype, method) || [],
|
|
705
|
-
currentUser:
|
|
706
|
-
Reflect.getMetadata(REQUEST_USER_META_KEY, prototype, method) || [],
|
|
707
|
-
// swagger: Reflect.getMetadata("route:openapi", prototype, method) || {}
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
this.metaCache.set(cacheKey, meta);
|
|
711
|
-
return meta;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
private _resolveControllerDir(dir?: string) {
|
|
715
|
-
const isTsNode =
|
|
716
|
-
process.env.TS_NODE_DEV ||
|
|
717
|
-
process.env.TS_NODE_PROJECT ||
|
|
718
|
-
(process as any)[Symbol.for("ts-node.register.instance")];
|
|
719
|
-
const controllerDir = path.join(process.cwd(), this.registerControllerPath);
|
|
720
|
-
|
|
721
|
-
return isTsNode ? controllerDir : controllerDir.replace("src", "dist");
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
private async autoControllers(controllersPath?: string) {
|
|
725
|
-
const conDir = this._resolveControllerDir(controllersPath);
|
|
726
|
-
|
|
727
|
-
const files = await fs.readdir(conDir, { recursive: true });
|
|
728
|
-
for (const file of files) {
|
|
729
|
-
const isTestFile = /\.(test|spec|e2e-spec)\.ts$/.test(file);
|
|
730
|
-
if (isTestFile) continue;
|
|
731
|
-
if (isTsNode ? file.endsWith(".ts") : file.endsWith(".js")) {
|
|
732
|
-
const filePath = path.join(conDir, file);
|
|
733
|
-
const module = await import(filePath);
|
|
734
|
-
for (const exported of Object.values(module)) {
|
|
735
|
-
if (typeof exported === "function" && isApiController(exported)) {
|
|
736
|
-
console.log("adding", exported.name);
|
|
737
|
-
if (!this.controllers.some((con) => exported.name == con.name)) {
|
|
738
|
-
this.controllers.push(exported);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
//this.buildController(exported);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
useControllers(controllers: Constructor[] | AutoControllerOptions) {
|
|
749
|
-
if (Array.isArray(controllers)) {
|
|
750
|
-
this.controllers = controllers;
|
|
751
|
-
|
|
752
|
-
controllers.forEach((controller) => {
|
|
753
|
-
if (!this.controllers.includes(controller)) {
|
|
754
|
-
this.controllers.push(controller);
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
} else {
|
|
758
|
-
this.registerControllerAuto = true;
|
|
759
|
-
if (controllers.path) {
|
|
760
|
-
this.registerControllerPath = controllers.path;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// addFeature(feature:{controllers:Function[]}){
|
|
766
|
-
// feature.controllers.forEach(c=> this.controllers.push(c))
|
|
767
|
-
// }
|
|
768
|
-
|
|
769
|
-
// mapFeature(){
|
|
770
|
-
// if(!this.isMapFeatures){
|
|
771
|
-
// this.isMapFeatures = true;
|
|
772
|
-
// }
|
|
773
|
-
// }
|
|
774
|
-
|
|
775
|
-
private async _mapControllers() {
|
|
776
|
-
if (this.controllers.length > 0) {
|
|
777
|
-
for (let controller of this.controllers) {
|
|
778
|
-
if (isApiController(controller)) {
|
|
779
|
-
this.buildController(controller);
|
|
780
|
-
} else {
|
|
781
|
-
throw new SystemUseError("Not a api controller.");
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// useControllersAuto(controllerPath?:string) {
|
|
788
|
-
// this.registerControllerAuto = true;
|
|
789
|
-
// //this.autoControllers();
|
|
790
|
-
// }
|
|
791
|
-
|
|
792
|
-
private async mapFn(fn: Function) {
|
|
793
|
-
const original = fn;
|
|
794
|
-
fn = function () { };
|
|
795
|
-
return fn;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
private _handleError(error: any): {
|
|
799
|
-
code: number;
|
|
800
|
-
error: string;
|
|
801
|
-
message: any;
|
|
802
|
-
} {
|
|
803
|
-
if (error instanceof BaseHttpException) {
|
|
804
|
-
return {
|
|
805
|
-
code: error.code,
|
|
806
|
-
error: error.name,
|
|
807
|
-
message: isValidJsonString(error.message)
|
|
808
|
-
? JSON.parse(error.message)
|
|
809
|
-
: error.message,
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
return {
|
|
813
|
-
code: 500,
|
|
814
|
-
error: "INTERNAL_ERROR",
|
|
815
|
-
message: error.message ? error.message : "Something going wrong.",
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
async mapRoute<T extends (...args: any[]) => any>(
|
|
820
|
-
method: "get" | "post" | "put" | "delete",
|
|
821
|
-
path: string = "",
|
|
822
|
-
fn: T,
|
|
823
|
-
) {
|
|
824
|
-
await this.mapFn(fn);
|
|
825
|
-
|
|
826
|
-
this.app[method](path, async (req: any, res: any) => {
|
|
827
|
-
|
|
828
|
-
try {
|
|
829
|
-
const result = await fn.apply(this, [req, res]);
|
|
830
|
-
if (typeof result === "object" && result !== null) {
|
|
831
|
-
res.json(result);
|
|
832
|
-
} else {
|
|
833
|
-
res.send(result);
|
|
834
|
-
}
|
|
835
|
-
} catch (error) {
|
|
836
|
-
console.error(`Error in ${method} route handler:`, error);
|
|
837
|
-
const handledErr = this._handleError(error);
|
|
838
|
-
res.status(handledErr.code).send(handledErr);
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
private _routeHandler<T extends (...args: any[]) => any>(
|
|
843
|
-
routePath: string,
|
|
844
|
-
method: string,
|
|
845
|
-
fn: T,
|
|
846
|
-
) {
|
|
847
|
-
const routeKey = method + ":" + routePath;
|
|
848
|
-
this.rMap.set(routeKey, {
|
|
849
|
-
handler: fn,
|
|
850
|
-
middlewares: [],
|
|
851
|
-
schema: {},
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
const route = {
|
|
855
|
-
useMiddleware: <M extends AvleonMiddleware>(
|
|
856
|
-
middlewares: Constructor<AvleonMiddleware>[],
|
|
857
|
-
) => {
|
|
858
|
-
const midds = Array.isArray(middlewares) ? middlewares : [middlewares];
|
|
859
|
-
const ms: any[] = (midds as unknown as any[]).map((mclass) => {
|
|
860
|
-
const cls = Container.get<AvleonMiddleware>(mclass);
|
|
861
|
-
this.middlewares.set(mclass.name, cls);
|
|
862
|
-
return cls.invoke;
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
const r = this.rMap.get(routeKey);
|
|
866
|
-
if (r) {
|
|
867
|
-
r.middlewares = ms;
|
|
868
|
-
}
|
|
869
|
-
return route;
|
|
870
|
-
},
|
|
871
|
-
|
|
872
|
-
useOpenApi: (options: OpenApiOptions) => {
|
|
873
|
-
const r = this.rMap.get(routeKey);
|
|
874
|
-
if (r) {
|
|
875
|
-
r.schema = options;
|
|
876
|
-
}
|
|
877
|
-
return route;
|
|
878
|
-
},
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
return route;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
mapGet<T extends (...args: any[]) => any>(path: string = "", fn: T) {
|
|
885
|
-
return this._routeHandler(path, "GET", fn);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
mapPost<T extends (...args: any[]) => any>(path: string = "", fn: T) {
|
|
889
|
-
return this._routeHandler(path, "POST", fn);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
mapPut<T extends (...args: any[]) => any>(path: string = "", fn: T) {
|
|
893
|
-
return this._routeHandler(path, "PUT", fn);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
mapDelete<T extends (...args: any[]) => any>(path: string = "", fn: T) {
|
|
897
|
-
return this._routeHandler(path, "DELETE", fn);
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
private _mapFeatures() {
|
|
901
|
-
const features = Container.get("features");
|
|
902
|
-
console.log("Features", features);
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
async initializeDatabase() {
|
|
906
|
-
if (this.dataSourceOptions && this.dataSource) {
|
|
907
|
-
await this.dataSource.initialize();
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
handleSocket(socket: any) {
|
|
913
|
-
subscriberRegistry.register(socket);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
async run(port: number = 4000, fn?: CallableFunction): Promise<void> {
|
|
917
|
-
if (this.alreadyRun) throw new SystemUseError("App already running");
|
|
918
|
-
this.alreadyRun = true;
|
|
919
|
-
|
|
920
|
-
if (this.hasSwagger) {
|
|
921
|
-
await this.initSwagger(this.globalSwaggerOptions);
|
|
922
|
-
}
|
|
923
|
-
await this.initializeDatabase();
|
|
924
|
-
if (this.isMapFeatures) {
|
|
925
|
-
this._mapFeatures();
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
if (this.registerControllerAuto) {
|
|
929
|
-
await this.autoControllers();
|
|
930
|
-
}
|
|
931
|
-
await this._mapControllers();
|
|
932
|
-
|
|
933
|
-
this.rMap.forEach((value, key) => {
|
|
934
|
-
const [m, r] = key.split(":");
|
|
935
|
-
this.app.route({
|
|
936
|
-
method: m,
|
|
937
|
-
url: r,
|
|
938
|
-
schema: value.schema || {},
|
|
939
|
-
preHandler: value.middlewares ? value.middlewares : [],
|
|
940
|
-
handler: async (req, res) => {
|
|
941
|
-
const result = await value.handler.apply(this, [req, res]);
|
|
942
|
-
return result;
|
|
943
|
-
},
|
|
944
|
-
});
|
|
945
|
-
});
|
|
946
|
-
this.app.setErrorHandler(async (error, req, res) => {
|
|
947
|
-
const handledErr = this._handleError(error);
|
|
948
|
-
if (
|
|
949
|
-
error instanceof ValidationErrorException ||
|
|
950
|
-
error instanceof BadRequestException
|
|
951
|
-
) {
|
|
952
|
-
return res.status(handledErr.code).send({
|
|
953
|
-
code: handledErr.code,
|
|
954
|
-
error: handledErr.error,
|
|
955
|
-
errors: handledErr.message,
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
return res.status(handledErr.code).send(handledErr);
|
|
959
|
-
});
|
|
960
|
-
|
|
961
|
-
await this.app.ready();
|
|
962
|
-
if (this._hasWebsocket) {
|
|
963
|
-
await this.app.io.on("connection", this.handleSocket);
|
|
964
|
-
await this.app.io.use((socket: { handshake: { auth: { token: any; }; }; data: { user: any; }; }, next:any) => {
|
|
965
|
-
const token = socket.handshake.auth.token;
|
|
966
|
-
try {
|
|
967
|
-
console.log("token", token);
|
|
968
|
-
const user = {id:1, name:"tareq"};
|
|
969
|
-
socket.data.user = user; // this powers @AuthUser()
|
|
970
|
-
next();
|
|
971
|
-
} catch {
|
|
972
|
-
next(new Error("Unauthorized"));
|
|
973
|
-
}
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
await this.app.listen({ port });
|
|
977
|
-
console.log(`Application running on http://127.0.0.1:${port}`);
|
|
978
|
-
}
|
|
979
|
-
getTestApp(buildOptions?: any): TestApplication {
|
|
980
|
-
try {
|
|
981
|
-
// }
|
|
982
|
-
// this.initializeDatabase();
|
|
983
|
-
this._mapControllers();
|
|
984
|
-
this.rMap.forEach((value, key) => {
|
|
985
|
-
const [m, r] = key.split(":");
|
|
986
|
-
this.app.route({
|
|
987
|
-
method: m,
|
|
988
|
-
url: r,
|
|
989
|
-
schema: value.schema || {},
|
|
990
|
-
preHandler: value.middlewares ? value.middlewares : [],
|
|
991
|
-
handler: async (req, res) => {
|
|
992
|
-
const result = await value.handler.apply(this, [req, res]);
|
|
993
|
-
return result;
|
|
994
|
-
},
|
|
995
|
-
});
|
|
996
|
-
});
|
|
997
|
-
|
|
998
|
-
this.app.setErrorHandler(async (error, req, res) => {
|
|
999
|
-
const handledErr = this._handleError(error);
|
|
1000
|
-
if (error instanceof ValidationErrorException) {
|
|
1001
|
-
return res.status(handledErr.code).send({
|
|
1002
|
-
code: handledErr.code,
|
|
1003
|
-
error: handledErr.error,
|
|
1004
|
-
errors: handledErr.message,
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
return res.status(handledErr.code).send(handledErr);
|
|
1008
|
-
});
|
|
1009
|
-
// return this.app as any;
|
|
1010
|
-
//
|
|
1011
|
-
return {
|
|
1012
|
-
get: async (url: string, options?: InjectOptions) =>
|
|
1013
|
-
this.app.inject({ method: "GET", url, ...options }),
|
|
1014
|
-
post: async (url: string, options?: InjectOptions) =>
|
|
1015
|
-
this.app.inject({ method: "POST", url, ...options }),
|
|
1016
|
-
put: async (url: string, options?: InjectOptions) =>
|
|
1017
|
-
this.app.inject({ method: "PUT", url, ...options }),
|
|
1018
|
-
patch: async (url: string, options?: InjectOptions) =>
|
|
1019
|
-
this.app.inject({ method: "PATCH", url, ...options }),
|
|
1020
|
-
delete: async (url: string, options?: InjectOptions) =>
|
|
1021
|
-
this.app.inject({ method: "DELETE", url, ...options }),
|
|
1022
|
-
options: async (url: string, options?: InjectOptions) =>
|
|
1023
|
-
this.app.inject({ method: "OPTIONS", url, ...options }),
|
|
1024
|
-
getController: <T>(controller: Constructor<T>, deps: any[] = []) => {
|
|
1025
|
-
const paramTypes =
|
|
1026
|
-
Reflect.getMetadata("design:paramtypes", controller) || [];
|
|
1027
|
-
|
|
1028
|
-
deps.forEach((dep, i) => {
|
|
1029
|
-
Container.set(paramTypes[i], dep);
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
return Container.get(controller);
|
|
1033
|
-
},
|
|
1034
|
-
};
|
|
1035
|
-
} catch (error) {
|
|
1036
|
-
console.log(error);
|
|
1037
|
-
throw new SystemUseError("Can't get test appliction");
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
export type Application = typeof AvleonApplication;
|
|
1043
|
-
|
|
1044
|
-
// Applciation Builder
|
|
1045
|
-
export interface ITestBuilder {
|
|
1046
|
-
getTestApplication(): AvleonTestAppliction;
|
|
1047
|
-
createTestApplication(options: any): AvleonTestAppliction;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
export interface IAppBuilder {
|
|
1051
|
-
registerPlugin<T extends Function, S extends {}>(
|
|
1052
|
-
plugin: T,
|
|
1053
|
-
options: S,
|
|
1054
|
-
): Promise<void>;
|
|
1055
|
-
addDataSource<
|
|
1056
|
-
T extends IConfig<R>,
|
|
1057
|
-
R = ReturnType<InstanceType<Constructable<T>>["config"]>,
|
|
1058
|
-
>(
|
|
1059
|
-
ConfigClass: Constructable<T>,
|
|
1060
|
-
modifyConfig?: (config: R) => R,
|
|
1061
|
-
): void;
|
|
1062
|
-
build<T extends IAvleonApplication>(): T;
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
export class AvleonTest {
|
|
1066
|
-
private constructor() {
|
|
1067
|
-
process.env.NODE_ENV = "test";
|
|
1068
|
-
}
|
|
1069
|
-
static getController<T>(controller: Constructor<T>, deps: any[] = []) {
|
|
1070
|
-
const paramTypes =
|
|
1071
|
-
Reflect.getMetadata("design:paramtypes", controller) || [];
|
|
1072
|
-
|
|
1073
|
-
deps.forEach((dep, i) => {
|
|
1074
|
-
Container.set(paramTypes[i], dep);
|
|
1075
|
-
});
|
|
1076
|
-
|
|
1077
|
-
return Container.get(controller);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
private getService<T>(service: Constructor<T>) {
|
|
1081
|
-
return Container.get(service);
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
static createTestApplication(options: TestAppOptions) {
|
|
1085
|
-
const app = AvleonApplication.getInternalApp({
|
|
1086
|
-
dataSourceOptions: options.dataSource ? options.dataSource : null,
|
|
1087
|
-
});
|
|
1088
|
-
app.useControllers([...options.controllers]);
|
|
1089
|
-
return app.getTestApp();
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
static from(app: AvleonApplication) {
|
|
1093
|
-
return app.getTestApp();
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
static clean() {
|
|
1097
|
-
Container.reset();
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
export class Avleon {
|
|
1102
|
-
static createApplication() {
|
|
1103
|
-
const app = AvleonApplication.getApp();
|
|
1104
|
-
return app;
|
|
1105
|
-
}
|
|
1106
|
-
}
|