@mini2/core 1.0.2 → 1.0.5
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 +75 -2
- package/dist/app.d.ts +14 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +65 -0
- package/dist/app.js.map +1 -0
- package/dist/container.d.ts +4 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +11 -0
- package/dist/container.js.map +1 -0
- package/dist/expections/http.expection.d.ts +79 -0
- package/dist/expections/http.expection.d.ts.map +1 -0
- package/dist/expections/http.expection.js +140 -0
- package/dist/expections/http.expection.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/app.interface.d.ts +6 -0
- package/dist/interfaces/app.interface.d.ts.map +1 -0
- package/dist/interfaces/app.interface.js +3 -0
- package/dist/interfaces/app.interface.js.map +1 -0
- package/dist/interfaces/authenticated.interface.d.ts +5 -0
- package/dist/interfaces/authenticated.interface.d.ts.map +1 -0
- package/dist/interfaces/authenticated.interface.js +3 -0
- package/dist/interfaces/authenticated.interface.js.map +1 -0
- package/dist/interfaces/config.interface.d.ts +6 -0
- package/dist/interfaces/config.interface.d.ts.map +1 -0
- package/dist/interfaces/config.interface.js +3 -0
- package/dist/interfaces/config.interface.js.map +1 -0
- package/dist/interfaces/queue.interface.d.ts +6 -0
- package/dist/interfaces/queue.interface.d.ts.map +1 -0
- package/dist/interfaces/queue.interface.js +3 -0
- package/dist/interfaces/queue.interface.js.map +1 -0
- package/dist/interfaces/repository.interface.d.ts +34 -0
- package/dist/interfaces/repository.interface.d.ts.map +1 -0
- package/dist/interfaces/repository.interface.js +3 -0
- package/dist/interfaces/repository.interface.js.map +1 -0
- package/dist/middlewares/authenticated.middleware.d.ts +4 -0
- package/dist/middlewares/authenticated.middleware.d.ts.map +1 -0
- package/dist/middlewares/authenticated.middleware.js +14 -0
- package/dist/middlewares/authenticated.middleware.js.map +1 -0
- package/dist/middlewares/authorized.middleware.d.ts +7 -0
- package/dist/middlewares/authorized.middleware.d.ts.map +1 -0
- package/dist/middlewares/authorized.middleware.js +16 -0
- package/dist/middlewares/authorized.middleware.js.map +1 -0
- package/dist/middlewares/validation.middleware.d.ts +4 -0
- package/dist/middlewares/validation.middleware.d.ts.map +1 -0
- package/dist/middlewares/validation.middleware.js +65 -0
- package/dist/middlewares/validation.middleware.js.map +1 -0
- package/dist/response-builder.d.ts +26 -0
- package/dist/response-builder.d.ts.map +1 -0
- package/dist/response-builder.js +45 -0
- package/dist/response-builder.js.map +1 -0
- package/dist/rest.d.ts +43 -0
- package/dist/rest.d.ts.map +1 -0
- package/dist/rest.js +213 -0
- package/dist/rest.js.map +1 -0
- package/dist/swagger.d.ts +30 -0
- package/dist/swagger.d.ts.map +1 -0
- package/dist/swagger.js +214 -0
- package/dist/swagger.js.map +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/array-unify.d.ts +2 -0
- package/dist/utils/array-unify.d.ts.map +1 -0
- package/dist/utils/array-unify.js +8 -0
- package/dist/utils/array-unify.js.map +1 -0
- package/dist/utils/math.d.ts +2 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.js +7 -0
- package/dist/utils/math.js.map +1 -0
- package/package.json +38 -6
- package/__tests__/controller.test.ts +0 -349
- package/app.ts +0 -51
- package/container.ts +0 -7
- package/expections/http.expection.ts +0 -147
- package/interfaces/app.interface.ts +0 -6
- package/interfaces/authenticated.interface.ts +0 -3
- package/interfaces/config.interface.ts +0 -5
- package/interfaces/queue.interface.ts +0 -5
- package/interfaces/repository.interface.ts +0 -32
- package/middlewares/authenticated.middleware.ts +0 -15
- package/middlewares/authorized.middleware.ts +0 -19
- package/middlewares/validation.middleware.ts +0 -76
- package/response-builder.ts +0 -61
- package/rest.ts +0 -254
- package/swagger.ts +0 -265
- package/types.ts +0 -3
- package/utils/array-unify.ts +0 -4
- package/utils/math.ts +0 -3
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { plainToInstance } from 'class-transformer';
|
|
2
|
-
import { validate, ValidationError } from 'class-validator';
|
|
3
|
-
import type { RequestHandler, Request, Response, NextFunction } from 'express';
|
|
4
|
-
import type { IValidationError } from '../expections/http.expection';
|
|
5
|
-
import HttpException from '../expections/http.expection';
|
|
6
|
-
|
|
7
|
-
const validationMiddleware = <T extends object>(
|
|
8
|
-
type: new () => T,
|
|
9
|
-
value: 'body' | 'query' | 'params',
|
|
10
|
-
skipMissingProperties = false,
|
|
11
|
-
whitelist = true,
|
|
12
|
-
forbidNonWhitelisted = true
|
|
13
|
-
): RequestHandler => {
|
|
14
|
-
return (req: Request, _res: Response, next: NextFunction) => {
|
|
15
|
-
// Query parametrelerinde boolean değerleri düzgün işle
|
|
16
|
-
if (value === 'query' && req.query) {
|
|
17
|
-
Object.keys(req.query).forEach((key) => {
|
|
18
|
-
if (req.query[key] === 'true') req.query[key] = true as any;
|
|
19
|
-
if (req.query[key] === 'false') req.query[key] = false as any;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 🔽 Eğer dosya alanları varsa, req.body'ye dahil et
|
|
24
|
-
if (value === 'body' && req.files) {
|
|
25
|
-
const files = req.files as Record<string, Express.Multer.File[]>;
|
|
26
|
-
for (const field in files) {
|
|
27
|
-
if (Array.isArray(files[field]) && files[field].length > 0) {
|
|
28
|
-
req.body[field] = files[field][0]; // sadece ilk dosyayı al
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const data = plainToInstance(type, req[value], {
|
|
34
|
-
enableImplicitConversion: true, // Otomatik tip dönüşümü için
|
|
35
|
-
exposeDefaultValues: true,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
validate(data as object, {
|
|
39
|
-
skipMissingProperties,
|
|
40
|
-
whitelist,
|
|
41
|
-
forbidNonWhitelisted,
|
|
42
|
-
}).then((errors: ValidationError[]) => {
|
|
43
|
-
if (errors.length > 0) {
|
|
44
|
-
const messages: IValidationError[] = errors.map(
|
|
45
|
-
(error: ValidationError) => {
|
|
46
|
-
const error1: IValidationError = {
|
|
47
|
-
field: error.property,
|
|
48
|
-
errors: [],
|
|
49
|
-
};
|
|
50
|
-
for (const key of Object.keys(error?.constraints || {})) {
|
|
51
|
-
if (error.constraints?.[key]) {
|
|
52
|
-
error1.errors.push(error.constraints[key]);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return error1;
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
next(
|
|
59
|
-
new HttpException(
|
|
60
|
-
{
|
|
61
|
-
errorId: 1,
|
|
62
|
-
message: 'Validation error',
|
|
63
|
-
validationErrors: messages,
|
|
64
|
-
},
|
|
65
|
-
400
|
|
66
|
-
)
|
|
67
|
-
);
|
|
68
|
-
} else {
|
|
69
|
-
req[value] = data as any;
|
|
70
|
-
next();
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export default validationMiddleware;
|
package/response-builder.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { Response } from 'express';
|
|
2
|
-
|
|
3
|
-
export interface IResponseBuilder<T = any> {
|
|
4
|
-
status: number;
|
|
5
|
-
data: T;
|
|
6
|
-
headers: Record<string, string>;
|
|
7
|
-
isFile: boolean;
|
|
8
|
-
|
|
9
|
-
ok(data: T): IResponseBuilder<T>;
|
|
10
|
-
created(data: T): IResponseBuilder<T>;
|
|
11
|
-
setHeader(key: string, value: string): IResponseBuilder<T>;
|
|
12
|
-
setHeaders(headers: Record<string, string>): IResponseBuilder<T>;
|
|
13
|
-
asFile(): IResponseBuilder<T>;
|
|
14
|
-
build(res: Response): void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class ResponseBuilder<T> implements IResponseBuilder<T> {
|
|
18
|
-
public status: number = 200;
|
|
19
|
-
public data!: T;
|
|
20
|
-
public headers: Record<string, string> = {};
|
|
21
|
-
public isFile: boolean = false;
|
|
22
|
-
|
|
23
|
-
ok(data: T): ResponseBuilder<T> {
|
|
24
|
-
this.status = 200;
|
|
25
|
-
this.data = data;
|
|
26
|
-
return this;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
created(data: T): ResponseBuilder<T> {
|
|
30
|
-
this.status = 201;
|
|
31
|
-
this.data = data;
|
|
32
|
-
return this;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
setHeader(key: string, value: string): ResponseBuilder<T> {
|
|
36
|
-
this.headers[key] = value;
|
|
37
|
-
return this;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
setHeaders(headers: Record<string, string>): ResponseBuilder<T> {
|
|
41
|
-
this.headers = { ...this.headers, ...headers };
|
|
42
|
-
return this;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
asFile(): ResponseBuilder<T> {
|
|
46
|
-
this.isFile = true;
|
|
47
|
-
return this;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
build(res: Response): void {
|
|
51
|
-
Object.entries(this.headers).forEach(([key, value]) => {
|
|
52
|
-
res.setHeader(key, value);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
if (this.isFile && this.data) {
|
|
56
|
-
res.status(this.status).send(this.data);
|
|
57
|
-
} else {
|
|
58
|
-
res.status(this.status).json(this.data);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
package/rest.ts
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import 'reflect-metadata';
|
|
2
|
-
import express, { type Express } from 'express';
|
|
3
|
-
import type {
|
|
4
|
-
Request,
|
|
5
|
-
Response,
|
|
6
|
-
NextFunction,
|
|
7
|
-
IRouter,
|
|
8
|
-
RequestHandler,
|
|
9
|
-
} from 'express';
|
|
10
|
-
import { arrayUnify } from './utils/array-unify';
|
|
11
|
-
import { IResponseBuilder } from './response-builder';
|
|
12
|
-
import validationMiddleware from './middlewares/validation.middleware';
|
|
13
|
-
import { authenticatedMiddleware } from './middlewares/authenticated.middleware';
|
|
14
|
-
import { authorizedMiddleware } from './middlewares/authorized.middleware';
|
|
15
|
-
|
|
16
|
-
export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch';
|
|
17
|
-
export const keyOfPath = Symbol('path');
|
|
18
|
-
export const keyOfRouteOptions = Symbol('routeOptions');
|
|
19
|
-
export const keyOfReq = Symbol('req');
|
|
20
|
-
export const keyOfRes = Symbol('res');
|
|
21
|
-
export const keyOfNext = Symbol('next');
|
|
22
|
-
|
|
23
|
-
// Controller method signature type'ı
|
|
24
|
-
export type ControllerMethodSignature = (
|
|
25
|
-
...args: (Request | Response | NextFunction)[]
|
|
26
|
-
) => IResponseBuilder | Promise<IResponseBuilder>;
|
|
27
|
-
export type IValidation = {
|
|
28
|
-
body?: any;
|
|
29
|
-
params?: any;
|
|
30
|
-
query?: any;
|
|
31
|
-
};
|
|
32
|
-
export interface RouteOptions {
|
|
33
|
-
method?: Method;
|
|
34
|
-
path?: string;
|
|
35
|
-
validations?: IValidation[];
|
|
36
|
-
permissions?: string[];
|
|
37
|
-
authenticated?: boolean;
|
|
38
|
-
otherHttpMiddlewares?: RequestHandler[];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function controller(path: string) {
|
|
42
|
-
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
|
|
43
|
-
Reflect.defineMetadata(keyOfPath, path, constructor);
|
|
44
|
-
return constructor;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
export function httpMethod(newOptions: RouteOptions) {
|
|
48
|
-
return function (
|
|
49
|
-
target: any,
|
|
50
|
-
propertyKey: string,
|
|
51
|
-
_descriptor: PropertyDescriptor
|
|
52
|
-
) {
|
|
53
|
-
const existingOptions =
|
|
54
|
-
Reflect.getMetadata(keyOfRouteOptions, target, propertyKey) || {};
|
|
55
|
-
const method = newOptions.method || existingOptions.method;
|
|
56
|
-
const path = newOptions.path || existingOptions.path;
|
|
57
|
-
const validations = arrayUnify(
|
|
58
|
-
(newOptions.validations || []).concat(existingOptions.validations || [])
|
|
59
|
-
);
|
|
60
|
-
const permissions = arrayUnify(
|
|
61
|
-
(newOptions.permissions || []).concat(existingOptions.permissions || [])
|
|
62
|
-
);
|
|
63
|
-
const authenticated =
|
|
64
|
-
newOptions.authenticated || existingOptions.authenticated;
|
|
65
|
-
const otherHttpMiddlewares = arrayUnify(
|
|
66
|
-
(newOptions.otherHttpMiddlewares || []).concat(
|
|
67
|
-
existingOptions.otherHttpMiddlewares || []
|
|
68
|
-
)
|
|
69
|
-
);
|
|
70
|
-
const mergedOptions = {
|
|
71
|
-
method,
|
|
72
|
-
path,
|
|
73
|
-
validations,
|
|
74
|
-
permissions,
|
|
75
|
-
authenticated,
|
|
76
|
-
otherHttpMiddlewares,
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
Reflect.defineMetadata(keyOfRouteOptions, mergedOptions, target, propertyKey);
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
export function get(path: string) {
|
|
83
|
-
return httpMethod({ path, method: 'get' });
|
|
84
|
-
}
|
|
85
|
-
export function post(path: string) {
|
|
86
|
-
return httpMethod({ path, method: 'post' });
|
|
87
|
-
}
|
|
88
|
-
export function put(path: string) {
|
|
89
|
-
return httpMethod({ path, method: 'put' });
|
|
90
|
-
}
|
|
91
|
-
export function del(path: string) {
|
|
92
|
-
return httpMethod({ path, method: 'delete' });
|
|
93
|
-
}
|
|
94
|
-
export function patch(path: string) {
|
|
95
|
-
return httpMethod({ path, method: 'patch' });
|
|
96
|
-
}
|
|
97
|
-
export function validate(options: IValidation | IValidation[]) {
|
|
98
|
-
return httpMethod({
|
|
99
|
-
validations: Array.isArray(options) ? options : [options],
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
export function authenticated(value: boolean = true) {
|
|
103
|
-
return httpMethod({ authenticated: value });
|
|
104
|
-
}
|
|
105
|
-
export function authorized(value: string | string[]) {
|
|
106
|
-
return httpMethod({
|
|
107
|
-
permissions: Array.isArray(value) ? value : [value],
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
export function middleware(middlewares: RequestHandler) {
|
|
111
|
-
return httpMethod({ otherHttpMiddlewares: [middlewares] });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Param decorator'ları
|
|
115
|
-
export function req() {
|
|
116
|
-
return function (target: any, propertyKey: string, parameterIndex: number) {
|
|
117
|
-
Reflect.defineMetadata(keyOfReq, parameterIndex, target, propertyKey);
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function res() {
|
|
122
|
-
return function (target: any, propertyKey: string, parameterIndex: number) {
|
|
123
|
-
Reflect.defineMetadata(keyOfRes, parameterIndex, target, propertyKey);
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
export function next() {
|
|
127
|
-
return function (target: any, propertyKey: string, parameterIndex: number) {
|
|
128
|
-
Reflect.defineMetadata(keyOfNext, parameterIndex, target, propertyKey);
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
export function buildRouterFromController(controllerClass: any): IRouter {
|
|
132
|
-
const path = Reflect.getMetadata(keyOfPath, controllerClass.constructor);
|
|
133
|
-
if (!path) {
|
|
134
|
-
throw new Error('Controller class must have a path property');
|
|
135
|
-
}
|
|
136
|
-
const allProperties = Object.getOwnPropertyNames(
|
|
137
|
-
Object.getPrototypeOf(controllerClass)
|
|
138
|
-
);
|
|
139
|
-
const router = express.Router();
|
|
140
|
-
for (const property of allProperties) {
|
|
141
|
-
const routeOptions: RouteOptions = Reflect.getMetadata(
|
|
142
|
-
keyOfRouteOptions,
|
|
143
|
-
controllerClass,
|
|
144
|
-
property
|
|
145
|
-
);
|
|
146
|
-
if (!routeOptions) {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (!routeOptions.path) {
|
|
150
|
-
throw new Error('Route path is required');
|
|
151
|
-
}
|
|
152
|
-
if (!routeOptions.method) {
|
|
153
|
-
throw new Error('Route method is required');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const validations = routeOptions.validations;
|
|
157
|
-
const permissions = routeOptions.permissions;
|
|
158
|
-
const authenticated = routeOptions.authenticated;
|
|
159
|
-
const otherHttpMiddlewares = routeOptions.otherHttpMiddlewares;
|
|
160
|
-
const handler = controllerClass[property];
|
|
161
|
-
const validationMiddlewares = [];
|
|
162
|
-
if (validations) {
|
|
163
|
-
for (const validation of validations) {
|
|
164
|
-
if (validation.body) {
|
|
165
|
-
validationMiddlewares.push(validationMiddleware(validation.body, 'body'));
|
|
166
|
-
}
|
|
167
|
-
if (validation.params) {
|
|
168
|
-
validationMiddlewares.push(
|
|
169
|
-
validationMiddleware(validation.params, 'params')
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
if (validation.query) {
|
|
173
|
-
validationMiddlewares.push(
|
|
174
|
-
validationMiddleware(validation.query, 'query')
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const middlewares: RequestHandler[] = [];
|
|
180
|
-
if (authenticated) {
|
|
181
|
-
middlewares.push(authenticatedMiddleware as unknown as RequestHandler);
|
|
182
|
-
}
|
|
183
|
-
if (permissions) {
|
|
184
|
-
middlewares.push(
|
|
185
|
-
authorizedMiddleware(permissions) as unknown as RequestHandler
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
if (otherHttpMiddlewares) {
|
|
189
|
-
middlewares.push(...otherHttpMiddlewares);
|
|
190
|
-
}
|
|
191
|
-
if (validationMiddlewares) {
|
|
192
|
-
middlewares.push(...validationMiddlewares);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const method = routeOptions.method;
|
|
196
|
-
const routePath = routeOptions.path;
|
|
197
|
-
const reqIndex = Reflect.getMetadata(keyOfReq, controllerClass, property);
|
|
198
|
-
const resIndex = Reflect.getMetadata(keyOfRes, controllerClass, property);
|
|
199
|
-
const nextIndex = Reflect.getMetadata(keyOfNext, controllerClass, property);
|
|
200
|
-
const argsNotSorted = [
|
|
201
|
-
{ name: 'req', index: reqIndex },
|
|
202
|
-
{ name: 'res', index: resIndex },
|
|
203
|
-
{ name: 'next', index: nextIndex },
|
|
204
|
-
];
|
|
205
|
-
const args = [...argsNotSorted];
|
|
206
|
-
const argsSorted = args.sort((a, b) => a.index - b.index);
|
|
207
|
-
const handlerMiddleware = async (
|
|
208
|
-
req: Request,
|
|
209
|
-
res: Response,
|
|
210
|
-
next: NextFunction
|
|
211
|
-
) => {
|
|
212
|
-
try {
|
|
213
|
-
const realArgs = [];
|
|
214
|
-
for (const arg of argsSorted) {
|
|
215
|
-
if (arg.name === 'req') {
|
|
216
|
-
realArgs.push(req);
|
|
217
|
-
} else if (arg.name === 'res') {
|
|
218
|
-
realArgs.push(res);
|
|
219
|
-
} else if (arg.name === 'next') {
|
|
220
|
-
realArgs.push(next);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const result = await handler(...realArgs);
|
|
224
|
-
|
|
225
|
-
// ResponseBuilder'ı handle et
|
|
226
|
-
if (result && typeof result.build === 'function') {
|
|
227
|
-
result.build(res);
|
|
228
|
-
} else {
|
|
229
|
-
res.json(result);
|
|
230
|
-
}
|
|
231
|
-
} catch (error) {
|
|
232
|
-
next(error);
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
router[method](
|
|
236
|
-
routePath,
|
|
237
|
-
...middlewares,
|
|
238
|
-
handlerMiddleware as RequestHandler
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
return router;
|
|
242
|
-
}
|
|
243
|
-
export function buildApp(app: Express, controllers: any[]) {
|
|
244
|
-
for (const controller of controllers) {
|
|
245
|
-
const router = buildRouterFromController(controller);
|
|
246
|
-
const controllerPath = Reflect.getMetadata(keyOfPath, controller.constructor);
|
|
247
|
-
if (controllerPath) {
|
|
248
|
-
app.use(controllerPath, router);
|
|
249
|
-
} else {
|
|
250
|
-
app.use(router);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return app;
|
|
254
|
-
}
|
package/swagger.ts
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
import 'reflect-metadata';
|
|
2
|
-
import swaggerUi from 'swagger-ui-express';
|
|
3
|
-
import { Express } from 'express';
|
|
4
|
-
import { keyOfPath, keyOfRouteOptions, RouteOptions } from './rest';
|
|
5
|
-
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
|
|
6
|
-
|
|
7
|
-
export interface SwaggerOptions {
|
|
8
|
-
title?: string;
|
|
9
|
-
description?: string;
|
|
10
|
-
version?: string;
|
|
11
|
-
servers?: { url: string; description?: string }[];
|
|
12
|
-
docsPath?: string;
|
|
13
|
-
jsonPath?: string;
|
|
14
|
-
components?: any;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class SwaggerIntegration {
|
|
18
|
-
private swaggerSpec: any;
|
|
19
|
-
private options: SwaggerOptions;
|
|
20
|
-
|
|
21
|
-
constructor(options: SwaggerOptions = {}) {
|
|
22
|
-
this.options = {
|
|
23
|
-
title: 'Mini Framework API',
|
|
24
|
-
description: 'API documentation for Mini Framework',
|
|
25
|
-
version: '1.0.0',
|
|
26
|
-
servers: [
|
|
27
|
-
{ url: 'http://localhost:3000', description: 'Development server' },
|
|
28
|
-
],
|
|
29
|
-
docsPath: '/api-docs',
|
|
30
|
-
jsonPath: '/api-docs.json',
|
|
31
|
-
...options,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public generateSwaggerSpec(controllers: any[]) {
|
|
36
|
-
const paths: any = {};
|
|
37
|
-
const components: any = {
|
|
38
|
-
securitySchemes: {
|
|
39
|
-
bearerAuth: {
|
|
40
|
-
type: 'http',
|
|
41
|
-
scheme: 'bearer',
|
|
42
|
-
bearerFormat: 'JWT',
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
schemas: validationMetadatasToSchemas(),
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
controllers.forEach((controller, index) => {
|
|
49
|
-
const controllerPath = Reflect.getMetadata(
|
|
50
|
-
keyOfPath,
|
|
51
|
-
controller.constructor
|
|
52
|
-
);
|
|
53
|
-
if (!controllerPath) {
|
|
54
|
-
console.log(`❌ No path metadata found for ${controller.constructor.name}`);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const allProperties = Object.getOwnPropertyNames(
|
|
59
|
-
Object.getPrototypeOf(controller)
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
allProperties.forEach((property) => {
|
|
63
|
-
const routeOptions: RouteOptions = Reflect.getMetadata(
|
|
64
|
-
keyOfRouteOptions,
|
|
65
|
-
controller,
|
|
66
|
-
property
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
if (!routeOptions || !routeOptions.path || !routeOptions.method) {
|
|
70
|
-
if (property !== 'constructor') {
|
|
71
|
-
console.log(`⚠️ Skipping ${property} - no valid route options`);
|
|
72
|
-
}
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const fullPath = controllerPath + routeOptions.path;
|
|
77
|
-
const method = routeOptions.method.toLowerCase();
|
|
78
|
-
if (!paths[fullPath]) {
|
|
79
|
-
paths[fullPath] = {};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Generate OpenAPI operation
|
|
83
|
-
const operation: any = {
|
|
84
|
-
summary: this.generateSummary(method, fullPath),
|
|
85
|
-
description: this.generateDescription(method, fullPath),
|
|
86
|
-
tags: [this.extractControllerTag(controllerPath)],
|
|
87
|
-
responses: {
|
|
88
|
-
'200': {
|
|
89
|
-
description: 'Success',
|
|
90
|
-
content: {
|
|
91
|
-
'application/json': {
|
|
92
|
-
schema: {
|
|
93
|
-
type: 'object',
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// Add parameters from path
|
|
102
|
-
const pathParams = this.extractPathParameters(routeOptions.path);
|
|
103
|
-
if (pathParams.length > 0) {
|
|
104
|
-
operation.parameters = pathParams.map((param) => ({
|
|
105
|
-
name: param,
|
|
106
|
-
in: 'path',
|
|
107
|
-
required: true,
|
|
108
|
-
schema: {
|
|
109
|
-
type: 'string',
|
|
110
|
-
},
|
|
111
|
-
}));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Add request body for POST/PUT/PATCH
|
|
115
|
-
if (['post', 'put', 'patch'].includes(method) && routeOptions.validations) {
|
|
116
|
-
const bodyValidation = routeOptions.validations?.find((v) => v.body);
|
|
117
|
-
if (bodyValidation) {
|
|
118
|
-
operation.requestBody = {
|
|
119
|
-
required: true,
|
|
120
|
-
content: {
|
|
121
|
-
'application/json': {
|
|
122
|
-
schema: this.generateSchemaFromValidation(bodyValidation.body),
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Add security if authenticated
|
|
130
|
-
if (routeOptions.authenticated) {
|
|
131
|
-
operation.security = [{ bearerAuth: [] }];
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Add error responses
|
|
135
|
-
if (routeOptions.authenticated) {
|
|
136
|
-
operation.responses['401'] = {
|
|
137
|
-
description: 'Unauthorized',
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (routeOptions.permissions && routeOptions.permissions.length > 0) {
|
|
142
|
-
operation.responses['403'] = {
|
|
143
|
-
description: 'Forbidden',
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
operation.responses['400'] = {
|
|
148
|
-
description: 'Bad Request',
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
paths[fullPath][method] = operation;
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
this.swaggerSpec = {
|
|
155
|
-
openapi: '3.0.0',
|
|
156
|
-
info: {
|
|
157
|
-
title: this.options.title!,
|
|
158
|
-
description: this.options.description!,
|
|
159
|
-
version: this.options.version!,
|
|
160
|
-
contact: {
|
|
161
|
-
name: 'API Support',
|
|
162
|
-
email: 'support@example.com',
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
servers: this.options.servers,
|
|
166
|
-
paths,
|
|
167
|
-
components,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private generateSummary(method: string, path: string): string {
|
|
172
|
-
const action = method.toUpperCase();
|
|
173
|
-
const resource = this.extractResourceName(path);
|
|
174
|
-
|
|
175
|
-
const actionMap: { [key: string]: string } = {
|
|
176
|
-
GET: path.includes('/:') ? `Get ${resource} by ID` : `Get all ${resource}`,
|
|
177
|
-
POST: `Create ${resource}`,
|
|
178
|
-
PUT: `Update ${resource}`,
|
|
179
|
-
PATCH: `Partially update ${resource}`,
|
|
180
|
-
DELETE: `Delete ${resource}`,
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
return actionMap[action] || `${action} ${resource}`;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private generateDescription(method: string, path: string): string {
|
|
187
|
-
const action = method.toLowerCase();
|
|
188
|
-
const resource = this.extractResourceName(path);
|
|
189
|
-
|
|
190
|
-
const descriptions: { [key: string]: string } = {
|
|
191
|
-
get: path.includes('/:')
|
|
192
|
-
? `Retrieve a specific ${resource} by its ID`
|
|
193
|
-
: `Retrieve all ${resource} records`,
|
|
194
|
-
post: `Create a new ${resource} record`,
|
|
195
|
-
put: `Update an existing ${resource} record`,
|
|
196
|
-
patch: `Partially update an existing ${resource} record`,
|
|
197
|
-
delete: `Delete a ${resource} record`,
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
return descriptions[action] || `${action} operation on ${resource}`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private extractControllerTag(controllerPath: string): string {
|
|
204
|
-
const segments = controllerPath.split('/').filter(Boolean);
|
|
205
|
-
const lastSegment = segments[segments.length - 1];
|
|
206
|
-
return lastSegment.charAt(0).toUpperCase() + lastSegment.slice(1);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private extractResourceName(path: string): string {
|
|
210
|
-
const segments = path.split('/').filter(Boolean);
|
|
211
|
-
let resource = segments[segments.length - 1];
|
|
212
|
-
|
|
213
|
-
// Remove path parameters (e.g., :id)
|
|
214
|
-
if (resource.startsWith(':')) {
|
|
215
|
-
resource = segments[segments.length - 2] || 'Resource';
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return resource.charAt(0).toUpperCase() + resource.slice(1);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private extractPathParameters(path: string): string[] {
|
|
222
|
-
const matches = path.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
|
|
223
|
-
return matches ? matches.map((match) => match.substring(1)) : [];
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private generateSchemaFromValidation(validationClass: any): any {
|
|
227
|
-
const className = validationClass.name;
|
|
228
|
-
return { $ref: `#/components/schemas/${className}` };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
public setupSwagger(app: Express) {
|
|
232
|
-
// Swagger UI middleware
|
|
233
|
-
app.use(
|
|
234
|
-
this.options.docsPath!,
|
|
235
|
-
swaggerUi.serve,
|
|
236
|
-
swaggerUi.setup(this.swaggerSpec, {
|
|
237
|
-
explorer: true,
|
|
238
|
-
customCss: '.swagger-ui .topbar { display: none }',
|
|
239
|
-
customSiteTitle: this.options.title,
|
|
240
|
-
swaggerOptions: {
|
|
241
|
-
docExpansion: 'list',
|
|
242
|
-
filter: true,
|
|
243
|
-
showRequestHeaders: true,
|
|
244
|
-
tryItOutEnabled: true,
|
|
245
|
-
persistAuthorization: true,
|
|
246
|
-
},
|
|
247
|
-
})
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
// JSON endpoint for OpenAPI spec
|
|
251
|
-
app.get(this.options.jsonPath!, (req, res) => {
|
|
252
|
-
res.setHeader('Content-Type', 'application/json');
|
|
253
|
-
res.send(this.swaggerSpec);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
console.log(`📚 Swagger UI available at: ${this.options.docsPath}`);
|
|
257
|
-
console.log(`📄 OpenAPI JSON spec available at: ${this.options.jsonPath}`);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
public getSwaggerSpec() {
|
|
261
|
-
return this.swaggerSpec;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export default SwaggerIntegration;
|
package/types.ts
DELETED
package/utils/array-unify.ts
DELETED
package/utils/math.ts
DELETED