@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.
Files changed (47) hide show
  1. package/dist/app/console/generate/GenerateApiDocsCommand.d.mts +4 -1
  2. package/dist/app/console/generate/GenerateApiDocsCommand.mjs +56 -4
  3. package/dist/app/console/generate/GenerateApiDocsCommand.mjs.map +1 -1
  4. package/dist/app/console/project/CreateProjectCommand.mjs +27 -2
  5. package/dist/app/console/project/CreateProjectCommand.mjs.map +1 -1
  6. package/dist/app/console/project/base_project/package.json.tpl +7 -2
  7. package/dist/app/console/project/base_project/src/app/queues/index.ts.tpl +9 -0
  8. package/dist/app/console/project/base_project/src/config/caches.ts.tpl +2 -4
  9. package/dist/app/console/project/base_project/src/config/databases.ts.tpl +7 -9
  10. package/dist/app/console/project/base_project/src/config/default.mts.tpl +17 -3
  11. package/dist/app/console/project/base_project/src/config/loggers.ts.tpl +6 -8
  12. package/dist/app/console/project/base_project/src/config/mailer.ts.tpl +15 -16
  13. package/dist/app/console/project/base_project/src/config/queues.ts.tpl +2 -4
  14. package/dist/app/console/project/base_project/src/config/storages.ts.tpl +5 -4
  15. package/dist/app/console/project/base_project/src/helpers/QueryKit.ts.tpl +175 -0
  16. package/dist/app/console/project/base_project/src/helpers/index.ts.tpl +96 -0
  17. package/dist/app/console/project/base_project/src/helpers/validation.ts.tpl +26 -0
  18. package/dist/app/console/project/base_project/src/initialize.ts.tpl +64 -7
  19. package/dist/app/console/project/base_project/src/middlewares.ts.tpl +1 -1
  20. package/dist/bin/app/console/DefaultCommand.cjs +0 -19
  21. package/dist/bin/app/console/KeyGenerateCommand.cjs +0 -19
  22. package/dist/bin/app/console/StartCommand.cjs +0 -19
  23. package/dist/bin/app/console/generate/GenerateApiDocsCommand.cjs +62 -24
  24. package/dist/bin/app/console/generate/GenerateControllerCommand.cjs +0 -19
  25. package/dist/bin/app/console/generate/index.cjs +62 -24
  26. package/dist/bin/app/console/index.cjs +89 -26
  27. package/dist/bin/app/console/migrate/GenerateMigrateCommand.cjs +0 -19
  28. package/dist/bin/app/console/migrate/MigrateCommand.cjs +0 -19
  29. package/dist/bin/app/console/migrate/MigrateRollbackCommand.cjs +0 -19
  30. package/dist/bin/app/console/migrate/index.cjs +0 -19
  31. package/dist/bin/app/console/project/CreateProjectCommand.cjs +27 -2
  32. package/dist/bin/app/console/queue/GenerateQueueMigrateCommand.cjs +0 -19
  33. package/dist/bin/bin/pashmak_cli.cjs +27 -2
  34. package/dist/bin/cache.cjs +0 -19
  35. package/dist/bin/facades.cjs +0 -19
  36. package/dist/bin/factories.cjs +0 -19
  37. package/dist/bin/index.cjs +96 -29
  38. package/dist/bin/middlewares.cjs +0 -19
  39. package/dist/bin/queue.cjs +0 -19
  40. package/dist/bin/router.cjs +0 -9
  41. package/dist/facades.mjs +0 -19
  42. package/dist/facades.mjs.map +1 -1
  43. package/dist/router.d.mts +1 -3
  44. package/dist/router.mjs +1 -9
  45. package/dist/router.mjs.map +1 -1
  46. package/package.json +12 -11
  47. 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 { bootstrap } from "@devbro/pashmak";
2
- import { dirname } from "path";
3
- import { fileURLToPath } from "url";
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
- console.log("Registering service providers...");
10
- await import(`./app/console`);
11
- await import(`./routes`);
12
- await import(`./schedules`);
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();
@@ -8,7 +8,7 @@ export async function loggerMiddleware(
8
8
  next: () => Promise<void>,
9
9
  ): Promise<void> {
10
10
  logger().info({
11
- msg: "available context",
11
+ msg: "Incoming Http Request",
12
12
  keys: ctx().keys(),
13
13
  route: req.url,
14
14
  });
@@ -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(config3 = {}) {
548
- this.config = { ...this.config, ...config3 };
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
- const publicDir = import_path2.default.join(rootDir, "public");
835
- await fs.mkdir(publicDir, { recursive: true });
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(config4 = {}) {
549
- this.config = { ...this.config, ...config4 };
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
- const publicDir = import_path3.default.join(rootDir, "public");
889
- await fs2.mkdir(publicDir, { recursive: true });
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;