@devbro/pashmak 0.1.43 → 0.1.45
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/dist/app/console/generate/GenerateApiDocsCommand.d.mts +4 -1
- package/dist/app/console/generate/GenerateApiDocsCommand.mjs +56 -4
- package/dist/app/console/generate/GenerateApiDocsCommand.mjs.map +1 -1
- package/dist/app/console/project/CreateProjectCommand.mjs +27 -2
- package/dist/app/console/project/CreateProjectCommand.mjs.map +1 -1
- package/dist/app/console/project/base_project/package.json.tpl +7 -2
- package/dist/app/console/project/base_project/src/app/queues/index.ts.tpl +9 -0
- package/dist/app/console/project/base_project/src/config/caches.ts.tpl +2 -4
- package/dist/app/console/project/base_project/src/config/databases.ts.tpl +7 -9
- package/dist/app/console/project/base_project/src/config/default.mts.tpl +17 -3
- package/dist/app/console/project/base_project/src/config/loggers.ts.tpl +6 -8
- package/dist/app/console/project/base_project/src/config/mailer.ts.tpl +15 -16
- package/dist/app/console/project/base_project/src/config/queues.ts.tpl +2 -4
- package/dist/app/console/project/base_project/src/config/storages.ts.tpl +5 -4
- package/dist/app/console/project/base_project/src/helpers/QueryKit.ts.tpl +175 -0
- package/dist/app/console/project/base_project/src/helpers/index.ts.tpl +96 -0
- package/dist/app/console/project/base_project/src/helpers/validation.ts.tpl +26 -0
- package/dist/app/console/project/base_project/src/initialize.ts.tpl +64 -7
- package/dist/app/console/project/base_project/src/middlewares.ts.tpl +1 -1
- package/dist/bin/app/console/DefaultCommand.cjs +0 -19
- package/dist/bin/app/console/KeyGenerateCommand.cjs +0 -19
- package/dist/bin/app/console/StartCommand.cjs +0 -19
- package/dist/bin/app/console/generate/GenerateApiDocsCommand.cjs +62 -24
- package/dist/bin/app/console/generate/GenerateControllerCommand.cjs +0 -19
- package/dist/bin/app/console/generate/index.cjs +62 -24
- package/dist/bin/app/console/index.cjs +89 -26
- package/dist/bin/app/console/migrate/GenerateMigrateCommand.cjs +0 -19
- package/dist/bin/app/console/migrate/MigrateCommand.cjs +0 -19
- package/dist/bin/app/console/migrate/MigrateRollbackCommand.cjs +0 -19
- package/dist/bin/app/console/migrate/index.cjs +0 -19
- package/dist/bin/app/console/project/CreateProjectCommand.cjs +27 -2
- package/dist/bin/app/console/queue/GenerateQueueMigrateCommand.cjs +0 -19
- package/dist/bin/bin/pashmak_cli.cjs +27 -2
- package/dist/bin/cache.cjs +0 -19
- package/dist/bin/facades.cjs +0 -19
- package/dist/bin/factories.cjs +0 -19
- package/dist/bin/index.cjs +96 -29
- package/dist/bin/middlewares.cjs +0 -19
- package/dist/bin/queue.cjs +0 -19
- package/dist/bin/router.cjs +0 -9
- package/dist/facades.mjs +0 -19
- package/dist/facades.mjs.map +1 -1
- package/dist/router.d.mts +1 -3
- package/dist/router.mjs +1 -9
- package/dist/router.mjs.map +1 -1
- package/package.json +12 -11
- package/dist/app/console/project/base_project/src/helpers.ts.tpl +0 -28
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { HttpBadRequestError, HttpForbiddenError } from '@devbro/pashmak/http';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
import { config } from '@devbro/pashmak/config';
|
|
4
|
+
import { BaseModel, RelationshipManagerMtoM } from '@devbro/pashmak/orm';
|
|
5
|
+
import { ctx } from '@devbro/pashmak/context';
|
|
6
|
+
import { logger } from '@devbro/pashmak/facades';
|
|
7
|
+
|
|
8
|
+
export function createJwtToken(data: any, token_params: jwt.SignOptions = {}) {
|
|
9
|
+
const secret = config.get('jwt.secret') as string;
|
|
10
|
+
const token_params2 = config.get('jwt.options') as jwt.SignOptions;
|
|
11
|
+
const token = jwt.sign(data, secret, { ...token_params2, ...token_params });
|
|
12
|
+
|
|
13
|
+
if (!token) {
|
|
14
|
+
throw new Error('Unable to sign token !!');
|
|
15
|
+
}
|
|
16
|
+
return token;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function decodeJwtToken(token: string) {
|
|
20
|
+
if (await jwt.verify(token, config.get('jwt.public'))) {
|
|
21
|
+
return await jwt.decode(token);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (await jwt.verify(token, config.get('jwt.public_retired'))) {
|
|
25
|
+
return await jwt.decode(token);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
throw new HttpBadRequestError('bad token. invalid, expired, or signed with wrong key.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* create two lists of what is to add and what to remove to convert currentRelations to newRelations
|
|
33
|
+
* @param currentRelations the base list
|
|
34
|
+
* @param newRelations the target list
|
|
35
|
+
* @param compare_method a method to compare two items, returns true if item is not in the list
|
|
36
|
+
* @returns { toAdd: T[]; toRemove: T[] }
|
|
37
|
+
*/
|
|
38
|
+
export function diffLists<T extends BaseModel>(
|
|
39
|
+
currentRelations: T[],
|
|
40
|
+
newRelations: T[],
|
|
41
|
+
compare_method: (item: T, list: T[]) => boolean = (item: T, list: T[]) => !list.some((c: T) => c.id === item.id)
|
|
42
|
+
): { toAdd: T[]; toRemove: T[] } {
|
|
43
|
+
const toAdd = newRelations.filter((r: T) => compare_method(r, currentRelations));
|
|
44
|
+
const toRemove = currentRelations.filter((r: T) => compare_method(r, newRelations));
|
|
45
|
+
return { toAdd, toRemove };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function syncManyToMany<T1 extends BaseModel, T2 extends BaseModel>(
|
|
49
|
+
rel_manager: RelationshipManagerMtoM<T1, T2>,
|
|
50
|
+
currentRelations: T2[],
|
|
51
|
+
newRelations: T2[]
|
|
52
|
+
) {
|
|
53
|
+
const { toAdd, toRemove } = diffLists(currentRelations, newRelations);
|
|
54
|
+
|
|
55
|
+
await rel_manager.dissociate(toRemove);
|
|
56
|
+
await rel_manager.associate(toAdd);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getAuthenticatedUser(): {
|
|
60
|
+
id: number;
|
|
61
|
+
can: (permission: string) => boolean;
|
|
62
|
+
canOrFail: (permissions: string) => boolean;
|
|
63
|
+
getScope: (permission: string) => any;
|
|
64
|
+
} {
|
|
65
|
+
return ctx().get('auth_user');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type VarOrArray<T> = T | T[];
|
|
69
|
+
|
|
70
|
+
export function requirePermissions(required_permissions: VarOrArray<string>): MethodDecorator {
|
|
71
|
+
return function (_target: any, _propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
72
|
+
const originalMethod = descriptor.value!;
|
|
73
|
+
|
|
74
|
+
descriptor.value = async function (...args: any[]) {
|
|
75
|
+
let auth_user = getAuthenticatedUser();
|
|
76
|
+
|
|
77
|
+
const permissionsArray = Array.isArray(required_permissions) ? required_permissions : [required_permissions];
|
|
78
|
+
|
|
79
|
+
let user_can_any = false;
|
|
80
|
+
for (let permission of permissionsArray) {
|
|
81
|
+
if (auth_user.can(permission)) {
|
|
82
|
+
user_can_any = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!user_can_any) {
|
|
87
|
+
logger().warn('Permission denied', {
|
|
88
|
+
user_id: auth_user.id,
|
|
89
|
+
required_permissions: permissionsArray,
|
|
90
|
+
});
|
|
91
|
+
throw new HttpForbiddenError();
|
|
92
|
+
}
|
|
93
|
+
return originalMethod.apply(this, args);
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ctx } from '@devbro/pashmak/context';
|
|
2
|
+
import { Request, createParamDecorator } from '@devbro/pashmak/router';
|
|
3
|
+
{{#if (eq validation_library "yup")}}import * as yup from 'yup';{{/if}}{{#if (eq validation_library "zod")}}import { z } from 'zod';{{/if}}
|
|
4
|
+
|
|
5
|
+
export function ValidatedRequest(
|
|
6
|
+
validationRules: {{#if (eq validation_library "yup")}} yup.ObjectSchema<any> | (() => yup.ObjectSchema<any>) {{/if}} {{#if (eq validation_library "zod")}} z.ZodType<any> | (() => z.ZodType<any>) {{/if}}
|
|
7
|
+
): ParameterDecorator {
|
|
8
|
+
return createParamDecorator(async () => {
|
|
9
|
+
const schema = typeof validationRules === "function" ? validationRules() : validationRules;
|
|
10
|
+
const requestBody = ctx().get<Request>("request").body;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
{{#if (eq validation_library "zod")}}
|
|
14
|
+
return await schema.parseAsync(requestBody);
|
|
15
|
+
{{/if}}
|
|
16
|
+
|
|
17
|
+
{{#if (eq validation_library "yup")}}
|
|
18
|
+
// treat it as Yup schema
|
|
19
|
+
const rc = await (schema as yup.ObjectSchema<any>)
|
|
20
|
+
.noUnknown()
|
|
21
|
+
.validate(requestBody, { abortEarly: false });
|
|
22
|
+
|
|
23
|
+
return rc;
|
|
24
|
+
{{/if}}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -1,12 +1,69 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
dotenv.config();
|
|
3
|
+
|
|
4
|
+
import { bootstrap } from '@devbro/pashmak';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { loadConfig } from '@devbro/pashmak/config';
|
|
8
|
+
import { httpServer, logger, mailer } from '@devbro/pashmak/facades';
|
|
9
|
+
import { startQueueListeners } from '@/app/queues';
|
|
10
|
+
import { HttpError } from '@devbro/pashmak/http';
|
|
11
|
+
{{#if (eq validation_library "yup")}}import * as yup from 'yup';{{/if}}{{#if (eq validation_library "zod")}}import { z, ZodError } from 'zod';{{/if}}
|
|
12
|
+
|
|
13
|
+
import './app/console';
|
|
14
|
+
import './routes';
|
|
15
|
+
import './schedules';
|
|
16
|
+
|
|
17
|
+
const config_data = await loadConfig(dirname(fileURLToPath(import.meta.url)) + '/config/default');
|
|
4
18
|
|
|
5
19
|
await bootstrap({
|
|
6
20
|
root_dir: dirname(fileURLToPath(import.meta.url)),
|
|
21
|
+
config_data,
|
|
7
22
|
});
|
|
8
23
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
httpServer().setErrorHandler(async (err: Error, req: any, res: any) => {
|
|
25
|
+
if (err instanceof HttpError) {
|
|
26
|
+
res.writeHead(err.statusCode, { "Content-Type": "application/json" });
|
|
27
|
+
res.end(JSON.stringify({ message: err.message, error: err.code }));
|
|
28
|
+
logger().warn({ msg: "HttpError: " + err.message, err });
|
|
29
|
+
return;
|
|
30
|
+
{{#if (eq validation_library "zod")}}
|
|
31
|
+
} else if (err instanceof ZodError) {
|
|
32
|
+
res.writeHead(422, { "Content-Type": "application/json" });
|
|
33
|
+
const { errors } = z.treeifyError(err);
|
|
34
|
+
|
|
35
|
+
res.end(JSON.stringify({ message: "validation error", errors: errors }));
|
|
36
|
+
logger().warn({ msg: "ZodError: " + err.message, err });
|
|
37
|
+
return;
|
|
38
|
+
{{/if}}
|
|
39
|
+
{{#if (eq validation_library "yup")}}
|
|
40
|
+
} else if (err instanceof yup.ValidationError) {
|
|
41
|
+
res.writeHead(422, { "Content-Type": "application/json" });
|
|
42
|
+
const errs: any = {};
|
|
43
|
+
err.inner.forEach((e: yup.ValidationError) => {
|
|
44
|
+
// Sanitize sensitive fields
|
|
45
|
+
const sanitizedParams = { ...e.params };
|
|
46
|
+
if (/passw/i.test(e.path!)) {
|
|
47
|
+
sanitizedParams.value = "******";
|
|
48
|
+
sanitizedParams.originalValue = "******";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
errs[e.path!] = {
|
|
52
|
+
type: e.type,
|
|
53
|
+
message: e.message,
|
|
54
|
+
params: sanitizedParams,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
res.end(JSON.stringify({ message: "validation error", errors: errs }));
|
|
59
|
+
logger().warn({ msg: "ValidationError: " + err.message, err });
|
|
60
|
+
return;
|
|
61
|
+
{{/if}}
|
|
62
|
+
} else {
|
|
63
|
+
logger().error({ msg: "Error: " + err.message, err });
|
|
64
|
+
}
|
|
65
|
+
res.writeHead(500, { "Content-Type": "" });
|
|
66
|
+
res.end(JSON.stringify({ error: "Internal Server Error" }));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
startQueueListeners();
|
|
@@ -473,7 +473,6 @@ var http_exports = {};
|
|
|
473
473
|
__reExport(http_exports, require("@devbro/neko-http"));
|
|
474
474
|
|
|
475
475
|
// src/facades.mts
|
|
476
|
-
var yup = __toESM(require("yup"), 1);
|
|
477
476
|
var import_neko_logger = require("@devbro/neko-logger");
|
|
478
477
|
|
|
479
478
|
// src/factories.mts
|
|
@@ -686,24 +685,6 @@ var httpServer = (0, import_neko_helper2.createSingleton)(() => {
|
|
|
686
685
|
res.end(JSON.stringify({ message: err.message, error: err.code }));
|
|
687
686
|
logger().warn({ msg: "HttpError: " + err.message, err });
|
|
688
687
|
return;
|
|
689
|
-
} else if (err instanceof yup.ValidationError) {
|
|
690
|
-
res.writeHead(422, { "Content-Type": "application/json" });
|
|
691
|
-
const errs = {};
|
|
692
|
-
err.inner.forEach((e) => {
|
|
693
|
-
const sanitizedParams = { ...e.params };
|
|
694
|
-
if (/passw/i.test(e.path)) {
|
|
695
|
-
sanitizedParams.value = "******";
|
|
696
|
-
sanitizedParams.originalValue = "******";
|
|
697
|
-
}
|
|
698
|
-
errs[e.path] = {
|
|
699
|
-
type: e.type,
|
|
700
|
-
message: e.message,
|
|
701
|
-
params: sanitizedParams
|
|
702
|
-
};
|
|
703
|
-
});
|
|
704
|
-
res.end(JSON.stringify({ message: "validation error", errors: errs }));
|
|
705
|
-
logger().warn({ msg: "ValidationError: " + err.message, err });
|
|
706
|
-
return;
|
|
707
688
|
} else {
|
|
708
689
|
logger().error({ msg: "Error: " + err.message, err });
|
|
709
690
|
}
|
|
@@ -476,7 +476,6 @@ var http_exports = {};
|
|
|
476
476
|
__reExport(http_exports, require("@devbro/neko-http"));
|
|
477
477
|
|
|
478
478
|
// src/facades.mts
|
|
479
|
-
var yup = __toESM(require("yup"), 1);
|
|
480
479
|
var import_neko_logger = require("@devbro/neko-logger");
|
|
481
480
|
|
|
482
481
|
// src/factories.mts
|
|
@@ -689,24 +688,6 @@ var httpServer = (0, import_neko_helper2.createSingleton)(() => {
|
|
|
689
688
|
res.end(JSON.stringify({ message: err.message, error: err.code }));
|
|
690
689
|
logger().warn({ msg: "HttpError: " + err.message, err });
|
|
691
690
|
return;
|
|
692
|
-
} else if (err instanceof yup.ValidationError) {
|
|
693
|
-
res.writeHead(422, { "Content-Type": "application/json" });
|
|
694
|
-
const errs = {};
|
|
695
|
-
err.inner.forEach((e) => {
|
|
696
|
-
const sanitizedParams = { ...e.params };
|
|
697
|
-
if (/passw/i.test(e.path)) {
|
|
698
|
-
sanitizedParams.value = "******";
|
|
699
|
-
sanitizedParams.originalValue = "******";
|
|
700
|
-
}
|
|
701
|
-
errs[e.path] = {
|
|
702
|
-
type: e.type,
|
|
703
|
-
message: e.message,
|
|
704
|
-
params: sanitizedParams
|
|
705
|
-
};
|
|
706
|
-
});
|
|
707
|
-
res.end(JSON.stringify({ message: "validation error", errors: errs }));
|
|
708
|
-
logger().warn({ msg: "ValidationError: " + err.message, err });
|
|
709
|
-
return;
|
|
710
691
|
} else {
|
|
711
692
|
logger().error({ msg: "Error: " + err.message, err });
|
|
712
693
|
}
|
|
@@ -474,7 +474,6 @@ var http_exports = {};
|
|
|
474
474
|
__reExport(http_exports, require("@devbro/neko-http"));
|
|
475
475
|
|
|
476
476
|
// src/facades.mts
|
|
477
|
-
var yup = __toESM(require("yup"), 1);
|
|
478
477
|
var import_neko_logger = require("@devbro/neko-logger");
|
|
479
478
|
|
|
480
479
|
// src/factories.mts
|
|
@@ -687,24 +686,6 @@ var httpServer = (0, import_neko_helper2.createSingleton)(() => {
|
|
|
687
686
|
res.end(JSON.stringify({ message: err.message, error: err.code }));
|
|
688
687
|
logger().warn({ msg: "HttpError: " + err.message, err });
|
|
689
688
|
return;
|
|
690
|
-
} else if (err instanceof yup.ValidationError) {
|
|
691
|
-
res.writeHead(422, { "Content-Type": "application/json" });
|
|
692
|
-
const errs = {};
|
|
693
|
-
err.inner.forEach((e) => {
|
|
694
|
-
const sanitizedParams = { ...e.params };
|
|
695
|
-
if (/passw/i.test(e.path)) {
|
|
696
|
-
sanitizedParams.value = "******";
|
|
697
|
-
sanitizedParams.originalValue = "******";
|
|
698
|
-
}
|
|
699
|
-
errs[e.path] = {
|
|
700
|
-
type: e.type,
|
|
701
|
-
message: e.message,
|
|
702
|
-
params: sanitizedParams
|
|
703
|
-
};
|
|
704
|
-
});
|
|
705
|
-
res.end(JSON.stringify({ message: "validation error", errors: errs }));
|
|
706
|
-
logger().warn({ msg: "ValidationError: " + err.message, err });
|
|
707
|
-
return;
|
|
708
689
|
} else {
|
|
709
690
|
logger().error({ msg: "Error: " + err.message, err });
|
|
710
691
|
}
|
|
@@ -472,7 +472,6 @@ var http_exports = {};
|
|
|
472
472
|
__reExport(http_exports, require("@devbro/neko-http"));
|
|
473
473
|
|
|
474
474
|
// src/facades.mts
|
|
475
|
-
var yup = __toESM(require("yup"), 1);
|
|
476
475
|
var import_neko_logger = require("@devbro/neko-logger");
|
|
477
476
|
|
|
478
477
|
// src/factories.mts
|
|
@@ -544,8 +543,8 @@ var DatabaseTransport = class {
|
|
|
544
543
|
}
|
|
545
544
|
});
|
|
546
545
|
}, "processMessage");
|
|
547
|
-
constructor(
|
|
548
|
-
this.config = { ...this.config, ...
|
|
546
|
+
constructor(config4 = {}) {
|
|
547
|
+
this.config = { ...this.config, ...config4 };
|
|
549
548
|
this.repeater = (0, import_neko_helper.createRepeater)(
|
|
550
549
|
this.processMessage,
|
|
551
550
|
this.config.listen_interval * 1e3
|
|
@@ -685,24 +684,6 @@ var httpServer = (0, import_neko_helper2.createSingleton)(() => {
|
|
|
685
684
|
res.end(JSON.stringify({ message: err.message, error: err.code }));
|
|
686
685
|
logger().warn({ msg: "HttpError: " + err.message, err });
|
|
687
686
|
return;
|
|
688
|
-
} else if (err instanceof yup.ValidationError) {
|
|
689
|
-
res.writeHead(422, { "Content-Type": "application/json" });
|
|
690
|
-
const errs = {};
|
|
691
|
-
err.inner.forEach((e) => {
|
|
692
|
-
const sanitizedParams = { ...e.params };
|
|
693
|
-
if (/passw/i.test(e.path)) {
|
|
694
|
-
sanitizedParams.value = "******";
|
|
695
|
-
sanitizedParams.originalValue = "******";
|
|
696
|
-
}
|
|
697
|
-
errs[e.path] = {
|
|
698
|
-
type: e.type,
|
|
699
|
-
message: e.message,
|
|
700
|
-
params: sanitizedParams
|
|
701
|
-
};
|
|
702
|
-
});
|
|
703
|
-
res.end(JSON.stringify({ message: "validation error", errors: errs }));
|
|
704
|
-
logger().warn({ msg: "ValidationError: " + err.message, err });
|
|
705
|
-
return;
|
|
706
687
|
} else {
|
|
707
688
|
logger().error({ msg: "Error: " + err.message, err });
|
|
708
689
|
}
|
|
@@ -758,6 +739,13 @@ var cache = (0, import_neko_helper2.createSingleton)((label) => {
|
|
|
758
739
|
var import_clipanion2 = require("clipanion");
|
|
759
740
|
var import_path2 = __toESM(require("path"), 1);
|
|
760
741
|
var fs = __toESM(require("fs/promises"), 1);
|
|
742
|
+
|
|
743
|
+
// src/config.mts
|
|
744
|
+
var config_exports = {};
|
|
745
|
+
__reExport(config_exports, require("@devbro/neko-config"));
|
|
746
|
+
|
|
747
|
+
// src/app/console/generate/GenerateApiDocsCommand.mts
|
|
748
|
+
var import_neko_helper3 = require("@devbro/neko-helper");
|
|
761
749
|
var GenerateApiDocsCommand = class extends import_clipanion2.Command {
|
|
762
750
|
static {
|
|
763
751
|
__name(this, "GenerateApiDocsCommand");
|
|
@@ -766,7 +754,44 @@ var GenerateApiDocsCommand = class extends import_clipanion2.Command {
|
|
|
766
754
|
[`make`, `apidocs`],
|
|
767
755
|
[`generate`, `apidocs`]
|
|
768
756
|
];
|
|
757
|
+
static usage = import_clipanion2.Command.Usage({
|
|
758
|
+
category: `Generate`,
|
|
759
|
+
description: `Generate OpenAPI documentation from routes`,
|
|
760
|
+
details: `
|
|
761
|
+
This command generates OpenAPI 3.0 specification documentation by analyzing
|
|
762
|
+
your application's routes and merging with example files.
|
|
763
|
+
|
|
764
|
+
The generated documentation includes:
|
|
765
|
+
- All registered routes with their HTTP methods
|
|
766
|
+
- Path parameters extracted from route definitions
|
|
767
|
+
- Request body schemas for POST, PUT, and PATCH methods
|
|
768
|
+
- Response schemas
|
|
769
|
+
|
|
770
|
+
The command will merge files specified in config.api_docs.merge_files
|
|
771
|
+
and output the final documentation to config.api_docs.output.
|
|
772
|
+
|
|
773
|
+
This command depends on config data. make sure your default config contains the following:
|
|
774
|
+
api_docs: {
|
|
775
|
+
merge_files: [
|
|
776
|
+
path.join(__dirname, '../..', 'private', 'openapi_examples.json'),
|
|
777
|
+
path.join(__dirname, '../..', 'private', 'openapi_base.json'),
|
|
778
|
+
path.join(__dirname, '../..', 'private', 'openapi_user_changes.json'),
|
|
779
|
+
],
|
|
780
|
+
output: path.join(__dirname, '../..', 'private', 'openapi.json'),
|
|
781
|
+
}
|
|
782
|
+
`,
|
|
783
|
+
examples: [[`Generate API documentation`, `$0 generate apidocs`]]
|
|
784
|
+
});
|
|
785
|
+
help = import_clipanion2.Option.Boolean(`--help,-h`, false, {
|
|
786
|
+
description: `Show help message for this command`
|
|
787
|
+
});
|
|
769
788
|
async execute() {
|
|
789
|
+
if (this.help) {
|
|
790
|
+
this.context.stdout.write(
|
|
791
|
+
this.constructor.usage?.toString() || "No help available\n"
|
|
792
|
+
);
|
|
793
|
+
return 0;
|
|
794
|
+
}
|
|
770
795
|
const rootDir = process.cwd();
|
|
771
796
|
this.context.stdout.write(`Generating OpenAPI documentation...
|
|
772
797
|
`);
|
|
@@ -831,9 +856,8 @@ var GenerateApiDocsCommand = class extends import_clipanion2.Command {
|
|
|
831
856
|
}
|
|
832
857
|
}
|
|
833
858
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const outputPath = import_path2.default.join(publicDir, "openapi.json");
|
|
859
|
+
await fs.mkdir(config_exports.config.get("private_path"), { recursive: true });
|
|
860
|
+
const outputPath = import_path2.default.join(config_exports.config.get("private_path"), "openapi.json");
|
|
837
861
|
await fs.writeFile(
|
|
838
862
|
outputPath,
|
|
839
863
|
JSON.stringify(openApiSpec, null, 2),
|
|
@@ -845,6 +869,20 @@ var GenerateApiDocsCommand = class extends import_clipanion2.Command {
|
|
|
845
869
|
);
|
|
846
870
|
this.context.stdout.write(`Total routes documented: ${routes.length}
|
|
847
871
|
`);
|
|
872
|
+
let files_to_merge = config_exports.config.get("api_docs.merge_files");
|
|
873
|
+
let final_api_docs = {};
|
|
874
|
+
for (let file_path of files_to_merge) {
|
|
875
|
+
let file_json = JSON.parse(await fs.readFile(file_path, "utf8"));
|
|
876
|
+
final_api_docs = import_neko_helper3.Arr.deepMerge(final_api_docs, file_json);
|
|
877
|
+
}
|
|
878
|
+
await fs.writeFile(
|
|
879
|
+
config_exports.config.get("api_docs.output"),
|
|
880
|
+
JSON.stringify(final_api_docs, null, 2)
|
|
881
|
+
);
|
|
882
|
+
this.context.stdout.write(
|
|
883
|
+
`wrote final open api document to : ${config_exports.config.get("api_docs.output")}
|
|
884
|
+
`
|
|
885
|
+
);
|
|
848
886
|
}
|
|
849
887
|
extractParameters(routePath) {
|
|
850
888
|
const paramRegex = /:([a-zA-Z0-9_]+)/g;
|
|
@@ -472,7 +472,6 @@ var http_exports = {};
|
|
|
472
472
|
__reExport(http_exports, require("@devbro/neko-http"));
|
|
473
473
|
|
|
474
474
|
// src/facades.mts
|
|
475
|
-
var yup = __toESM(require("yup"), 1);
|
|
476
475
|
var import_neko_logger = require("@devbro/neko-logger");
|
|
477
476
|
|
|
478
477
|
// src/factories.mts
|
|
@@ -685,24 +684,6 @@ var httpServer = (0, import_neko_helper2.createSingleton)(() => {
|
|
|
685
684
|
res.end(JSON.stringify({ message: err.message, error: err.code }));
|
|
686
685
|
logger().warn({ msg: "HttpError: " + err.message, err });
|
|
687
686
|
return;
|
|
688
|
-
} else if (err instanceof yup.ValidationError) {
|
|
689
|
-
res.writeHead(422, { "Content-Type": "application/json" });
|
|
690
|
-
const errs = {};
|
|
691
|
-
err.inner.forEach((e) => {
|
|
692
|
-
const sanitizedParams = { ...e.params };
|
|
693
|
-
if (/passw/i.test(e.path)) {
|
|
694
|
-
sanitizedParams.value = "******";
|
|
695
|
-
sanitizedParams.originalValue = "******";
|
|
696
|
-
}
|
|
697
|
-
errs[e.path] = {
|
|
698
|
-
type: e.type,
|
|
699
|
-
message: e.message,
|
|
700
|
-
params: sanitizedParams
|
|
701
|
-
};
|
|
702
|
-
});
|
|
703
|
-
res.end(JSON.stringify({ message: "validation error", errors: errs }));
|
|
704
|
-
logger().warn({ msg: "ValidationError: " + err.message, err });
|
|
705
|
-
return;
|
|
706
687
|
} else {
|
|
707
688
|
logger().error({ msg: "Error: " + err.message, err });
|
|
708
689
|
}
|
|
@@ -473,7 +473,6 @@ var http_exports = {};
|
|
|
473
473
|
__reExport(http_exports, require("@devbro/neko-http"));
|
|
474
474
|
|
|
475
475
|
// src/facades.mts
|
|
476
|
-
var yup = __toESM(require("yup"), 1);
|
|
477
476
|
var import_neko_logger = require("@devbro/neko-logger");
|
|
478
477
|
|
|
479
478
|
// src/factories.mts
|
|
@@ -545,8 +544,8 @@ var DatabaseTransport = class {
|
|
|
545
544
|
}
|
|
546
545
|
});
|
|
547
546
|
}, "processMessage");
|
|
548
|
-
constructor(
|
|
549
|
-
this.config = { ...this.config, ...
|
|
547
|
+
constructor(config5 = {}) {
|
|
548
|
+
this.config = { ...this.config, ...config5 };
|
|
550
549
|
this.repeater = (0, import_neko_helper.createRepeater)(
|
|
551
550
|
this.processMessage,
|
|
552
551
|
this.config.listen_interval * 1e3
|
|
@@ -686,24 +685,6 @@ var httpServer = (0, import_neko_helper2.createSingleton)(() => {
|
|
|
686
685
|
res.end(JSON.stringify({ message: err.message, error: err.code }));
|
|
687
686
|
logger().warn({ msg: "HttpError: " + err.message, err });
|
|
688
687
|
return;
|
|
689
|
-
} else if (err instanceof yup.ValidationError) {
|
|
690
|
-
res.writeHead(422, { "Content-Type": "application/json" });
|
|
691
|
-
const errs = {};
|
|
692
|
-
err.inner.forEach((e) => {
|
|
693
|
-
const sanitizedParams = { ...e.params };
|
|
694
|
-
if (/passw/i.test(e.path)) {
|
|
695
|
-
sanitizedParams.value = "******";
|
|
696
|
-
sanitizedParams.originalValue = "******";
|
|
697
|
-
}
|
|
698
|
-
errs[e.path] = {
|
|
699
|
-
type: e.type,
|
|
700
|
-
message: e.message,
|
|
701
|
-
params: sanitizedParams
|
|
702
|
-
};
|
|
703
|
-
});
|
|
704
|
-
res.end(JSON.stringify({ message: "validation error", errors: errs }));
|
|
705
|
-
logger().warn({ msg: "ValidationError: " + err.message, err });
|
|
706
|
-
return;
|
|
707
688
|
} else {
|
|
708
689
|
logger().error({ msg: "Error: " + err.message, err });
|
|
709
690
|
}
|
|
@@ -812,6 +793,13 @@ cli().register(GenerateControllerCommand);
|
|
|
812
793
|
var import_clipanion3 = require("clipanion");
|
|
813
794
|
var import_path3 = __toESM(require("path"), 1);
|
|
814
795
|
var fs2 = __toESM(require("fs/promises"), 1);
|
|
796
|
+
|
|
797
|
+
// src/config.mts
|
|
798
|
+
var config_exports = {};
|
|
799
|
+
__reExport(config_exports, require("@devbro/neko-config"));
|
|
800
|
+
|
|
801
|
+
// src/app/console/generate/GenerateApiDocsCommand.mts
|
|
802
|
+
var import_neko_helper3 = require("@devbro/neko-helper");
|
|
815
803
|
var GenerateApiDocsCommand = class extends import_clipanion3.Command {
|
|
816
804
|
static {
|
|
817
805
|
__name(this, "GenerateApiDocsCommand");
|
|
@@ -820,7 +808,44 @@ var GenerateApiDocsCommand = class extends import_clipanion3.Command {
|
|
|
820
808
|
[`make`, `apidocs`],
|
|
821
809
|
[`generate`, `apidocs`]
|
|
822
810
|
];
|
|
811
|
+
static usage = import_clipanion3.Command.Usage({
|
|
812
|
+
category: `Generate`,
|
|
813
|
+
description: `Generate OpenAPI documentation from routes`,
|
|
814
|
+
details: `
|
|
815
|
+
This command generates OpenAPI 3.0 specification documentation by analyzing
|
|
816
|
+
your application's routes and merging with example files.
|
|
817
|
+
|
|
818
|
+
The generated documentation includes:
|
|
819
|
+
- All registered routes with their HTTP methods
|
|
820
|
+
- Path parameters extracted from route definitions
|
|
821
|
+
- Request body schemas for POST, PUT, and PATCH methods
|
|
822
|
+
- Response schemas
|
|
823
|
+
|
|
824
|
+
The command will merge files specified in config.api_docs.merge_files
|
|
825
|
+
and output the final documentation to config.api_docs.output.
|
|
826
|
+
|
|
827
|
+
This command depends on config data. make sure your default config contains the following:
|
|
828
|
+
api_docs: {
|
|
829
|
+
merge_files: [
|
|
830
|
+
path.join(__dirname, '../..', 'private', 'openapi_examples.json'),
|
|
831
|
+
path.join(__dirname, '../..', 'private', 'openapi_base.json'),
|
|
832
|
+
path.join(__dirname, '../..', 'private', 'openapi_user_changes.json'),
|
|
833
|
+
],
|
|
834
|
+
output: path.join(__dirname, '../..', 'private', 'openapi.json'),
|
|
835
|
+
}
|
|
836
|
+
`,
|
|
837
|
+
examples: [[`Generate API documentation`, `$0 generate apidocs`]]
|
|
838
|
+
});
|
|
839
|
+
help = import_clipanion3.Option.Boolean(`--help,-h`, false, {
|
|
840
|
+
description: `Show help message for this command`
|
|
841
|
+
});
|
|
823
842
|
async execute() {
|
|
843
|
+
if (this.help) {
|
|
844
|
+
this.context.stdout.write(
|
|
845
|
+
this.constructor.usage?.toString() || "No help available\n"
|
|
846
|
+
);
|
|
847
|
+
return 0;
|
|
848
|
+
}
|
|
824
849
|
const rootDir = process.cwd();
|
|
825
850
|
this.context.stdout.write(`Generating OpenAPI documentation...
|
|
826
851
|
`);
|
|
@@ -885,9 +910,8 @@ var GenerateApiDocsCommand = class extends import_clipanion3.Command {
|
|
|
885
910
|
}
|
|
886
911
|
}
|
|
887
912
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
const outputPath = import_path3.default.join(publicDir, "openapi.json");
|
|
913
|
+
await fs2.mkdir(config_exports.config.get("private_path"), { recursive: true });
|
|
914
|
+
const outputPath = import_path3.default.join(config_exports.config.get("private_path"), "openapi.json");
|
|
891
915
|
await fs2.writeFile(
|
|
892
916
|
outputPath,
|
|
893
917
|
JSON.stringify(openApiSpec, null, 2),
|
|
@@ -899,6 +923,20 @@ var GenerateApiDocsCommand = class extends import_clipanion3.Command {
|
|
|
899
923
|
);
|
|
900
924
|
this.context.stdout.write(`Total routes documented: ${routes.length}
|
|
901
925
|
`);
|
|
926
|
+
let files_to_merge = config_exports.config.get("api_docs.merge_files");
|
|
927
|
+
let final_api_docs = {};
|
|
928
|
+
for (let file_path of files_to_merge) {
|
|
929
|
+
let file_json = JSON.parse(await fs2.readFile(file_path, "utf8"));
|
|
930
|
+
final_api_docs = import_neko_helper3.Arr.deepMerge(final_api_docs, file_json);
|
|
931
|
+
}
|
|
932
|
+
await fs2.writeFile(
|
|
933
|
+
config_exports.config.get("api_docs.output"),
|
|
934
|
+
JSON.stringify(final_api_docs, null, 2)
|
|
935
|
+
);
|
|
936
|
+
this.context.stdout.write(
|
|
937
|
+
`wrote final open api document to : ${config_exports.config.get("api_docs.output")}
|
|
938
|
+
`
|
|
939
|
+
);
|
|
902
940
|
}
|
|
903
941
|
extractParameters(routePath) {
|
|
904
942
|
const paramRegex = /:([a-zA-Z0-9_]+)/g;
|