@restura/core 0.1.0-alpha.8 → 0.1.0-alpha.9

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 (38) hide show
  1. package/dist/index.d.mts +133 -13
  2. package/dist/index.d.ts +133 -13
  3. package/dist/index.js +711 -546
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +700 -543
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +8 -5
  8. package/dist/acorn-SW5GI5G7.mjs +0 -3016
  9. package/dist/acorn-SW5GI5G7.mjs.map +0 -1
  10. package/dist/angular-FYEH6QOL.mjs +0 -1547
  11. package/dist/angular-FYEH6QOL.mjs.map +0 -1
  12. package/dist/babel-V6GZHMYX.mjs +0 -6911
  13. package/dist/babel-V6GZHMYX.mjs.map +0 -1
  14. package/dist/chunk-TL4KRYOF.mjs +0 -58
  15. package/dist/chunk-TL4KRYOF.mjs.map +0 -1
  16. package/dist/estree-67ZCSSSI.mjs +0 -4396
  17. package/dist/estree-67ZCSSSI.mjs.map +0 -1
  18. package/dist/flow-SJW7PRXX.mjs +0 -26365
  19. package/dist/flow-SJW7PRXX.mjs.map +0 -1
  20. package/dist/glimmer-PF2X22V2.mjs +0 -2995
  21. package/dist/glimmer-PF2X22V2.mjs.map +0 -1
  22. package/dist/graphql-NOJ5HX7K.mjs +0 -1253
  23. package/dist/graphql-NOJ5HX7K.mjs.map +0 -1
  24. package/dist/html-ROPIWVPQ.mjs +0 -2780
  25. package/dist/html-ROPIWVPQ.mjs.map +0 -1
  26. package/dist/index.cjs +0 -34
  27. package/dist/index.cjs.map +0 -1
  28. package/dist/index.d.cts +0 -3
  29. package/dist/markdown-PFYT4MSP.mjs +0 -3423
  30. package/dist/markdown-PFYT4MSP.mjs.map +0 -1
  31. package/dist/meriyah-5NLZXLMA.mjs +0 -2356
  32. package/dist/meriyah-5NLZXLMA.mjs.map +0 -1
  33. package/dist/postcss-ITO6IEN5.mjs +0 -5027
  34. package/dist/postcss-ITO6IEN5.mjs.map +0 -1
  35. package/dist/typescript-6IE7K56Q.mjs +0 -13392
  36. package/dist/typescript-6IE7K56Q.mjs.map +0 -1
  37. package/dist/yaml-DB2OVPLH.mjs +0 -4230
  38. package/dist/yaml-DB2OVPLH.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -47,12 +47,13 @@ import { format } from "logform";
47
47
  // src/config.schema.ts
48
48
  import { z } from "zod";
49
49
  var loggerConfigSchema = z.object({
50
- level: z.enum(["info", "warn", "error", "debug"]).default("info")
50
+ level: z.enum(["info", "warn", "error", "debug", "silly"]).default("info")
51
51
  });
52
52
  var resturaConfigSchema = z.object({
53
53
  authToken: z.string().min(1, "Missing Restura Auth Token"),
54
54
  sendErrorStackTrace: z.boolean().default(false),
55
55
  schemaFilePath: z.string().default(process.cwd() + "/restura.schema.json"),
56
+ customApiFolderPath: z.string().default(process.cwd() + "/dist/api"),
56
57
  generatedTypesPath: z.string().default(process.cwd() + "/src/@types")
57
58
  });
58
59
 
@@ -91,8 +92,74 @@ var logger = winston.createLogger({
91
92
  ]
92
93
  });
93
94
 
95
+ // src/restura/errors.ts
96
+ var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
97
+ HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
98
+ HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
99
+ HtmlStatusCodes2[HtmlStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
100
+ HtmlStatusCodes2[HtmlStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
101
+ HtmlStatusCodes2[HtmlStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
102
+ HtmlStatusCodes2[HtmlStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
103
+ HtmlStatusCodes2[HtmlStatusCodes2["VERSION_OUT_OF_DATE"] = 418] = "VERSION_OUT_OF_DATE";
104
+ HtmlStatusCodes2[HtmlStatusCodes2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
105
+ HtmlStatusCodes2[HtmlStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
106
+ HtmlStatusCodes2[HtmlStatusCodes2["NETWORK_CONNECT_TIMEOUT"] = 599] = "NETWORK_CONNECT_TIMEOUT";
107
+ return HtmlStatusCodes2;
108
+ })(HtmlStatusCodes || {});
109
+ var RsError = class _RsError {
110
+ constructor(errCode, message) {
111
+ this.err = errCode;
112
+ this.msg = message || "";
113
+ this.status = _RsError.htmlStatus(errCode);
114
+ this.stack = new Error().stack || "";
115
+ }
116
+ static htmlStatus(code) {
117
+ return htmlStatusMap[code];
118
+ }
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ static isRsError(error) {
121
+ return error instanceof _RsError;
122
+ }
123
+ };
124
+ var htmlStatusMap = {
125
+ UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
126
+ NOT_FOUND: 404 /* NOT_FOUND */,
127
+ EMAIL_TAKEN: 409 /* CONFLICT */,
128
+ FORBIDDEN: 403 /* FORBIDDEN */,
129
+ CONFLICT: 409 /* CONFLICT */,
130
+ UNAUTHORIZED: 401 /* UNAUTHORIZED */,
131
+ UPDATE_FORBIDDEN: 403 /* FORBIDDEN */,
132
+ CREATE_FORBIDDEN: 403 /* FORBIDDEN */,
133
+ DELETE_FORBIDDEN: 403 /* FORBIDDEN */,
134
+ DELETE_FAILURE: 500 /* SERVER_ERROR */,
135
+ BAD_REQUEST: 400 /* BAD_REQUEST */,
136
+ INVALID_TOKEN: 401 /* UNAUTHORIZED */,
137
+ INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
138
+ DUPLICATE_TOKEN: 409 /* CONFLICT */,
139
+ DUPLICATE_USERNAME: 409 /* CONFLICT */,
140
+ DUPLICATE_EMAIL: 409 /* CONFLICT */,
141
+ DUPLICATE: 409 /* CONFLICT */,
142
+ EMAIL_NOT_VERIFIED: 400 /* BAD_REQUEST */,
143
+ UPDATE_WITHOUT_ID: 400 /* BAD_REQUEST */,
144
+ CONNECTION_ERROR: 599 /* NETWORK_CONNECT_TIMEOUT */,
145
+ INVALID_PAYMENT: 403 /* FORBIDDEN */,
146
+ DECLINED_PAYMENT: 403 /* FORBIDDEN */,
147
+ INTEGRATION_ERROR: 500 /* SERVER_ERROR */,
148
+ CANNOT_RESERVE: 403 /* FORBIDDEN */,
149
+ REFUND_FAILURE: 403 /* FORBIDDEN */,
150
+ INVALID_INVOICE: 403 /* FORBIDDEN */,
151
+ INVALID_COUPON: 403 /* FORBIDDEN */,
152
+ SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
153
+ METHOD_UNALLOWED: 405 /* METHOD_NOT_ALLOWED */,
154
+ LOGIN_EXPIRED: 401 /* UNAUTHORIZED */,
155
+ THIRD_PARTY_ERROR: 400 /* BAD_REQUEST */,
156
+ ACCESS_DENIED: 403 /* FORBIDDEN */,
157
+ DATABASE_ERROR: 500 /* SERVER_ERROR */,
158
+ SCHEMA_ERROR: 500 /* SERVER_ERROR */
159
+ };
160
+
94
161
  // src/restura/restura.ts
95
- import { ObjectUtils as ObjectUtils5 } from "@redskytech/core-utils";
162
+ import { ObjectUtils as ObjectUtils5, StringUtils as StringUtils3 } from "@redskytech/core-utils";
96
163
  import { config as config2 } from "@restura/internal";
97
164
 
98
165
  // ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
@@ -147,59 +214,11 @@ import compression from "compression";
147
214
  import cookieParser from "cookie-parser";
148
215
  import { createHash } from "crypto";
149
216
  import * as express from "express";
150
- import fs2 from "fs";
151
- import path2 from "path";
217
+ import fs3 from "fs";
218
+ import path3 from "path";
219
+ import pg2 from "pg";
152
220
  import * as prettier3 from "prettier";
153
221
 
154
- // src/restura/errors.ts
155
- var RsError = class _RsError {
156
- constructor(errCode, message) {
157
- this.err = errCode;
158
- this.msg = message || "";
159
- this.status = _RsError.htmlStatus(errCode);
160
- this.stack = new Error().stack || "";
161
- }
162
- static htmlStatus(code) {
163
- return htmlStatusMap[code];
164
- }
165
- };
166
- var htmlStatusMap = {
167
- UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
168
- NOT_FOUND: 404 /* NOT_FOUND */,
169
- EMAIL_TAKEN: 409 /* CONFLICT */,
170
- FORBIDDEN: 403 /* FORBIDDEN */,
171
- CONFLICT: 409 /* CONFLICT */,
172
- UNAUTHORIZED: 401 /* UNAUTHORIZED */,
173
- UPDATE_FORBIDDEN: 403 /* FORBIDDEN */,
174
- CREATE_FORBIDDEN: 403 /* FORBIDDEN */,
175
- DELETE_FORBIDDEN: 403 /* FORBIDDEN */,
176
- DELETE_FAILURE: 500 /* SERVER_ERROR */,
177
- BAD_REQUEST: 400 /* BAD_REQUEST */,
178
- INVALID_TOKEN: 401 /* UNAUTHORIZED */,
179
- INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
180
- DUPLICATE_TOKEN: 409 /* CONFLICT */,
181
- DUPLICATE_USERNAME: 409 /* CONFLICT */,
182
- DUPLICATE_EMAIL: 409 /* CONFLICT */,
183
- DUPLICATE: 409 /* CONFLICT */,
184
- EMAIL_NOT_VERIFIED: 400 /* BAD_REQUEST */,
185
- UPDATE_WITHOUT_ID: 400 /* BAD_REQUEST */,
186
- CONNECTION_ERROR: 599 /* NETWORK_CONNECT_TIMEOUT */,
187
- INVALID_PAYMENT: 403 /* FORBIDDEN */,
188
- DECLINED_PAYMENT: 403 /* FORBIDDEN */,
189
- INTEGRATION_ERROR: 500 /* SERVER_ERROR */,
190
- CANNOT_RESERVE: 403 /* FORBIDDEN */,
191
- REFUND_FAILURE: 403 /* FORBIDDEN */,
192
- INVALID_INVOICE: 403 /* FORBIDDEN */,
193
- INVALID_COUPON: 403 /* FORBIDDEN */,
194
- SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
195
- METHOD_UNALLOWED: 405 /* METHOD_NOT_ALLOWED */,
196
- LOGIN_EXPIRED: 401 /* UNAUTHORIZED */,
197
- THIRD_PARTY_ERROR: 400 /* BAD_REQUEST */,
198
- ACCESS_DENIED: 403 /* FORBIDDEN */,
199
- DATABASE_ERROR: 500 /* SERVER_ERROR */,
200
- SCHEMA_ERROR: 500 /* SERVER_ERROR */
201
- };
202
-
203
222
  // src/restura/sql/SqlUtils.ts
204
223
  var SqlUtils = class _SqlUtils {
205
224
  static convertDatabaseTypeToTypescript(type, value) {
@@ -291,9 +310,9 @@ var ResponseValidator = class _ResponseValidator {
291
310
  return { validator: "any" };
292
311
  }
293
312
  getTypeFromTable(selector, name) {
294
- const path3 = selector.split(".");
295
- if (path3.length === 0 || path3.length > 2 || path3[0] === "") return { validator: "any", isOptional: false };
296
- const tableName = path3.length == 2 ? path3[0] : name, columnName = path3.length == 2 ? path3[1] : path3[0];
313
+ const path4 = selector.split(".");
314
+ if (path4.length === 0 || path4.length > 2 || path4[0] === "") return { validator: "any", isOptional: false };
315
+ const tableName = path4.length == 2 ? path4[0] : name, columnName = path4.length == 2 ? path4[1] : path4[0];
297
316
  const table = this.database.find((t) => t.name == tableName);
298
317
  const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
299
318
  if (!table || !column) return { validator: "any", isOptional: false };
@@ -509,10 +528,10 @@ var ApiTree = class _ApiTree {
509
528
  return `${p.name}${optional ? "?" : ""}:${responseType}${array ? "[]" : ""}`;
510
529
  }
511
530
  getTypeFromTable(selector, name) {
512
- const path3 = selector.split(".");
513
- if (path3.length === 0 || path3.length > 2 || path3[0] === "") return { responseType: "any", optional: false };
514
- let tableName = path3.length == 2 ? path3[0] : name;
515
- const columnName = path3.length == 2 ? path3[1] : path3[0];
531
+ const path4 = selector.split(".");
532
+ if (path4.length === 0 || path4.length > 2 || path4[0] === "") return { responseType: "any", optional: false };
533
+ let tableName = path4.length == 2 ? path4[0] : name;
534
+ const columnName = path4.length == 2 ? path4[1] : path4[0];
516
535
  let table = this.database.find((t) => t.name == tableName);
517
536
  if (!table && tableName.includes("_")) {
518
537
  const tableAliasSplit = tableName.split("_");
@@ -527,8 +546,8 @@ var ApiTree = class _ApiTree {
527
546
  };
528
547
  }
529
548
  };
530
- function pathToNamespaces(path3) {
531
- return path3.split("/").map((e) => StringUtils.toPascalCasing(e)).filter((e) => e);
549
+ function pathToNamespaces(path4) {
550
+ return path4.split("/").map((e) => StringUtils.toPascalCasing(e)).filter((e) => e);
532
551
  }
533
552
  function apiGenerator(schema, schemaHash) {
534
553
  let apiString = `/** Auto generated file from Schema Hash (${schemaHash}). DO NOT MODIFY **/`;
@@ -561,6 +580,92 @@ function apiGenerator(schema, schemaHash) {
561
580
  }));
562
581
  }
563
582
 
583
+ // src/restura/customApiFactory.ts
584
+ import fs from "fs";
585
+ import path from "path";
586
+ var CustomApiFactory = class {
587
+ constructor() {
588
+ this.customApis = {};
589
+ }
590
+ async loadApiFiles(baseFolderPath) {
591
+ const apiVersions = ["v1"];
592
+ for (const apiVersion of apiVersions) {
593
+ const apiVersionFolderPath = path.join(baseFolderPath, apiVersion);
594
+ if (!fs.existsSync(apiVersionFolderPath)) continue;
595
+ await this.addDirectory(apiVersionFolderPath, apiVersion);
596
+ }
597
+ }
598
+ getCustomApi(customApiName) {
599
+ return this.customApis[customApiName];
600
+ }
601
+ async addDirectory(directoryPath, apiVersion) {
602
+ const entries = fs.readdirSync(directoryPath, {
603
+ withFileTypes: true
604
+ });
605
+ for (const entry of entries) {
606
+ if (entry.isFile()) {
607
+ if (entry.name.endsWith(`.api.${apiVersion}.js`) === false) continue;
608
+ try {
609
+ const importPath = `${path.join(directoryPath, entry.name)}`;
610
+ const ApiImport = await import(importPath);
611
+ const customApiClass = new ApiImport.default();
612
+ logger.info(`Registering custom API: ${ApiImport.default.name}`);
613
+ this.bindMethodsToInstance(customApiClass);
614
+ this.customApis[ApiImport.default.name] = customApiClass;
615
+ } catch (e) {
616
+ console.error(e);
617
+ }
618
+ }
619
+ }
620
+ }
621
+ bindMethodsToInstance(instance) {
622
+ const proto = Object.getPrototypeOf(instance);
623
+ Object.getOwnPropertyNames(proto).forEach((key) => {
624
+ const property = instance[key];
625
+ if (typeof property === "function" && key !== "constructor") {
626
+ instance[key] = property.bind(instance);
627
+ }
628
+ });
629
+ }
630
+ };
631
+ var customApiFactory = new CustomApiFactory();
632
+ var customApiFactory_default = customApiFactory;
633
+
634
+ // src/restura/customTypeValidationGenerator.ts
635
+ import fs2 from "fs";
636
+ import * as TJS from "typescript-json-schema";
637
+ import path2, { resolve } from "path";
638
+ import tmp from "tmp";
639
+ import * as process2 from "process";
640
+ function customTypeValidationGenerator(currentSchema) {
641
+ const schemaObject = {};
642
+ const customInterfaceNames = currentSchema.customTypes.match(new RegExp("(?<=interface\\s)(\\w+)|(?<=type\\s)(\\w+)", "g"));
643
+ if (!customInterfaceNames) return {};
644
+ const temporaryFile = tmp.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
645
+ fs2.writeFileSync(temporaryFile.name, currentSchema.customTypes);
646
+ const compilerOptions = {
647
+ strictNullChecks: true,
648
+ skipLibCheck: true
649
+ };
650
+ const program = TJS.getProgramFromFiles(
651
+ [
652
+ resolve(temporaryFile.name),
653
+ // find a way to remove
654
+ path2.join(process2.cwd(), "src/@types/models.d.ts"),
655
+ path2.join(process2.cwd(), "src/@types/api.d.ts")
656
+ ],
657
+ compilerOptions
658
+ );
659
+ customInterfaceNames.forEach((item) => {
660
+ const ddlSchema = TJS.generateSchema(program, item, {
661
+ required: true
662
+ });
663
+ schemaObject[item] = ddlSchema || {};
664
+ });
665
+ temporaryFile.removeCallback();
666
+ return schemaObject;
667
+ }
668
+
564
669
  // src/restura/middleware/addApiResponseFunctions.ts
565
670
  function addApiResponseFunctions(req, res, next) {
566
671
  res.sendData = function(data, statusCode = 200) {
@@ -589,9 +694,17 @@ function addApiResponseFunctions(req, res, next) {
589
694
  next();
590
695
  }
591
696
 
592
- // src/restura/validateRequestParams.ts
593
- import { ObjectUtils as ObjectUtils2 } from "@redskytech/core-utils";
594
- import jsonschema from "jsonschema";
697
+ // src/restura/middleware/authenticateUser.ts
698
+ function authenticateUser(applicationAuthenticateHandler) {
699
+ return (req, res, next) => {
700
+ applicationAuthenticateHandler(req, res, (userDetails) => {
701
+ req.requesterDetails = __spreadValues(__spreadValues({}, req.requesterDetails), userDetails);
702
+ next();
703
+ });
704
+ };
705
+ }
706
+
707
+ // src/restura/restura.schema.ts
595
708
  import { z as z3 } from "zod";
596
709
 
597
710
  // src/restura/types/validation.types.ts
@@ -602,264 +715,83 @@ var validatorDataSchema = z2.object({
602
715
  value: validatorDataSchemeValue
603
716
  }).strict();
604
717
 
605
- // src/restura/utils/addQuotesToStrings.ts
606
- function addQuotesToStrings(variable) {
607
- if (typeof variable === "string") {
608
- return `'${variable}'`;
609
- } else if (Array.isArray(variable)) {
610
- const arrayWithQuotes = variable.map(addQuotesToStrings);
611
- return arrayWithQuotes;
612
- } else {
613
- return variable;
614
- }
615
- }
616
-
617
- // src/restura/validateRequestParams.ts
618
- function validateRequestParams(req, routeData, validationSchema) {
619
- const requestData = getRequestData(req);
620
- req.data = requestData;
621
- if (routeData.request === void 0) {
622
- if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
623
- throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
624
- if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
625
- if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
626
- const currentInterface = validationSchema[routeData.requestType];
627
- const validator = new jsonschema.Validator();
628
- const executeValidation = validator.validate(req.data, currentInterface);
629
- if (!executeValidation.valid) {
630
- throw new RsError(
631
- "BAD_REQUEST",
632
- `Request custom setup has failed the following check: (${executeValidation.errors})`
633
- );
634
- }
635
- return;
636
- }
637
- Object.keys(req.data).forEach((requestParamName) => {
638
- const requestParam = routeData.request.find((param) => param.name === requestParamName);
639
- if (!requestParam) {
640
- throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
641
- }
642
- });
643
- routeData.request.forEach((requestParam) => {
644
- const requestValue = requestData[requestParam.name];
645
- if (requestParam.required && requestValue === void 0)
646
- throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
647
- else if (!requestParam.required && requestValue === void 0) return;
648
- validateRequestSingleParam(requestValue, requestParam);
649
- });
650
- }
651
- function validateRequestSingleParam(requestValue, requestParam) {
652
- requestParam.validator.forEach((validator) => {
653
- switch (validator.type) {
654
- case "TYPE_CHECK":
655
- performTypeCheck(requestValue, validator, requestParam.name);
656
- break;
657
- case "MIN":
658
- performMinCheck(requestValue, validator, requestParam.name);
659
- break;
660
- case "MAX":
661
- performMaxCheck(requestValue, validator, requestParam.name);
662
- break;
663
- case "ONE_OF":
664
- performOneOfCheck(requestValue, validator, requestParam.name);
665
- break;
666
- }
667
- });
668
- }
669
- function isValidType(type, requestValue) {
670
- try {
671
- expectValidType(type, requestValue);
672
- return true;
673
- } catch (e) {
674
- return false;
675
- }
676
- }
677
- function expectValidType(type, requestValue) {
678
- if (type === "number") {
679
- return z3.number().parse(requestValue);
680
- }
681
- if (type === "string") {
682
- return z3.string().parse(requestValue);
683
- }
684
- if (type === "boolean") {
685
- return z3.boolean().parse(requestValue);
686
- }
687
- if (type === "string[]") {
688
- return z3.array(z3.string()).parse(requestValue);
689
- }
690
- if (type === "number[]") {
691
- return z3.array(z3.number()).parse(requestValue);
692
- }
693
- if (type === "any[]") {
694
- return z3.array(z3.any()).parse(requestValue);
695
- }
696
- if (type === "object") {
697
- return z3.object({}).strict().parse(requestValue);
698
- }
699
- }
700
- function performTypeCheck(requestValue, validator, requestParamName) {
701
- if (!isValidType(validator.value, requestValue)) {
702
- throw new RsError(
703
- "BAD_REQUEST",
704
- `Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
705
- );
706
- }
707
- try {
708
- validatorDataSchemeValue.parse(validator.value);
709
- } catch (e) {
710
- throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
711
- }
712
- }
713
- function expectOnlyNumbers(requestValue, validator, requestParamName) {
714
- if (!isValueNumber(requestValue))
715
- throw new RsError(
716
- "BAD_REQUEST",
717
- `Request param (${requestParamName}) with value (${requestValue}) is not of type number`
718
- );
719
- if (!isValueNumber(validator.value))
720
- throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
721
- }
722
- function performMinCheck(requestValue, validator, requestParamName) {
723
- expectOnlyNumbers(requestValue, validator, requestParamName);
724
- if (requestValue < validator.value)
725
- throw new RsError(
726
- "BAD_REQUEST",
727
- `Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
728
- );
729
- }
730
- function performMaxCheck(requestValue, validator, requestParamName) {
731
- expectOnlyNumbers(requestValue, validator, requestParamName);
732
- if (requestValue > validator.value)
733
- throw new RsError(
734
- "BAD_REQUEST",
735
- `Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
736
- );
737
- }
738
- function performOneOfCheck(requestValue, validator, requestParamName) {
739
- if (!ObjectUtils2.isArrayWithData(validator.value))
740
- throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
741
- if (typeof requestValue === "object")
742
- throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
743
- if (!validator.value.includes(requestValue))
744
- throw new RsError(
745
- "BAD_REQUEST",
746
- `Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
747
- );
748
- }
749
- function isValueNumber(value) {
750
- return !isNaN(Number(value));
751
- }
752
- function getRequestData(req) {
753
- let body = "";
754
- if (req.method === "GET" || req.method === "DELETE") {
755
- body = "query";
756
- } else {
757
- body = "body";
758
- }
759
- const bodyData = req[body];
760
- if (bodyData) {
761
- for (const attr in bodyData) {
762
- if (attr === "token") {
763
- delete bodyData[attr];
764
- continue;
765
- }
766
- if (bodyData[attr] instanceof Array) {
767
- const attrList = [];
768
- for (const value of bodyData[attr]) {
769
- if (isNaN(Number(value))) continue;
770
- attrList.push(Number(value));
771
- }
772
- if (ObjectUtils2.isArrayWithData(attrList)) {
773
- bodyData[attr] = attrList;
774
- }
775
- } else {
776
- bodyData[attr] = ObjectUtils2.safeParse(bodyData[attr]);
777
- if (isNaN(Number(bodyData[attr]))) continue;
778
- bodyData[attr] = Number(bodyData[attr]);
779
- }
780
- }
781
- }
782
- return bodyData;
783
- }
784
-
785
718
  // src/restura/restura.schema.ts
786
- import { z as z4 } from "zod";
787
- var orderBySchema = z4.object({
788
- columnName: z4.string(),
789
- order: z4.enum(["ASC", "DESC"]),
790
- tableName: z4.string()
719
+ var orderBySchema = z3.object({
720
+ columnName: z3.string(),
721
+ order: z3.enum(["ASC", "DESC"]),
722
+ tableName: z3.string()
791
723
  }).strict();
792
- var groupBySchema = z4.object({
793
- columnName: z4.string(),
794
- tableName: z4.string()
724
+ var groupBySchema = z3.object({
725
+ columnName: z3.string(),
726
+ tableName: z3.string()
795
727
  }).strict();
796
- var whereDataSchema = z4.object({
797
- tableName: z4.string().optional(),
798
- columnName: z4.string().optional(),
799
- operator: z4.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
800
- value: z4.string().or(z4.number()).optional(),
801
- custom: z4.string().optional(),
802
- conjunction: z4.enum(["AND", "OR"]).optional()
728
+ var whereDataSchema = z3.object({
729
+ tableName: z3.string().optional(),
730
+ columnName: z3.string().optional(),
731
+ operator: z3.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
732
+ value: z3.string().or(z3.number()).optional(),
733
+ custom: z3.string().optional(),
734
+ conjunction: z3.enum(["AND", "OR"]).optional()
803
735
  }).strict();
804
- var assignmentDataSchema = z4.object({
805
- name: z4.string(),
806
- value: z4.string()
736
+ var assignmentDataSchema = z3.object({
737
+ name: z3.string(),
738
+ value: z3.string()
807
739
  }).strict();
808
- var joinDataSchema = z4.object({
809
- table: z4.string(),
810
- localColumnName: z4.string().optional(),
811
- foreignColumnName: z4.string().optional(),
812
- custom: z4.string().optional(),
813
- type: z4.enum(["LEFT", "INNER"]),
814
- alias: z4.string().optional()
740
+ var joinDataSchema = z3.object({
741
+ table: z3.string(),
742
+ localColumnName: z3.string().optional(),
743
+ foreignColumnName: z3.string().optional(),
744
+ custom: z3.string().optional(),
745
+ type: z3.enum(["LEFT", "INNER"]),
746
+ alias: z3.string().optional()
815
747
  }).strict();
816
- var requestDataSchema = z4.object({
817
- name: z4.string(),
818
- required: z4.boolean(),
819
- validator: z4.array(validatorDataSchema)
748
+ var requestDataSchema = z3.object({
749
+ name: z3.string(),
750
+ required: z3.boolean(),
751
+ validator: z3.array(validatorDataSchema)
820
752
  }).strict();
821
- var responseDataSchema = z4.object({
822
- name: z4.string(),
823
- selector: z4.string().optional(),
824
- subquery: z4.object({
825
- table: z4.string(),
826
- joins: z4.array(joinDataSchema),
827
- where: z4.array(whereDataSchema),
828
- properties: z4.array(z4.lazy(() => responseDataSchema)),
753
+ var responseDataSchema = z3.object({
754
+ name: z3.string(),
755
+ selector: z3.string().optional(),
756
+ subquery: z3.object({
757
+ table: z3.string(),
758
+ joins: z3.array(joinDataSchema),
759
+ where: z3.array(whereDataSchema),
760
+ properties: z3.array(z3.lazy(() => responseDataSchema)),
829
761
  // Explicit type for the lazy schema
830
762
  groupBy: groupBySchema.optional(),
831
763
  orderBy: orderBySchema.optional()
832
764
  }).optional()
833
765
  }).strict();
834
- var routeDataBaseSchema = z4.object({
835
- method: z4.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
836
- name: z4.string(),
837
- description: z4.string(),
838
- path: z4.string(),
839
- roles: z4.array(z4.string())
766
+ var routeDataBaseSchema = z3.object({
767
+ method: z3.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
768
+ name: z3.string(),
769
+ description: z3.string(),
770
+ path: z3.string(),
771
+ roles: z3.array(z3.string())
840
772
  }).strict();
841
773
  var standardRouteSchema = routeDataBaseSchema.extend({
842
- type: z4.enum(["ONE", "ARRAY", "PAGED"]),
843
- table: z4.string(),
844
- joins: z4.array(joinDataSchema),
845
- assignments: z4.array(assignmentDataSchema),
846
- where: z4.array(whereDataSchema),
847
- request: z4.array(requestDataSchema),
848
- response: z4.array(responseDataSchema),
774
+ type: z3.enum(["ONE", "ARRAY", "PAGED"]),
775
+ table: z3.string(),
776
+ joins: z3.array(joinDataSchema),
777
+ assignments: z3.array(assignmentDataSchema),
778
+ where: z3.array(whereDataSchema),
779
+ request: z3.array(requestDataSchema),
780
+ response: z3.array(responseDataSchema),
849
781
  groupBy: groupBySchema.optional(),
850
782
  orderBy: orderBySchema.optional()
851
783
  }).strict();
852
784
  var customRouteSchema = routeDataBaseSchema.extend({
853
- type: z4.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
854
- responseType: z4.union([z4.string(), z4.enum(["string", "number", "boolean"])]),
855
- requestType: z4.string().optional(),
856
- request: z4.array(requestDataSchema).optional(),
857
- table: z4.undefined(),
858
- joins: z4.undefined(),
859
- assignments: z4.undefined(),
860
- fileUploadType: z4.enum(["SINGLE", "MULTIPLE"]).optional()
785
+ type: z3.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
786
+ responseType: z3.union([z3.string(), z3.enum(["string", "number", "boolean"])]),
787
+ requestType: z3.string().optional(),
788
+ request: z3.array(requestDataSchema).optional(),
789
+ table: z3.undefined(),
790
+ joins: z3.undefined(),
791
+ assignments: z3.undefined(),
792
+ fileUploadType: z3.enum(["SINGLE", "MULTIPLE"]).optional()
861
793
  }).strict();
862
- var postgresColumnNumericTypesSchema = z4.enum([
794
+ var postgresColumnNumericTypesSchema = z3.enum([
863
795
  "SMALLINT",
864
796
  // 2 bytes, -32,768 to 32,767
865
797
  "INTEGER",
@@ -879,7 +811,7 @@ var postgresColumnNumericTypesSchema = z4.enum([
879
811
  "BIGSERIAL"
880
812
  // auto-incrementing big integer
881
813
  ]);
882
- var postgresColumnStringTypesSchema = z4.enum([
814
+ var postgresColumnStringTypesSchema = z3.enum([
883
815
  "CHAR",
884
816
  // fixed-length, blank-padded
885
817
  "VARCHAR",
@@ -889,7 +821,7 @@ var postgresColumnStringTypesSchema = z4.enum([
889
821
  "BYTEA"
890
822
  // binary data
891
823
  ]);
892
- var postgresColumnDateTypesSchema = z4.enum([
824
+ var postgresColumnDateTypesSchema = z3.enum([
893
825
  "DATE",
894
826
  // calendar date (year, month, day)
895
827
  "TIMESTAMP",
@@ -901,7 +833,7 @@ var postgresColumnDateTypesSchema = z4.enum([
901
833
  "INTERVAL"
902
834
  // time span
903
835
  ]);
904
- var mariaDbColumnNumericTypesSchema = z4.enum([
836
+ var mariaDbColumnNumericTypesSchema = z3.enum([
905
837
  "BOOLEAN",
906
838
  // 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
907
839
  "TINYINT",
@@ -921,7 +853,7 @@ var mariaDbColumnNumericTypesSchema = z4.enum([
921
853
  "DOUBLE"
922
854
  // 8 bytes Stored in 64-bit IEEE-754 floating point format. As such, the number of significant digits is about 15 and the range of values is approximately +/-1e308.
923
855
  ]);
924
- var mariaDbColumnStringTypesSchema = z4.enum([
856
+ var mariaDbColumnStringTypesSchema = z3.enum([
925
857
  "CHAR",
926
858
  // 1, 2, 4, or 8 bytes Holds letters and special characters of fixed length. Max length is 255. Default and minimum size is 1 byte.
927
859
  "VARCHAR",
@@ -947,7 +879,7 @@ var mariaDbColumnStringTypesSchema = z4.enum([
947
879
  "ENUM"
948
880
  // Enum type
949
881
  ]);
950
- var mariaDbColumnDateTypesSchema = z4.enum([
882
+ var mariaDbColumnDateTypesSchema = z3.enum([
951
883
  "DATE",
952
884
  // 4-bytes Date has year, month, and day.
953
885
  "DATETIME",
@@ -957,9 +889,9 @@ var mariaDbColumnDateTypesSchema = z4.enum([
957
889
  "TIMESTAMP"
958
890
  // 4-bytes Values are stored as the number of seconds since 1970-01-01 00:00:00 UTC, and optionally microseconds.
959
891
  ]);
960
- var columnDataSchema = z4.object({
961
- name: z4.string(),
962
- type: z4.union([
892
+ var columnDataSchema = z3.object({
893
+ name: z3.string(),
894
+ type: z3.union([
963
895
  postgresColumnNumericTypesSchema,
964
896
  postgresColumnStringTypesSchema,
965
897
  postgresColumnDateTypesSchema,
@@ -967,24 +899,24 @@ var columnDataSchema = z4.object({
967
899
  mariaDbColumnStringTypesSchema,
968
900
  mariaDbColumnDateTypesSchema
969
901
  ]),
970
- isNullable: z4.boolean(),
971
- roles: z4.array(z4.string()),
972
- comment: z4.string().optional(),
973
- default: z4.string().optional(),
974
- value: z4.string().optional(),
975
- isPrimary: z4.boolean().optional(),
976
- isUnique: z4.boolean().optional(),
977
- hasAutoIncrement: z4.boolean().optional(),
978
- length: z4.number().optional()
902
+ isNullable: z3.boolean(),
903
+ roles: z3.array(z3.string()),
904
+ comment: z3.string().optional(),
905
+ default: z3.string().optional(),
906
+ value: z3.string().optional(),
907
+ isPrimary: z3.boolean().optional(),
908
+ isUnique: z3.boolean().optional(),
909
+ hasAutoIncrement: z3.boolean().optional(),
910
+ length: z3.number().optional()
979
911
  }).strict();
980
- var indexDataSchema = z4.object({
981
- name: z4.string(),
982
- columns: z4.array(z4.string()),
983
- isUnique: z4.boolean(),
984
- isPrimaryKey: z4.boolean(),
985
- order: z4.enum(["ASC", "DESC"])
912
+ var indexDataSchema = z3.object({
913
+ name: z3.string(),
914
+ columns: z3.array(z3.string()),
915
+ isUnique: z3.boolean(),
916
+ isPrimaryKey: z3.boolean(),
917
+ order: z3.enum(["ASC", "DESC"])
986
918
  }).strict();
987
- var foreignKeyActionsSchema = z4.enum([
919
+ var foreignKeyActionsSchema = z3.enum([
988
920
  "CASCADE",
989
921
  // CASCADE action for foreign keys
990
922
  "SET NULL",
@@ -996,48 +928,229 @@ var foreignKeyActionsSchema = z4.enum([
996
928
  "SET DEFAULT"
997
929
  // SET DEFAULT action for foreign keys
998
930
  ]);
999
- var foreignKeyDataSchema = z4.object({
1000
- name: z4.string(),
1001
- column: z4.string(),
1002
- refTable: z4.string(),
1003
- refColumn: z4.string(),
931
+ var foreignKeyDataSchema = z3.object({
932
+ name: z3.string(),
933
+ column: z3.string(),
934
+ refTable: z3.string(),
935
+ refColumn: z3.string(),
1004
936
  onDelete: foreignKeyActionsSchema,
1005
937
  onUpdate: foreignKeyActionsSchema
1006
938
  }).strict();
1007
- var checkConstraintDataSchema = z4.object({
1008
- name: z4.string(),
1009
- check: z4.string()
939
+ var checkConstraintDataSchema = z3.object({
940
+ name: z3.string(),
941
+ check: z3.string()
1010
942
  }).strict();
1011
- var tableDataSchema = z4.object({
1012
- name: z4.string(),
1013
- columns: z4.array(columnDataSchema),
1014
- indexes: z4.array(indexDataSchema),
1015
- foreignKeys: z4.array(foreignKeyDataSchema),
1016
- checkConstraints: z4.array(checkConstraintDataSchema),
1017
- roles: z4.array(z4.string())
943
+ var tableDataSchema = z3.object({
944
+ name: z3.string(),
945
+ columns: z3.array(columnDataSchema),
946
+ indexes: z3.array(indexDataSchema),
947
+ foreignKeys: z3.array(foreignKeyDataSchema),
948
+ checkConstraints: z3.array(checkConstraintDataSchema),
949
+ roles: z3.array(z3.string())
1018
950
  }).strict();
1019
- var endpointDataSchema = z4.object({
1020
- name: z4.string(),
1021
- description: z4.string(),
1022
- baseUrl: z4.string(),
1023
- routes: z4.array(z4.union([standardRouteSchema, customRouteSchema]))
951
+ var endpointDataSchema = z3.object({
952
+ name: z3.string(),
953
+ description: z3.string(),
954
+ baseUrl: z3.string(),
955
+ routes: z3.array(z3.union([standardRouteSchema, customRouteSchema]))
1024
956
  }).strict();
1025
- var resturaZodSchema = z4.object({
1026
- database: z4.array(tableDataSchema),
1027
- endpoints: z4.array(endpointDataSchema),
1028
- globalParams: z4.array(z4.string()),
1029
- roles: z4.array(z4.string()),
1030
- customTypes: z4.string()
957
+ var resturaZodSchema = z3.object({
958
+ database: z3.array(tableDataSchema),
959
+ endpoints: z3.array(endpointDataSchema),
960
+ globalParams: z3.array(z3.string()),
961
+ roles: z3.array(z3.string()),
962
+ customTypes: z3.string()
1031
963
  }).strict();
1032
964
  async function isSchemaValid(schemaToCheck) {
1033
965
  try {
1034
966
  resturaZodSchema.parse(schemaToCheck);
1035
967
  return true;
1036
- } catch (error) {
1037
- logger.error(error);
968
+ } catch (error) {
969
+ logger.error(error);
970
+ return false;
971
+ }
972
+ }
973
+
974
+ // src/restura/validateRequestParams.ts
975
+ import { ObjectUtils as ObjectUtils2 } from "@redskytech/core-utils";
976
+ import jsonschema from "jsonschema";
977
+ import { z as z4 } from "zod";
978
+
979
+ // src/restura/utils/addQuotesToStrings.ts
980
+ function addQuotesToStrings(variable) {
981
+ if (typeof variable === "string") {
982
+ return `'${variable}'`;
983
+ } else if (Array.isArray(variable)) {
984
+ const arrayWithQuotes = variable.map(addQuotesToStrings);
985
+ return arrayWithQuotes;
986
+ } else {
987
+ return variable;
988
+ }
989
+ }
990
+
991
+ // src/restura/validateRequestParams.ts
992
+ function validateRequestParams(req, routeData, validationSchema) {
993
+ const requestData = getRequestData(req);
994
+ req.data = requestData;
995
+ if (routeData.request === void 0) {
996
+ if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
997
+ throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
998
+ if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
999
+ if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
1000
+ const currentInterface = validationSchema[routeData.requestType];
1001
+ const validator = new jsonschema.Validator();
1002
+ const executeValidation = validator.validate(req.data, currentInterface);
1003
+ if (!executeValidation.valid) {
1004
+ throw new RsError(
1005
+ "BAD_REQUEST",
1006
+ `Request custom setup has failed the following check: (${executeValidation.errors})`
1007
+ );
1008
+ }
1009
+ return;
1010
+ }
1011
+ Object.keys(req.data).forEach((requestParamName) => {
1012
+ const requestParam = routeData.request.find((param) => param.name === requestParamName);
1013
+ if (!requestParam) {
1014
+ throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
1015
+ }
1016
+ });
1017
+ routeData.request.forEach((requestParam) => {
1018
+ const requestValue = requestData[requestParam.name];
1019
+ if (requestParam.required && requestValue === void 0)
1020
+ throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
1021
+ else if (!requestParam.required && requestValue === void 0) return;
1022
+ validateRequestSingleParam(requestValue, requestParam);
1023
+ });
1024
+ }
1025
+ function validateRequestSingleParam(requestValue, requestParam) {
1026
+ requestParam.validator.forEach((validator) => {
1027
+ switch (validator.type) {
1028
+ case "TYPE_CHECK":
1029
+ performTypeCheck(requestValue, validator, requestParam.name);
1030
+ break;
1031
+ case "MIN":
1032
+ performMinCheck(requestValue, validator, requestParam.name);
1033
+ break;
1034
+ case "MAX":
1035
+ performMaxCheck(requestValue, validator, requestParam.name);
1036
+ break;
1037
+ case "ONE_OF":
1038
+ performOneOfCheck(requestValue, validator, requestParam.name);
1039
+ break;
1040
+ }
1041
+ });
1042
+ }
1043
+ function isValidType(type, requestValue) {
1044
+ try {
1045
+ expectValidType(type, requestValue);
1046
+ return true;
1047
+ } catch (e) {
1038
1048
  return false;
1039
1049
  }
1040
1050
  }
1051
+ function expectValidType(type, requestValue) {
1052
+ if (type === "number") {
1053
+ return z4.number().parse(requestValue);
1054
+ }
1055
+ if (type === "string") {
1056
+ return z4.string().parse(requestValue);
1057
+ }
1058
+ if (type === "boolean") {
1059
+ return z4.boolean().parse(requestValue);
1060
+ }
1061
+ if (type === "string[]") {
1062
+ return z4.array(z4.string()).parse(requestValue);
1063
+ }
1064
+ if (type === "number[]") {
1065
+ return z4.array(z4.number()).parse(requestValue);
1066
+ }
1067
+ if (type === "any[]") {
1068
+ return z4.array(z4.any()).parse(requestValue);
1069
+ }
1070
+ if (type === "object") {
1071
+ return z4.object({}).strict().parse(requestValue);
1072
+ }
1073
+ }
1074
+ function performTypeCheck(requestValue, validator, requestParamName) {
1075
+ if (!isValidType(validator.value, requestValue)) {
1076
+ throw new RsError(
1077
+ "BAD_REQUEST",
1078
+ `Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
1079
+ );
1080
+ }
1081
+ try {
1082
+ validatorDataSchemeValue.parse(validator.value);
1083
+ } catch (e) {
1084
+ throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
1085
+ }
1086
+ }
1087
+ function expectOnlyNumbers(requestValue, validator, requestParamName) {
1088
+ if (!isValueNumber(requestValue))
1089
+ throw new RsError(
1090
+ "BAD_REQUEST",
1091
+ `Request param (${requestParamName}) with value (${requestValue}) is not of type number`
1092
+ );
1093
+ if (!isValueNumber(validator.value))
1094
+ throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
1095
+ }
1096
+ function performMinCheck(requestValue, validator, requestParamName) {
1097
+ expectOnlyNumbers(requestValue, validator, requestParamName);
1098
+ if (requestValue < validator.value)
1099
+ throw new RsError(
1100
+ "BAD_REQUEST",
1101
+ `Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
1102
+ );
1103
+ }
1104
+ function performMaxCheck(requestValue, validator, requestParamName) {
1105
+ expectOnlyNumbers(requestValue, validator, requestParamName);
1106
+ if (requestValue > validator.value)
1107
+ throw new RsError(
1108
+ "BAD_REQUEST",
1109
+ `Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
1110
+ );
1111
+ }
1112
+ function performOneOfCheck(requestValue, validator, requestParamName) {
1113
+ if (!ObjectUtils2.isArrayWithData(validator.value))
1114
+ throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
1115
+ if (typeof requestValue === "object")
1116
+ throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
1117
+ if (!validator.value.includes(requestValue))
1118
+ throw new RsError(
1119
+ "BAD_REQUEST",
1120
+ `Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
1121
+ );
1122
+ }
1123
+ function isValueNumber(value) {
1124
+ return !isNaN(Number(value));
1125
+ }
1126
+ function getRequestData(req) {
1127
+ let body = "";
1128
+ if (req.method === "GET" || req.method === "DELETE") {
1129
+ body = "query";
1130
+ } else {
1131
+ body = "body";
1132
+ }
1133
+ const bodyData = req[body];
1134
+ if (bodyData && body === "query") {
1135
+ for (const attr in bodyData) {
1136
+ if (bodyData[attr] instanceof Array) {
1137
+ const attrList = [];
1138
+ for (const value of bodyData[attr]) {
1139
+ if (isNaN(Number(value))) continue;
1140
+ attrList.push(Number(value));
1141
+ }
1142
+ if (ObjectUtils2.isArrayWithData(attrList)) {
1143
+ bodyData[attr] = attrList;
1144
+ }
1145
+ } else {
1146
+ bodyData[attr] = ObjectUtils2.safeParse(bodyData[attr]);
1147
+ if (isNaN(Number(bodyData[attr]))) continue;
1148
+ bodyData[attr] = Number(bodyData[attr]);
1149
+ }
1150
+ }
1151
+ }
1152
+ return bodyData;
1153
+ }
1041
1154
 
1042
1155
  // src/restura/middleware/schemaValidation.ts
1043
1156
  async function schemaValidation(req, res, next) {
@@ -1086,55 +1199,52 @@ function convertTable(table) {
1086
1199
  return modelString;
1087
1200
  }
1088
1201
 
1089
- // src/restura/middleware/authenticateUser.ts
1090
- function authenticateUser(applicationAuthenticateHandler) {
1091
- return (req, res, next) => {
1092
- applicationAuthenticateHandler(
1093
- req,
1094
- (userDetails) => {
1095
- req.requesterDetails = __spreadValues(__spreadValues({}, req.requesterDetails), userDetails);
1096
- next();
1097
- },
1098
- (errorMessage) => {
1099
- res.sendError("UNAUTHORIZED", errorMessage);
1100
- }
1101
- );
1102
- };
1103
- }
1202
+ // src/restura/sql/PsqlEngine.ts
1203
+ import { ObjectUtils as ObjectUtils4 } from "@redskytech/core-utils";
1104
1204
 
1105
- // src/restura/customTypeValidationGenerator.ts
1106
- import fs from "fs";
1107
- import * as TJS from "typescript-json-schema";
1108
- import path, { resolve } from "path";
1109
- import tmp from "tmp";
1110
- import * as process2 from "process";
1111
- function customTypeValidationGenerator(currentSchema) {
1112
- const schemaObject = {};
1113
- const customInterfaceNames = currentSchema.customTypes.match(new RegExp("(?<=interface\\s)(\\w+)|(?<=type\\s)(\\w+)", "g"));
1114
- if (!customInterfaceNames) return {};
1115
- const temporaryFile = tmp.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
1116
- fs.writeFileSync(temporaryFile.name, currentSchema.customTypes);
1117
- const compilerOptions = {
1118
- strictNullChecks: true,
1119
- skipLibCheck: true
1120
- };
1121
- const program = TJS.getProgramFromFiles(
1122
- [
1123
- resolve(temporaryFile.name),
1124
- // find a way to remove
1125
- path.join(process2.cwd(), "src/@types/models.d.ts"),
1126
- path.join(process2.cwd(), "src/@types/api.d.ts")
1127
- ],
1128
- compilerOptions
1129
- );
1130
- customInterfaceNames.forEach((item) => {
1131
- const ddlSchema = TJS.generateSchema(program, item, {
1132
- required: true
1133
- });
1134
- schemaObject[item] = ddlSchema || {};
1205
+ // src/restura/sql/PsqlUtils.ts
1206
+ import format2 from "pg-format";
1207
+ function escapeColumnName(columnName) {
1208
+ if (columnName === void 0) return "";
1209
+ return `"${columnName.replace(/"/g, "")}"`.replace(".", '"."');
1210
+ }
1211
+ function questionMarksToOrderedParams(query) {
1212
+ let count = 1;
1213
+ return query.replace(/'\?'|\?/g, () => `$${count++}`);
1214
+ }
1215
+ function insertObjectQuery(table, obj) {
1216
+ const keys = Object.keys(obj);
1217
+ const params = Object.values(obj);
1218
+ const columns = keys.map((column) => escapeColumnName(column)).join(", ");
1219
+ const values = params.map((value) => SQL`${value}`).join(", ");
1220
+ const query = `INSERT INTO "${table}" (${columns})
1221
+ VALUES (${values})
1222
+ RETURNING *`;
1223
+ return query;
1224
+ }
1225
+ function updateObjectQuery(table, obj, whereStatement) {
1226
+ const setArray = [];
1227
+ for (const i in obj) {
1228
+ setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
1229
+ }
1230
+ return `UPDATE ${escapeColumnName(table)}
1231
+ SET ${setArray.join(", ")} ${whereStatement}
1232
+ RETURNING *`;
1233
+ }
1234
+ function isValueNumber2(value) {
1235
+ return !isNaN(Number(value));
1236
+ }
1237
+ function SQL(strings, ...values) {
1238
+ let query = strings[0];
1239
+ values.forEach((value, index) => {
1240
+ if (isValueNumber2(value)) {
1241
+ query += value;
1242
+ } else {
1243
+ query += format2.literal(value);
1244
+ }
1245
+ query += strings[index + 1];
1135
1246
  });
1136
- temporaryFile.removeCallback();
1137
- return schemaObject;
1247
+ return query;
1138
1248
  }
1139
1249
 
1140
1250
  // src/restura/sql/SqlEngine.ts
@@ -1226,7 +1336,10 @@ var SqlEngine = class {
1226
1336
  param = param.replace("#", "");
1227
1337
  const globalParamValue = req.requesterDetails[param];
1228
1338
  if (!globalParamValue)
1229
- throw new RsError("SCHEMA_ERROR", `Invalid global keyword clause in route ${routeData.name}`);
1339
+ throw new RsError(
1340
+ "SCHEMA_ERROR",
1341
+ `Invalid global keyword clause in route (${routeData.path}) when looking for (#${param})`
1342
+ );
1230
1343
  sqlParams.push(globalParamValue);
1231
1344
  });
1232
1345
  return value.replace(new RegExp(/#[a-zA-Z][a-zA-Z0-9_]+/g), "?");
@@ -1235,7 +1348,7 @@ var SqlEngine = class {
1235
1348
  }
1236
1349
  };
1237
1350
 
1238
- // src/restura/sql/filterSqlParser.ts
1351
+ // src/restura/sql/filterPsqlParser.ts
1239
1352
  import peg from "pegjs";
1240
1353
  var filterSqlGrammar = `
1241
1354
  start = expressionList
@@ -1247,7 +1360,7 @@ expressionList =
1247
1360
 
1248
1361
  expression =
1249
1362
  negate:negate?"(" "column:" column:column ","? value:value? ","? type:type? ")"
1250
- {return \`\${negate? "!" : ""}(\${type? type(column, value) : \`\${column} = \${mysql.escape(value)}\`})\`;}
1363
+ {return \`\${negate? "!" : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
1251
1364
  /
1252
1365
  negate:negate?"("expression:expressionList")" { return \`\${negate? "!" : ""}(\${expression})\`; }
1253
1366
 
@@ -1255,80 +1368,33 @@ negate = "!"
1255
1368
 
1256
1369
  operator = "and"i / "or"i
1257
1370
 
1258
- column = left:text "." right:text { return \`\${mysql.escapeId(left)}.\${mysql.escapeId(right)}\`; }
1259
- /
1260
- text:text { return mysql.escapeId(text); }
1261
-
1371
+
1372
+ column = left:text "." right:text { return \`\${format.ident(left)}.\${format.ident(right)}\`; }
1373
+ /
1374
+ text:text { return format.ident(text); }
1375
+
1262
1376
 
1263
- text = text:[ a-z0-9-_:.@]i+ { return text.join("");}
1377
+ text = text:[a-z0-9-_:@]i+ { return text.join("");}
1264
1378
 
1265
1379
  type = "type:" type:typeString { return type; }
1266
- typeString = text:"startsWith" { return function(column, value) { return \`\${column} LIKE '\${mysql.escape(value).slice(1,-1)}%'\`; } } /
1267
- text:"endsWith" { return function(column, value) { return \`\${column} LIKE '%\${mysql.escape(value).slice(1,-1)}'\`; } } /
1268
- text:"contains" { return function(column, value) { return \`\${column} LIKE '%\${mysql.escape(value).slice(1,-1)}%'\`; } } /
1269
- text:"exact" { return function(column, value) { return \`\${column} = '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1270
- text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1271
- text:"greaterThan" { return function(column, value) { return \`\${column} > '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1272
- text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1273
- text:"lessThan" { return function(column, value) { return \`\${column} < '\${mysql.escape(value).slice(1,-1)}'\`; } } /
1380
+ typeString = text:"startsWith" { return function(column, value) { return \`\${column} ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
1381
+ text:"endsWith" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
1382
+ text:"contains" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
1383
+ text:"exact" { return function(column, value) { return \`\${column} = '\${format.literal(value).slice(1,-1)}'\`; } } /
1384
+ text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${format.literal(value).slice(1,-1)}'\`; } } /
1385
+ text:"greaterThan" { return function(column, value) { return \`\${column} > '\${format.literal(value).slice(1,-1)}'\`; } } /
1386
+ text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
1387
+ text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
1274
1388
  text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
1275
1389
 
1276
1390
  value = "value:" value:text { return value; }
1277
1391
 
1278
1392
  `;
1279
- var filterSqlParser = peg.generate(filterSqlGrammar, {
1280
- format: "commonjs"
1281
- // dependencies: { mysql: 'mysql' } // todo: figure out a better way to escape values depending on the database type
1393
+ var filterPsqlParser = peg.generate(filterSqlGrammar, {
1394
+ format: "commonjs",
1395
+ dependencies: { format: "pg-format" }
1282
1396
  });
1283
- var filterSqlParser_default = filterSqlParser;
1284
-
1285
- // src/restura/sql/PsqlEngine.ts
1286
- import { ObjectUtils as ObjectUtils4 } from "@redskytech/core-utils";
1287
-
1288
- // src/restura/sql/PsqlUtils.ts
1289
- import format2 from "pg-format";
1290
- function escapeColumnName(columnName) {
1291
- if (columnName === void 0) return "";
1292
- return `"${columnName.replace(/"/g, "")}"`.replace(".", '"."');
1293
- }
1294
- function questionMarksToOrderedParams(query) {
1295
- let count = 1;
1296
- return query.replace(/'\?'/g, () => `$${count++}`);
1297
- }
1298
- function insertObjectQuery(table, obj) {
1299
- const keys = Object.keys(obj);
1300
- const params = Object.values(obj);
1301
- const columns = keys.map((column) => escapeColumnName(column)).join(", ");
1302
- const values = params.map((value) => SQL`${value}`).join(", ");
1303
- const query = `INSERT INTO "${table}" (${columns})
1304
- VALUES (${values})
1305
- RETURNING *`;
1306
- return query;
1307
- }
1308
- function updateObjectQuery(table, obj, whereStatement) {
1309
- const setArray = [];
1310
- for (const i in obj) {
1311
- setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
1312
- }
1313
- return `UPDATE ${escapeColumnName(table)}
1314
- SET ${setArray.join(", ")} ${whereStatement}
1315
- RETURNING *`;
1316
- }
1317
- function isValueNumber2(value) {
1318
- return !isNaN(Number(value));
1319
- }
1320
- function SQL(strings, ...values) {
1321
- let query = strings[0];
1322
- values.forEach((value, index) => {
1323
- if (isValueNumber2(value)) {
1324
- query += value;
1325
- } else {
1326
- query += format2.literal(value);
1327
- }
1328
- query += strings[index + 1];
1329
- });
1330
- return query;
1331
- }
1397
+ var filterPsqlParser_default = filterPsqlParser;
1332
1398
 
1333
1399
  // src/restura/sql/PsqlEngine.ts
1334
1400
  var PsqlEngine = class extends SqlEngine {
@@ -1345,8 +1411,45 @@ var PsqlEngine = class extends SqlEngine {
1345
1411
  return "";
1346
1412
  }
1347
1413
  createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
1348
- console.log(req, schema, item, routeData, userRole, sqlParams);
1349
- return "";
1414
+ if (!item.subquery) return "";
1415
+ if (!ObjectUtils4.isArrayWithData(
1416
+ item.subquery.properties.filter((nestedItem) => {
1417
+ return this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
1418
+ ...routeData.joins,
1419
+ ...item.subquery.joins
1420
+ ]);
1421
+ })
1422
+ )) {
1423
+ return "'[]'";
1424
+ }
1425
+ return `COALESCE((
1426
+ SELECT JSON_AGG(JSON_BUILD_OBJECT(
1427
+ ${item.subquery.properties.map((nestedItem) => {
1428
+ if (!this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
1429
+ ...routeData.joins,
1430
+ ...item.subquery.joins
1431
+ ])) {
1432
+ return;
1433
+ }
1434
+ if (nestedItem.subquery) {
1435
+ return `"${nestedItem.name}", ${this.createNestedSelect(
1436
+ // recursion
1437
+ req,
1438
+ schema,
1439
+ nestedItem,
1440
+ routeData,
1441
+ userRole,
1442
+ sqlParams
1443
+ )}`;
1444
+ }
1445
+ return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
1446
+ }).filter(Boolean).join(",")}
1447
+ ))
1448
+ FROM
1449
+ "${item.subquery.table}"
1450
+ ${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, userRole, sqlParams)}
1451
+ ${this.generateWhereClause(req, item.subquery.where, routeData, sqlParams)}
1452
+ ), '[]')`;
1350
1453
  }
1351
1454
  async executeCreateRequest(req, routeData, schema) {
1352
1455
  const sqlParams = [];
@@ -1355,7 +1458,7 @@ var PsqlEngine = class extends SqlEngine {
1355
1458
  parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
1356
1459
  });
1357
1460
  const query = insertObjectQuery(routeData.table, __spreadValues(__spreadValues({}, req.data), parameterObj));
1358
- const createdItem = await this.psqlConnectionPool.queryOne(query, sqlParams);
1461
+ const createdItem = await this.psqlConnectionPool.queryOne(query, sqlParams, req.requesterDetails);
1359
1462
  const insertId = createdItem == null ? void 0 : createdItem.id;
1360
1463
  const whereData = [
1361
1464
  {
@@ -1405,12 +1508,14 @@ var PsqlEngine = class extends SqlEngine {
1405
1508
  if (routeData.type === "ONE") {
1406
1509
  return this.psqlConnectionPool.queryOne(
1407
1510
  `${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
1408
- sqlParams
1511
+ sqlParams,
1512
+ req.requesterDetails
1409
1513
  );
1410
1514
  } else if (routeData.type === "ARRAY") {
1411
1515
  return this.psqlConnectionPool.runQuery(
1412
1516
  `${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
1413
- sqlParams
1517
+ sqlParams,
1518
+ req.requesterDetails
1414
1519
  );
1415
1520
  } else if (routeData.type === "PAGED") {
1416
1521
  const data = req.data;
@@ -1422,7 +1527,8 @@ ${sqlStatement};`,
1422
1527
  data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER,
1423
1528
  (data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER,
1424
1529
  ...sqlParams
1425
- ]
1530
+ ],
1531
+ req.requesterDetails
1426
1532
  );
1427
1533
  let total = 0;
1428
1534
  if (ObjectUtils4.isArrayWithData(pageResults)) {
@@ -1453,7 +1559,7 @@ ${sqlStatement};`,
1453
1559
  }
1454
1560
  const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
1455
1561
  const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
1456
- await this.psqlConnectionPool.queryOne(query, [...sqlParams]);
1562
+ await this.psqlConnectionPool.queryOne(query, [...sqlParams], req.requesterDetails);
1457
1563
  return this.executeGetRequest(req, routeData, schema);
1458
1564
  }
1459
1565
  async executeDeleteRequest(req, routeData, schema) {
@@ -1471,16 +1577,26 @@ ${sqlStatement};`,
1471
1577
  FROM "${routeData.table}" ${joinStatement}`;
1472
1578
  deleteStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
1473
1579
  deleteStatement += ";";
1474
- await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams);
1580
+ await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
1475
1581
  return true;
1476
1582
  }
1477
1583
  generateJoinStatements(req, joins, baseTable, routeData, schema, userRole, sqlParams) {
1478
- console.log(req, joins, baseTable, routeData, schema, userRole, sqlParams);
1479
- return "";
1480
- }
1481
- getTableSchema(schema, tableName) {
1482
- console.log(schema, tableName);
1483
- return {};
1584
+ let joinStatements = "";
1585
+ joins.forEach((item) => {
1586
+ if (!this.doesRoleHavePermissionToTable(userRole, schema, item.table))
1587
+ throw new RsError("UNAUTHORIZED", "You do not have permission to access this table");
1588
+ if (item.custom) {
1589
+ const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
1590
+ joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)} ON ${customReplaced}
1591
+ `;
1592
+ } else {
1593
+ joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)}${item.alias ? `AS "${item.alias}"` : ""} ON "${baseTable}"."${item.localColumnName}" = ${escapeColumnName(item.alias ? item.alias : item.table)}.${escapeColumnName(
1594
+ item.foreignColumnName
1595
+ )}
1596
+ `;
1597
+ }
1598
+ });
1599
+ return joinStatements;
1484
1600
  }
1485
1601
  generateGroupBy(routeData) {
1486
1602
  let groupBy = "";
@@ -1539,22 +1655,24 @@ ${sqlStatement};`,
1539
1655
  const data = req.data;
1540
1656
  if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
1541
1657
  let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
1658
+ var _a;
1542
1659
  const requestParam = routeData.request.find((item) => {
1543
1660
  return item.name === value.replace("$", "");
1544
1661
  });
1545
1662
  if (!requestParam)
1546
1663
  throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
1547
- return data[requestParam.name];
1664
+ return ((_a = data[requestParam.name]) == null ? void 0 : _a.toString()) || "";
1548
1665
  });
1549
1666
  statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
1667
+ var _a;
1550
1668
  const requestParam = routeData.request.find((item) => {
1551
1669
  return item.name === value.replace("#", "");
1552
1670
  });
1553
1671
  if (!requestParam)
1554
1672
  throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
1555
- return data[requestParam.name];
1673
+ return ((_a = data[requestParam.name]) == null ? void 0 : _a.toString()) || "";
1556
1674
  });
1557
- statement = filterSqlParser_default.parse(statement);
1675
+ statement = filterPsqlParser_default.parse(statement);
1558
1676
  if (whereClause.startsWith("WHERE")) {
1559
1677
  whereClause += ` AND (${statement})
1560
1678
  `;
@@ -1567,24 +1685,33 @@ ${sqlStatement};`,
1567
1685
  }
1568
1686
  };
1569
1687
 
1570
- // src/restura/restura.ts
1571
- import { types } from "pg";
1572
-
1573
1688
  // src/restura/sql/PsqlPool.ts
1574
- import { Pool } from "pg";
1689
+ import pg from "pg";
1690
+ import format3 from "pg-format";
1691
+ var { Pool } = pg;
1575
1692
  var PsqlPool = class {
1576
1693
  constructor(poolConfig) {
1577
1694
  this.poolConfig = poolConfig;
1578
1695
  this.pool = new Pool(poolConfig);
1696
+ this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
1697
+ logger.info("Connected to PostgreSQL database");
1698
+ }).catch((error) => {
1699
+ logger.error("Error connecting to database", error);
1700
+ process.exit(1);
1701
+ });
1579
1702
  }
1580
1703
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1581
- async queryOne(query, options) {
1704
+ async queryOne(query, options, requesterDetails) {
1582
1705
  const formattedQuery = questionMarksToOrderedParams(query);
1706
+ this.logSqlStatement(formattedQuery, options, requesterDetails);
1583
1707
  try {
1584
1708
  const response = await this.pool.query(formattedQuery, options);
1709
+ if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
1710
+ else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
1585
1711
  return response.rows[0];
1586
1712
  } catch (error) {
1587
1713
  console.error(error, query, options);
1714
+ if (RsError.isRsError(error)) throw error;
1588
1715
  if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1589
1716
  throw new RsError("DUPLICATE", error.message);
1590
1717
  }
@@ -1592,8 +1719,9 @@ var PsqlPool = class {
1592
1719
  }
1593
1720
  }
1594
1721
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1595
- async runQuery(query, options) {
1722
+ async runQuery(query, options, requesterDetails) {
1596
1723
  const formattedQuery = questionMarksToOrderedParams(query);
1724
+ this.logSqlStatement(formattedQuery, options, requesterDetails);
1597
1725
  const queryUpdated = query.replace(/[\t\n]/g, " ");
1598
1726
  console.log(queryUpdated, options);
1599
1727
  try {
@@ -1607,9 +1735,30 @@ var PsqlPool = class {
1607
1735
  throw new RsError("DATABASE_ERROR", `${error.message}`);
1608
1736
  }
1609
1737
  }
1738
+ logSqlStatement(query, options, requesterDetails, prefix = "") {
1739
+ if (logger.level !== "silly") return;
1740
+ let sqlStatement = "";
1741
+ if (options.length === 0) {
1742
+ sqlStatement = query;
1743
+ } else {
1744
+ let stringIndex = 0;
1745
+ sqlStatement = query.replace(/\$\d+/g, () => {
1746
+ const value = options[stringIndex++];
1747
+ if (typeof value === "number") return value.toString();
1748
+ return format3.literal(value);
1749
+ });
1750
+ }
1751
+ let initiator = "Anonymous";
1752
+ if ("userId" in requesterDetails && requesterDetails.userId)
1753
+ initiator = `User Id (${requesterDetails.userId.toString()})`;
1754
+ if ("isSystemUser" in requesterDetails && requesterDetails.isSystemUser) initiator = "SYSTEM";
1755
+ logger.silly(`${prefix}query by ${initiator}, Query ->
1756
+ ${sqlStatement}`);
1757
+ }
1610
1758
  };
1611
1759
 
1612
1760
  // src/restura/restura.ts
1761
+ var { types } = pg2;
1613
1762
  var ResturaEngine = class {
1614
1763
  constructor() {
1615
1764
  this.publicEndpoints = {
@@ -1627,10 +1776,11 @@ var ResturaEngine = class {
1627
1776
  * @returns A promise that resolves when the initialization is complete.
1628
1777
  */
1629
1778
  async init(app, authenticationHandler, psqlConnectionPool) {
1779
+ this.resturaConfig = config2.validate("restura", resturaConfigSchema);
1630
1780
  this.psqlConnectionPool = psqlConnectionPool;
1631
1781
  this.psqlEngine = new PsqlEngine(this.psqlConnectionPool);
1632
1782
  setupPgReturnTypes();
1633
- this.resturaConfig = config2.validate("restura", resturaConfigSchema);
1783
+ await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
1634
1784
  this.authenticationHandler = authenticationHandler;
1635
1785
  app.use(compression());
1636
1786
  app.use(bodyParser.json({ limit: "32mb" }));
@@ -1693,7 +1843,7 @@ var ResturaEngine = class {
1693
1843
  * @returns A promise that resolves when the API has been successfully generated and written to the output file.
1694
1844
  */
1695
1845
  async generateApiFromSchema(outputFile, providedSchema) {
1696
- fs2.writeFileSync(
1846
+ fs3.writeFileSync(
1697
1847
  outputFile,
1698
1848
  await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
1699
1849
  );
@@ -1706,7 +1856,7 @@ var ResturaEngine = class {
1706
1856
  * @returns A promise that resolves when the model has been successfully written to the output file.
1707
1857
  */
1708
1858
  async generateModelFromSchema(outputFile, providedSchema) {
1709
- fs2.writeFileSync(
1859
+ fs3.writeFileSync(
1710
1860
  outputFile,
1711
1861
  await modelGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
1712
1862
  );
@@ -1718,11 +1868,11 @@ var ResturaEngine = class {
1718
1868
  * @throws {Error} If the schema file is missing or the schema is not valid.
1719
1869
  */
1720
1870
  async getLatestFileSystemSchema() {
1721
- if (!fs2.existsSync(this.resturaConfig.schemaFilePath)) {
1871
+ if (!fs3.existsSync(this.resturaConfig.schemaFilePath)) {
1722
1872
  logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
1723
1873
  throw new Error("Missing restura schema file");
1724
1874
  }
1725
- const schemaFileData = fs2.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
1875
+ const schemaFileData = fs3.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
1726
1876
  const schema = ObjectUtils5.safeParse(schemaFileData);
1727
1877
  const isValid = await isSchemaValid(schema);
1728
1878
  if (!isValid) {
@@ -1743,9 +1893,9 @@ var ResturaEngine = class {
1743
1893
  async getHashes(providedSchema) {
1744
1894
  var _a, _b, _c, _d;
1745
1895
  const schemaHash = await this.generateHashForSchema(providedSchema);
1746
- const apiFile = fs2.readFileSync(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
1896
+ const apiFile = fs3.readFileSync(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
1747
1897
  const apiCreatedSchemaHash = (_b = (_a = apiFile.toString().match(/\((.*)\)/)) == null ? void 0 : _a[1]) != null ? _b : "";
1748
- const modelFile = fs2.readFileSync(path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
1898
+ const modelFile = fs3.readFileSync(path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
1749
1899
  const modelCreatedSchemaHash = (_d = (_c = modelFile.toString().match(/\((.*)\)/)) == null ? void 0 : _c[1]) != null ? _d : "";
1750
1900
  return {
1751
1901
  schemaHash,
@@ -1781,27 +1931,27 @@ var ResturaEngine = class {
1781
1931
  logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
1782
1932
  }
1783
1933
  async validateGeneratedTypesFolder() {
1784
- if (!fs2.existsSync(this.resturaConfig.generatedTypesPath)) {
1785
- fs2.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
1934
+ if (!fs3.existsSync(this.resturaConfig.generatedTypesPath)) {
1935
+ fs3.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
1786
1936
  }
1787
- const hasApiFile = fs2.existsSync(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
1788
- const hasModelsFile = fs2.existsSync(path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
1937
+ const hasApiFile = fs3.existsSync(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
1938
+ const hasModelsFile = fs3.existsSync(path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
1789
1939
  if (!hasApiFile) {
1790
- await this.generateApiFromSchema(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1940
+ await this.generateApiFromSchema(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1791
1941
  }
1792
1942
  if (!hasModelsFile) {
1793
1943
  await this.generateModelFromSchema(
1794
- path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1944
+ path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1795
1945
  this.schema
1796
1946
  );
1797
1947
  }
1798
1948
  const hashes = await this.getHashes(this.schema);
1799
1949
  if (hashes.schemaHash !== hashes.apiCreatedSchemaHash) {
1800
- await this.generateApiFromSchema(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1950
+ await this.generateApiFromSchema(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1801
1951
  }
1802
1952
  if (hashes.schemaHash !== hashes.modelCreatedSchemaHash) {
1803
1953
  await this.generateModelFromSchema(
1804
- path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1954
+ path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1805
1955
  this.schema
1806
1956
  );
1807
1957
  }
@@ -1831,9 +1981,9 @@ var ResturaEngine = class {
1831
1981
  }
1832
1982
  }
1833
1983
  async updateTypes() {
1834
- await this.generateApiFromSchema(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1984
+ await this.generateApiFromSchema(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
1835
1985
  await this.generateModelFromSchema(
1836
- path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1986
+ path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
1837
1987
  this.schema
1838
1988
  );
1839
1989
  }
@@ -1856,11 +2006,16 @@ var ResturaEngine = class {
1856
2006
  const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
1857
2007
  this.validateAuthorization(req, routeData);
1858
2008
  validateRequestParams(req, routeData, this.customTypeValidation);
2009
+ if (this.isCustomRoute(routeData)) {
2010
+ await this.runCustomRouteLogic(req, res, routeData);
2011
+ return;
2012
+ }
1859
2013
  const data = await this.psqlEngine.runQueryForRoute(
1860
2014
  req,
1861
2015
  routeData,
1862
2016
  this.schema
1863
2017
  );
2018
+ this.responseValidator.validateResponseParams(data, req.baseUrl, routeData);
1864
2019
  if (routeData.type === "PAGED") res.sendNoWrap(data);
1865
2020
  else res.sendData(data);
1866
2021
  } catch (e) {
@@ -1870,34 +2025,25 @@ var ResturaEngine = class {
1870
2025
  isCustomRoute(route) {
1871
2026
  return route.type === "CUSTOM_ONE" || route.type === "CUSTOM_ARRAY" || route.type === "CUSTOM_PAGED";
1872
2027
  }
1873
- // @boundMethod
1874
- // private async runCustomRouteLogic<T>(req: RsRequest<T>, res: RsResponse<T>, routeData: RouteData) {
1875
- // const version = req.baseUrl.split('/')[2];
1876
- // let domain = routeData.path.split('/')[1];
1877
- // domain = domain.split('-').reduce((acc, value, index) => {
1878
- // if (index === 0) acc = value;
1879
- // else acc += StringUtils.capitalizeFirst(value);
1880
- // return acc;
1881
- // }, '');
1882
- // const customApiName = `${StringUtils.capitalizeFirst(domain)}Api${StringUtils.capitalizeFirst(version)}`;
1883
- // const customApi = apiFactory.getCustomApi(customApiName);
1884
- // if (!customApi) throw new RsError('NOT_FOUND', `API domain ${domain}-${version} not found`);
1885
- // const functionName = `${routeData.method.toLowerCase()}${routeData.path
1886
- // .replace(new RegExp('-', 'g'), '/')
1887
- // .split('/')
1888
- // .reduce((acc, cur) => {
1889
- // if (cur === '') return acc;
1890
- // return acc + StringUtils.capitalizeFirst(cur);
1891
- // }, '')}`;
1892
- // // @ts-expect-error - Here we are dynamically calling the function from a custom class, not sure how to typescript this
1893
- // const customFunction = customApi[functionName] as (
1894
- // req: RsRequest<T>,
1895
- // res: RsResponse<T>,
1896
- // routeData: RouteData
1897
- // ) => Promise<void>;
1898
- // if (!customFunction) throw new RsError('NOT_FOUND', `API path ${routeData.path} not implemented`);
1899
- // await customFunction(req, res, routeData);
1900
- // }
2028
+ async runCustomRouteLogic(req, res, routeData) {
2029
+ const version = req.baseUrl.split("/")[2];
2030
+ let domain = routeData.path.split("/")[1];
2031
+ domain = domain.split("-").reduce((acc, value, index) => {
2032
+ if (index === 0) acc = value;
2033
+ else acc += StringUtils3.capitalizeFirst(value);
2034
+ return acc;
2035
+ }, "");
2036
+ const customApiName = `${StringUtils3.capitalizeFirst(domain)}Api${StringUtils3.capitalizeFirst(version)}`;
2037
+ const customApi = customApiFactory_default.getCustomApi(customApiName);
2038
+ if (!customApi) throw new RsError("NOT_FOUND", `API domain ${domain}-${version} not found`);
2039
+ const functionName = `${routeData.method.toLowerCase()}${routeData.path.replace(new RegExp("-", "g"), "/").split("/").reduce((acc, cur) => {
2040
+ if (cur === "") return acc;
2041
+ return acc + StringUtils3.capitalizeFirst(cur);
2042
+ }, "")}`;
2043
+ const customFunction = customApi[functionName];
2044
+ if (!customFunction) throw new RsError("NOT_FOUND", `API path ${routeData.path} not implemented`);
2045
+ await customFunction(req, res, routeData);
2046
+ }
1901
2047
  async generateHashForSchema(providedSchema) {
1902
2048
  const schemaPrettyStr = await prettier3.format(JSON.stringify(providedSchema), __spreadValues({
1903
2049
  parser: "json"
@@ -1922,7 +2068,7 @@ var ResturaEngine = class {
1922
2068
  printWidth: 120,
1923
2069
  singleQuote: true
1924
2070
  }));
1925
- fs2.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
2071
+ fs3.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
1926
2072
  }
1927
2073
  resetPublicEndpoints() {
1928
2074
  this.publicEndpoints = {
@@ -1939,13 +2085,13 @@ var ResturaEngine = class {
1939
2085
  if (!routeData.roles.includes(role))
1940
2086
  throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
1941
2087
  }
1942
- getRouteData(method, baseUrl, path3) {
2088
+ getRouteData(method, baseUrl, path4) {
1943
2089
  const endpoint = this.schema.endpoints.find((item) => {
1944
2090
  return item.baseUrl === baseUrl;
1945
2091
  });
1946
2092
  if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
1947
2093
  const route = endpoint.routes.find((item) => {
1948
- return item.method === method && item.path === path3;
2094
+ return item.method === method && item.path === path4;
1949
2095
  });
1950
2096
  if (!route) throw new RsError("NOT_FOUND", "Route not found");
1951
2097
  return route;
@@ -1972,6 +2118,9 @@ __decorateClass([
1972
2118
  __decorateClass([
1973
2119
  boundMethod
1974
2120
  ], ResturaEngine.prototype, "isCustomRoute", 1);
2121
+ __decorateClass([
2122
+ boundMethod
2123
+ ], ResturaEngine.prototype, "runCustomRouteLogic", 1);
1975
2124
  var setupPgReturnTypes = () => {
1976
2125
  const TIMESTAMPTZ_OID = 1184;
1977
2126
  types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
@@ -1985,8 +2134,16 @@ var setupPgReturnTypes = () => {
1985
2134
  setupPgReturnTypes();
1986
2135
  var restura = new ResturaEngine();
1987
2136
  export {
2137
+ HtmlStatusCodes,
1988
2138
  PsqlPool,
2139
+ RsError,
2140
+ SQL,
2141
+ escapeColumnName,
2142
+ insertObjectQuery,
2143
+ isValueNumber2 as isValueNumber,
1989
2144
  logger,
1990
- restura
2145
+ questionMarksToOrderedParams,
2146
+ restura,
2147
+ updateObjectQuery
1991
2148
  };
1992
2149
  //# sourceMappingURL=index.mjs.map