@restura/core 0.1.0-alpha.7 → 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.
- package/dist/index.d.mts +187 -33
- package/dist/index.d.ts +187 -33
- package/dist/index.js +1138 -271
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1128 -270
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -5
- package/dist/acorn-SW5GI5G7.mjs +0 -3016
- package/dist/acorn-SW5GI5G7.mjs.map +0 -1
- package/dist/angular-FYEH6QOL.mjs +0 -1547
- package/dist/angular-FYEH6QOL.mjs.map +0 -1
- package/dist/babel-V6GZHMYX.mjs +0 -6911
- package/dist/babel-V6GZHMYX.mjs.map +0 -1
- package/dist/chunk-TL4KRYOF.mjs +0 -58
- package/dist/chunk-TL4KRYOF.mjs.map +0 -1
- package/dist/estree-67ZCSSSI.mjs +0 -4396
- package/dist/estree-67ZCSSSI.mjs.map +0 -1
- package/dist/flow-SJW7PRXX.mjs +0 -26365
- package/dist/flow-SJW7PRXX.mjs.map +0 -1
- package/dist/glimmer-PF2X22V2.mjs +0 -2995
- package/dist/glimmer-PF2X22V2.mjs.map +0 -1
- package/dist/graphql-NOJ5HX7K.mjs +0 -1253
- package/dist/graphql-NOJ5HX7K.mjs.map +0 -1
- package/dist/html-ROPIWVPQ.mjs +0 -2780
- package/dist/html-ROPIWVPQ.mjs.map +0 -1
- package/dist/index.cjs +0 -34
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -3
- package/dist/markdown-PFYT4MSP.mjs +0 -3423
- package/dist/markdown-PFYT4MSP.mjs.map +0 -1
- package/dist/meriyah-5NLZXLMA.mjs +0 -2356
- package/dist/meriyah-5NLZXLMA.mjs.map +0 -1
- package/dist/postcss-ITO6IEN5.mjs +0 -5027
- package/dist/postcss-ITO6IEN5.mjs.map +0 -1
- package/dist/typescript-6IE7K56Q.mjs +0 -13392
- package/dist/typescript-6IE7K56Q.mjs.map +0 -1
- package/dist/yaml-DB2OVPLH.mjs +0 -4230
- package/dist/yaml-DB2OVPLH.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
2
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
3
5
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
4
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
7
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -15,6 +17,19 @@ var __spreadValues = (a, b) => {
|
|
|
15
17
|
}
|
|
16
18
|
return a;
|
|
17
19
|
};
|
|
20
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
|
+
var __objRest = (source, exclude) => {
|
|
22
|
+
var target = {};
|
|
23
|
+
for (var prop in source)
|
|
24
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
25
|
+
target[prop] = source[prop];
|
|
26
|
+
if (source != null && __getOwnPropSymbols)
|
|
27
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
28
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
}
|
|
31
|
+
return target;
|
|
32
|
+
};
|
|
18
33
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
19
34
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
20
35
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
@@ -32,12 +47,13 @@ import { format } from "logform";
|
|
|
32
47
|
// src/config.schema.ts
|
|
33
48
|
import { z } from "zod";
|
|
34
49
|
var loggerConfigSchema = z.object({
|
|
35
|
-
level: z.enum(["info", "warn", "error", "debug"]).default("info")
|
|
50
|
+
level: z.enum(["info", "warn", "error", "debug", "silly"]).default("info")
|
|
36
51
|
});
|
|
37
52
|
var resturaConfigSchema = z.object({
|
|
38
53
|
authToken: z.string().min(1, "Missing Restura Auth Token"),
|
|
39
54
|
sendErrorStackTrace: z.boolean().default(false),
|
|
40
55
|
schemaFilePath: z.string().default(process.cwd() + "/restura.schema.json"),
|
|
56
|
+
customApiFolderPath: z.string().default(process.cwd() + "/dist/api"),
|
|
41
57
|
generatedTypesPath: z.string().default(process.cwd() + "/src/@types")
|
|
42
58
|
});
|
|
43
59
|
|
|
@@ -76,8 +92,74 @@ var logger = winston.createLogger({
|
|
|
76
92
|
]
|
|
77
93
|
});
|
|
78
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
|
+
|
|
79
161
|
// src/restura/restura.ts
|
|
80
|
-
import { ObjectUtils as
|
|
162
|
+
import { ObjectUtils as ObjectUtils5, StringUtils as StringUtils3 } from "@redskytech/core-utils";
|
|
81
163
|
import { config as config2 } from "@restura/internal";
|
|
82
164
|
|
|
83
165
|
// ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
|
|
@@ -132,60 +214,12 @@ import compression from "compression";
|
|
|
132
214
|
import cookieParser from "cookie-parser";
|
|
133
215
|
import { createHash } from "crypto";
|
|
134
216
|
import * as express from "express";
|
|
135
|
-
import
|
|
136
|
-
import
|
|
217
|
+
import fs3 from "fs";
|
|
218
|
+
import path3 from "path";
|
|
219
|
+
import pg2 from "pg";
|
|
137
220
|
import * as prettier3 from "prettier";
|
|
138
221
|
|
|
139
|
-
// src/restura/
|
|
140
|
-
var RsError = class _RsError {
|
|
141
|
-
constructor(errCode, message) {
|
|
142
|
-
this.err = errCode;
|
|
143
|
-
this.msg = message || "";
|
|
144
|
-
this.status = _RsError.htmlStatus(errCode);
|
|
145
|
-
this.stack = new Error().stack || "";
|
|
146
|
-
}
|
|
147
|
-
static htmlStatus(code) {
|
|
148
|
-
return htmlStatusMap[code];
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
var htmlStatusMap = {
|
|
152
|
-
UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
|
|
153
|
-
NOT_FOUND: 404 /* NOT_FOUND */,
|
|
154
|
-
EMAIL_TAKEN: 409 /* CONFLICT */,
|
|
155
|
-
FORBIDDEN: 403 /* FORBIDDEN */,
|
|
156
|
-
CONFLICT: 409 /* CONFLICT */,
|
|
157
|
-
UNAUTHORIZED: 401 /* UNAUTHORIZED */,
|
|
158
|
-
UPDATE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
159
|
-
CREATE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
160
|
-
DELETE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
161
|
-
DELETE_FAILURE: 500 /* SERVER_ERROR */,
|
|
162
|
-
BAD_REQUEST: 400 /* BAD_REQUEST */,
|
|
163
|
-
INVALID_TOKEN: 401 /* UNAUTHORIZED */,
|
|
164
|
-
INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
|
|
165
|
-
DUPLICATE_TOKEN: 409 /* CONFLICT */,
|
|
166
|
-
DUPLICATE_USERNAME: 409 /* CONFLICT */,
|
|
167
|
-
DUPLICATE_EMAIL: 409 /* CONFLICT */,
|
|
168
|
-
DUPLICATE: 409 /* CONFLICT */,
|
|
169
|
-
EMAIL_NOT_VERIFIED: 400 /* BAD_REQUEST */,
|
|
170
|
-
UPDATE_WITHOUT_ID: 400 /* BAD_REQUEST */,
|
|
171
|
-
CONNECTION_ERROR: 599 /* NETWORK_CONNECT_TIMEOUT */,
|
|
172
|
-
INVALID_PAYMENT: 403 /* FORBIDDEN */,
|
|
173
|
-
DECLINED_PAYMENT: 403 /* FORBIDDEN */,
|
|
174
|
-
INTEGRATION_ERROR: 500 /* SERVER_ERROR */,
|
|
175
|
-
CANNOT_RESERVE: 403 /* FORBIDDEN */,
|
|
176
|
-
REFUND_FAILURE: 403 /* FORBIDDEN */,
|
|
177
|
-
INVALID_INVOICE: 403 /* FORBIDDEN */,
|
|
178
|
-
INVALID_COUPON: 403 /* FORBIDDEN */,
|
|
179
|
-
SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
|
|
180
|
-
METHOD_UNALLOWED: 405 /* METHOD_NOT_ALLOWED */,
|
|
181
|
-
LOGIN_EXPIRED: 401 /* UNAUTHORIZED */,
|
|
182
|
-
THIRD_PARTY_ERROR: 400 /* BAD_REQUEST */,
|
|
183
|
-
ACCESS_DENIED: 403 /* FORBIDDEN */,
|
|
184
|
-
DATABASE_ERROR: 500 /* SERVER_ERROR */,
|
|
185
|
-
SCHEMA_ERROR: 500 /* SERVER_ERROR */
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// src/restura/SqlUtils.ts
|
|
222
|
+
// src/restura/sql/SqlUtils.ts
|
|
189
223
|
var SqlUtils = class _SqlUtils {
|
|
190
224
|
static convertDatabaseTypeToTypescript(type, value) {
|
|
191
225
|
type = type.toLocaleLowerCase();
|
|
@@ -276,9 +310,9 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
276
310
|
return { validator: "any" };
|
|
277
311
|
}
|
|
278
312
|
getTypeFromTable(selector, name) {
|
|
279
|
-
const
|
|
280
|
-
if (
|
|
281
|
-
const tableName =
|
|
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];
|
|
282
316
|
const table = this.database.find((t) => t.name == tableName);
|
|
283
317
|
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
284
318
|
if (!table || !column) return { validator: "any", isOptional: false };
|
|
@@ -494,10 +528,10 @@ var ApiTree = class _ApiTree {
|
|
|
494
528
|
return `${p.name}${optional ? "?" : ""}:${responseType}${array ? "[]" : ""}`;
|
|
495
529
|
}
|
|
496
530
|
getTypeFromTable(selector, name) {
|
|
497
|
-
const
|
|
498
|
-
if (
|
|
499
|
-
let tableName =
|
|
500
|
-
const columnName =
|
|
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];
|
|
501
535
|
let table = this.database.find((t) => t.name == tableName);
|
|
502
536
|
if (!table && tableName.includes("_")) {
|
|
503
537
|
const tableAliasSplit = tableName.split("_");
|
|
@@ -512,8 +546,8 @@ var ApiTree = class _ApiTree {
|
|
|
512
546
|
};
|
|
513
547
|
}
|
|
514
548
|
};
|
|
515
|
-
function pathToNamespaces(
|
|
516
|
-
return
|
|
549
|
+
function pathToNamespaces(path4) {
|
|
550
|
+
return path4.split("/").map((e) => StringUtils.toPascalCasing(e)).filter((e) => e);
|
|
517
551
|
}
|
|
518
552
|
function apiGenerator(schema, schemaHash) {
|
|
519
553
|
let apiString = `/** Auto generated file from Schema Hash (${schemaHash}). DO NOT MODIFY **/`;
|
|
@@ -546,6 +580,92 @@ function apiGenerator(schema, schemaHash) {
|
|
|
546
580
|
}));
|
|
547
581
|
}
|
|
548
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
|
+
|
|
549
669
|
// src/restura/middleware/addApiResponseFunctions.ts
|
|
550
670
|
function addApiResponseFunctions(req, res, next) {
|
|
551
671
|
res.sendData = function(data, statusCode = 200) {
|
|
@@ -574,121 +694,104 @@ function addApiResponseFunctions(req, res, next) {
|
|
|
574
694
|
next();
|
|
575
695
|
}
|
|
576
696
|
|
|
577
|
-
// src/restura/
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
body = "body";
|
|
586
|
-
}
|
|
587
|
-
const bodyData = req[body];
|
|
588
|
-
if (bodyData) {
|
|
589
|
-
for (const attr in bodyData) {
|
|
590
|
-
if (attr === "token") {
|
|
591
|
-
delete bodyData[attr];
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
if (bodyData[attr] instanceof Array) {
|
|
595
|
-
const attrList = [];
|
|
596
|
-
for (const value of bodyData[attr]) {
|
|
597
|
-
if (isNaN(Number(value))) continue;
|
|
598
|
-
attrList.push(Number(value));
|
|
599
|
-
}
|
|
600
|
-
if (ObjectUtils2.isArrayWithData(attrList)) {
|
|
601
|
-
bodyData[attr] = attrList;
|
|
602
|
-
}
|
|
603
|
-
} else {
|
|
604
|
-
bodyData[attr] = ObjectUtils2.safeParse(bodyData[attr]);
|
|
605
|
-
if (isNaN(Number(bodyData[attr]))) continue;
|
|
606
|
-
bodyData[attr] = Number(bodyData[attr]);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
return bodyData;
|
|
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
|
+
};
|
|
611
705
|
}
|
|
612
706
|
|
|
613
707
|
// src/restura/restura.schema.ts
|
|
708
|
+
import { z as z3 } from "zod";
|
|
709
|
+
|
|
710
|
+
// src/restura/types/validation.types.ts
|
|
614
711
|
import { z as z2 } from "zod";
|
|
615
|
-
var
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
712
|
+
var validatorDataSchemeValue = z2.union([z2.string(), z2.array(z2.string()), z2.number(), z2.array(z2.number())]);
|
|
713
|
+
var validatorDataSchema = z2.object({
|
|
714
|
+
type: z2.enum(["TYPE_CHECK", "MIN", "MAX", "ONE_OF"]),
|
|
715
|
+
value: validatorDataSchemeValue
|
|
619
716
|
}).strict();
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
717
|
+
|
|
718
|
+
// src/restura/restura.schema.ts
|
|
719
|
+
var orderBySchema = z3.object({
|
|
720
|
+
columnName: z3.string(),
|
|
721
|
+
order: z3.enum(["ASC", "DESC"]),
|
|
722
|
+
tableName: z3.string()
|
|
623
723
|
}).strict();
|
|
624
|
-
var
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
operator: z2.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
|
|
628
|
-
value: z2.string().optional(),
|
|
629
|
-
custom: z2.string().optional(),
|
|
630
|
-
conjunction: z2.enum(["AND", "OR"]).optional()
|
|
724
|
+
var groupBySchema = z3.object({
|
|
725
|
+
columnName: z3.string(),
|
|
726
|
+
tableName: z3.string()
|
|
631
727
|
}).strict();
|
|
632
|
-
var
|
|
633
|
-
|
|
634
|
-
|
|
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()
|
|
635
735
|
}).strict();
|
|
636
|
-
var
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
foreignColumnName: z2.string().optional(),
|
|
640
|
-
custom: z2.string().optional(),
|
|
641
|
-
type: z2.enum(["LEFT", "INNER"]),
|
|
642
|
-
alias: z2.string().optional()
|
|
736
|
+
var assignmentDataSchema = z3.object({
|
|
737
|
+
name: z3.string(),
|
|
738
|
+
value: z3.string()
|
|
643
739
|
}).strict();
|
|
644
|
-
var
|
|
645
|
-
|
|
646
|
-
|
|
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()
|
|
647
747
|
}).strict();
|
|
648
|
-
var requestDataSchema =
|
|
649
|
-
name:
|
|
650
|
-
required:
|
|
651
|
-
validator:
|
|
748
|
+
var requestDataSchema = z3.object({
|
|
749
|
+
name: z3.string(),
|
|
750
|
+
required: z3.boolean(),
|
|
751
|
+
validator: z3.array(validatorDataSchema)
|
|
652
752
|
}).strict();
|
|
653
|
-
var responseDataSchema =
|
|
654
|
-
name:
|
|
655
|
-
selector:
|
|
656
|
-
subquery:
|
|
657
|
-
table:
|
|
658
|
-
joins:
|
|
659
|
-
where:
|
|
660
|
-
properties:
|
|
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)),
|
|
661
761
|
// Explicit type for the lazy schema
|
|
662
762
|
groupBy: groupBySchema.optional(),
|
|
663
763
|
orderBy: orderBySchema.optional()
|
|
664
764
|
}).optional()
|
|
665
765
|
}).strict();
|
|
666
|
-
var routeDataBaseSchema =
|
|
667
|
-
method:
|
|
668
|
-
name:
|
|
669
|
-
description:
|
|
670
|
-
path:
|
|
671
|
-
roles:
|
|
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())
|
|
672
772
|
}).strict();
|
|
673
773
|
var standardRouteSchema = routeDataBaseSchema.extend({
|
|
674
|
-
type:
|
|
675
|
-
table:
|
|
676
|
-
joins:
|
|
677
|
-
assignments:
|
|
678
|
-
where:
|
|
679
|
-
request:
|
|
680
|
-
response:
|
|
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),
|
|
681
781
|
groupBy: groupBySchema.optional(),
|
|
682
782
|
orderBy: orderBySchema.optional()
|
|
683
783
|
}).strict();
|
|
684
784
|
var customRouteSchema = routeDataBaseSchema.extend({
|
|
685
|
-
type:
|
|
686
|
-
responseType:
|
|
687
|
-
requestType:
|
|
688
|
-
request:
|
|
689
|
-
|
|
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()
|
|
690
793
|
}).strict();
|
|
691
|
-
var postgresColumnNumericTypesSchema =
|
|
794
|
+
var postgresColumnNumericTypesSchema = z3.enum([
|
|
692
795
|
"SMALLINT",
|
|
693
796
|
// 2 bytes, -32,768 to 32,767
|
|
694
797
|
"INTEGER",
|
|
@@ -708,7 +811,7 @@ var postgresColumnNumericTypesSchema = z2.enum([
|
|
|
708
811
|
"BIGSERIAL"
|
|
709
812
|
// auto-incrementing big integer
|
|
710
813
|
]);
|
|
711
|
-
var postgresColumnStringTypesSchema =
|
|
814
|
+
var postgresColumnStringTypesSchema = z3.enum([
|
|
712
815
|
"CHAR",
|
|
713
816
|
// fixed-length, blank-padded
|
|
714
817
|
"VARCHAR",
|
|
@@ -718,7 +821,7 @@ var postgresColumnStringTypesSchema = z2.enum([
|
|
|
718
821
|
"BYTEA"
|
|
719
822
|
// binary data
|
|
720
823
|
]);
|
|
721
|
-
var postgresColumnDateTypesSchema =
|
|
824
|
+
var postgresColumnDateTypesSchema = z3.enum([
|
|
722
825
|
"DATE",
|
|
723
826
|
// calendar date (year, month, day)
|
|
724
827
|
"TIMESTAMP",
|
|
@@ -730,7 +833,7 @@ var postgresColumnDateTypesSchema = z2.enum([
|
|
|
730
833
|
"INTERVAL"
|
|
731
834
|
// time span
|
|
732
835
|
]);
|
|
733
|
-
var mariaDbColumnNumericTypesSchema =
|
|
836
|
+
var mariaDbColumnNumericTypesSchema = z3.enum([
|
|
734
837
|
"BOOLEAN",
|
|
735
838
|
// 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
|
|
736
839
|
"TINYINT",
|
|
@@ -750,7 +853,7 @@ var mariaDbColumnNumericTypesSchema = z2.enum([
|
|
|
750
853
|
"DOUBLE"
|
|
751
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.
|
|
752
855
|
]);
|
|
753
|
-
var mariaDbColumnStringTypesSchema =
|
|
856
|
+
var mariaDbColumnStringTypesSchema = z3.enum([
|
|
754
857
|
"CHAR",
|
|
755
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.
|
|
756
859
|
"VARCHAR",
|
|
@@ -776,7 +879,7 @@ var mariaDbColumnStringTypesSchema = z2.enum([
|
|
|
776
879
|
"ENUM"
|
|
777
880
|
// Enum type
|
|
778
881
|
]);
|
|
779
|
-
var mariaDbColumnDateTypesSchema =
|
|
882
|
+
var mariaDbColumnDateTypesSchema = z3.enum([
|
|
780
883
|
"DATE",
|
|
781
884
|
// 4-bytes Date has year, month, and day.
|
|
782
885
|
"DATETIME",
|
|
@@ -786,9 +889,9 @@ var mariaDbColumnDateTypesSchema = z2.enum([
|
|
|
786
889
|
"TIMESTAMP"
|
|
787
890
|
// 4-bytes Values are stored as the number of seconds since 1970-01-01 00:00:00 UTC, and optionally microseconds.
|
|
788
891
|
]);
|
|
789
|
-
var columnDataSchema =
|
|
790
|
-
name:
|
|
791
|
-
type:
|
|
892
|
+
var columnDataSchema = z3.object({
|
|
893
|
+
name: z3.string(),
|
|
894
|
+
type: z3.union([
|
|
792
895
|
postgresColumnNumericTypesSchema,
|
|
793
896
|
postgresColumnStringTypesSchema,
|
|
794
897
|
postgresColumnDateTypesSchema,
|
|
@@ -796,24 +899,24 @@ var columnDataSchema = z2.object({
|
|
|
796
899
|
mariaDbColumnStringTypesSchema,
|
|
797
900
|
mariaDbColumnDateTypesSchema
|
|
798
901
|
]),
|
|
799
|
-
isNullable:
|
|
800
|
-
roles:
|
|
801
|
-
comment:
|
|
802
|
-
default:
|
|
803
|
-
value:
|
|
804
|
-
isPrimary:
|
|
805
|
-
isUnique:
|
|
806
|
-
hasAutoIncrement:
|
|
807
|
-
length:
|
|
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()
|
|
808
911
|
}).strict();
|
|
809
|
-
var indexDataSchema =
|
|
810
|
-
name:
|
|
811
|
-
columns:
|
|
812
|
-
isUnique:
|
|
813
|
-
isPrimaryKey:
|
|
814
|
-
order:
|
|
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"])
|
|
815
918
|
}).strict();
|
|
816
|
-
var foreignKeyActionsSchema =
|
|
919
|
+
var foreignKeyActionsSchema = z3.enum([
|
|
817
920
|
"CASCADE",
|
|
818
921
|
// CASCADE action for foreign keys
|
|
819
922
|
"SET NULL",
|
|
@@ -825,38 +928,38 @@ var foreignKeyActionsSchema = z2.enum([
|
|
|
825
928
|
"SET DEFAULT"
|
|
826
929
|
// SET DEFAULT action for foreign keys
|
|
827
930
|
]);
|
|
828
|
-
var foreignKeyDataSchema =
|
|
829
|
-
name:
|
|
830
|
-
column:
|
|
831
|
-
refTable:
|
|
832
|
-
refColumn:
|
|
931
|
+
var foreignKeyDataSchema = z3.object({
|
|
932
|
+
name: z3.string(),
|
|
933
|
+
column: z3.string(),
|
|
934
|
+
refTable: z3.string(),
|
|
935
|
+
refColumn: z3.string(),
|
|
833
936
|
onDelete: foreignKeyActionsSchema,
|
|
834
937
|
onUpdate: foreignKeyActionsSchema
|
|
835
938
|
}).strict();
|
|
836
|
-
var checkConstraintDataSchema =
|
|
837
|
-
name:
|
|
838
|
-
check:
|
|
939
|
+
var checkConstraintDataSchema = z3.object({
|
|
940
|
+
name: z3.string(),
|
|
941
|
+
check: z3.string()
|
|
839
942
|
}).strict();
|
|
840
|
-
var tableDataSchema =
|
|
841
|
-
name:
|
|
842
|
-
columns:
|
|
843
|
-
indexes:
|
|
844
|
-
foreignKeys:
|
|
845
|
-
checkConstraints:
|
|
846
|
-
roles:
|
|
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())
|
|
847
950
|
}).strict();
|
|
848
|
-
var endpointDataSchema =
|
|
849
|
-
name:
|
|
850
|
-
description:
|
|
851
|
-
baseUrl:
|
|
852
|
-
routes:
|
|
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]))
|
|
853
956
|
}).strict();
|
|
854
|
-
var resturaZodSchema =
|
|
855
|
-
database:
|
|
856
|
-
endpoints:
|
|
857
|
-
globalParams:
|
|
858
|
-
roles:
|
|
859
|
-
customTypes:
|
|
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()
|
|
860
963
|
}).strict();
|
|
861
964
|
async function isSchemaValid(schemaToCheck) {
|
|
862
965
|
try {
|
|
@@ -868,6 +971,187 @@ async function isSchemaValid(schemaToCheck) {
|
|
|
868
971
|
}
|
|
869
972
|
}
|
|
870
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) {
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
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
|
+
}
|
|
1154
|
+
|
|
871
1155
|
// src/restura/middleware/schemaValidation.ts
|
|
872
1156
|
async function schemaValidation(req, res, next) {
|
|
873
1157
|
req.data = getRequestData(req);
|
|
@@ -915,23 +1199,566 @@ function convertTable(table) {
|
|
|
915
1199
|
return modelString;
|
|
916
1200
|
}
|
|
917
1201
|
|
|
918
|
-
// src/restura/
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1202
|
+
// src/restura/sql/PsqlEngine.ts
|
|
1203
|
+
import { ObjectUtils as ObjectUtils4 } from "@redskytech/core-utils";
|
|
1204
|
+
|
|
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];
|
|
1246
|
+
});
|
|
1247
|
+
return query;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/restura/sql/SqlEngine.ts
|
|
1251
|
+
import { ObjectUtils as ObjectUtils3 } from "@redskytech/core-utils";
|
|
1252
|
+
var SqlEngine = class {
|
|
1253
|
+
async runQueryForRoute(req, routeData, schema) {
|
|
1254
|
+
if (!this.doesRoleHavePermissionToTable(req.requesterDetails.role, schema, routeData.table))
|
|
1255
|
+
throw new RsError("UNAUTHORIZED", "You do not have permission to access this table");
|
|
1256
|
+
switch (routeData.method) {
|
|
1257
|
+
case "POST":
|
|
1258
|
+
return this.executeCreateRequest(req, routeData, schema);
|
|
1259
|
+
case "GET":
|
|
1260
|
+
return this.executeGetRequest(req, routeData, schema);
|
|
1261
|
+
case "PUT":
|
|
1262
|
+
case "PATCH":
|
|
1263
|
+
return this.executeUpdateRequest(req, routeData, schema);
|
|
1264
|
+
case "DELETE":
|
|
1265
|
+
return this.executeDeleteRequest(req, routeData, schema);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
getTableSchema(schema, tableName) {
|
|
1269
|
+
const tableSchema = schema.database.find((item) => item.name === tableName);
|
|
1270
|
+
if (!tableSchema) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1271
|
+
return tableSchema;
|
|
1272
|
+
}
|
|
1273
|
+
doesRoleHavePermissionToColumn(role, schema, item, joins) {
|
|
1274
|
+
if (item.selector) {
|
|
1275
|
+
let tableName = item.selector.split(".")[0];
|
|
1276
|
+
const columnName = item.selector.split(".")[1];
|
|
1277
|
+
let tableSchema = schema.database.find((item2) => item2.name === tableName);
|
|
1278
|
+
if (!tableSchema) {
|
|
1279
|
+
const join = joins.find((join2) => join2.alias === tableName);
|
|
1280
|
+
if (!join) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1281
|
+
tableName = join.table;
|
|
1282
|
+
tableSchema = schema.database.find((item2) => item2.name === tableName);
|
|
1283
|
+
}
|
|
1284
|
+
if (!tableSchema) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1285
|
+
const columnSchema = tableSchema.columns.find((item2) => item2.name === columnName);
|
|
1286
|
+
if (!columnSchema)
|
|
1287
|
+
throw new RsError("SCHEMA_ERROR", `Column ${columnName} not found in table ${tableName}`);
|
|
1288
|
+
const doesColumnHaveRoles = ObjectUtils3.isArrayWithData(columnSchema.roles);
|
|
1289
|
+
if (!doesColumnHaveRoles) return true;
|
|
1290
|
+
if (!role) return false;
|
|
1291
|
+
return columnSchema.roles.includes(role);
|
|
1292
|
+
}
|
|
1293
|
+
if (item.subquery) {
|
|
1294
|
+
return ObjectUtils3.isArrayWithData(
|
|
1295
|
+
item.subquery.properties.filter((nestedItem) => {
|
|
1296
|
+
return this.doesRoleHavePermissionToColumn(role, schema, nestedItem, joins);
|
|
1297
|
+
})
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
doesRoleHavePermissionToTable(userRole, schema, tableName) {
|
|
1303
|
+
const tableSchema = this.getTableSchema(schema, tableName);
|
|
1304
|
+
const doesTableHaveRoles = ObjectUtils3.isArrayWithData(tableSchema.roles);
|
|
1305
|
+
if (!doesTableHaveRoles) return true;
|
|
1306
|
+
if (!userRole) return false;
|
|
1307
|
+
return tableSchema.roles.includes(userRole);
|
|
1308
|
+
}
|
|
1309
|
+
replaceParamKeywords(value, routeData, req, sqlParams) {
|
|
1310
|
+
let returnValue = value;
|
|
1311
|
+
returnValue = this.replaceLocalParamKeywords(returnValue, routeData, req, sqlParams);
|
|
1312
|
+
returnValue = this.replaceGlobalParamKeywords(returnValue, routeData, req, sqlParams);
|
|
1313
|
+
return returnValue;
|
|
1314
|
+
}
|
|
1315
|
+
replaceLocalParamKeywords(value, routeData, req, sqlParams) {
|
|
1316
|
+
var _a;
|
|
1317
|
+
if (!routeData.request) return value;
|
|
1318
|
+
const data = req.data;
|
|
1319
|
+
if (typeof value === "string") {
|
|
1320
|
+
(_a = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a.forEach((param) => {
|
|
1321
|
+
const requestParam = routeData.request.find((item) => {
|
|
1322
|
+
return item.name === param.replace("$", "");
|
|
1323
|
+
});
|
|
1324
|
+
if (!requestParam)
|
|
1325
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1326
|
+
sqlParams.push(data[requestParam.name]);
|
|
1327
|
+
});
|
|
1328
|
+
return value.replace(new RegExp(/\$[a-zA-Z][a-zA-Z0-9_]+/g), "?");
|
|
1329
|
+
}
|
|
1330
|
+
return value;
|
|
1331
|
+
}
|
|
1332
|
+
replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
|
|
1333
|
+
var _a;
|
|
1334
|
+
if (typeof value === "string") {
|
|
1335
|
+
(_a = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a.forEach((param) => {
|
|
1336
|
+
param = param.replace("#", "");
|
|
1337
|
+
const globalParamValue = req.requesterDetails[param];
|
|
1338
|
+
if (!globalParamValue)
|
|
1339
|
+
throw new RsError(
|
|
1340
|
+
"SCHEMA_ERROR",
|
|
1341
|
+
`Invalid global keyword clause in route (${routeData.path}) when looking for (#${param})`
|
|
1342
|
+
);
|
|
1343
|
+
sqlParams.push(globalParamValue);
|
|
1344
|
+
});
|
|
1345
|
+
return value.replace(new RegExp(/#[a-zA-Z][a-zA-Z0-9_]+/g), "?");
|
|
1346
|
+
}
|
|
1347
|
+
return value;
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
// src/restura/sql/filterPsqlParser.ts
|
|
1352
|
+
import peg from "pegjs";
|
|
1353
|
+
var filterSqlGrammar = `
|
|
1354
|
+
start = expressionList
|
|
1355
|
+
|
|
1356
|
+
expressionList =
|
|
1357
|
+
leftExpression:expression operator:operator rightExpression:expressionList
|
|
1358
|
+
{ return \`\${leftExpression} \${operator} \${rightExpression}\`;}
|
|
1359
|
+
/ expression
|
|
1360
|
+
|
|
1361
|
+
expression =
|
|
1362
|
+
negate:negate?"(" "column:" column:column ","? value:value? ","? type:type? ")"
|
|
1363
|
+
{return \`\${negate? "!" : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
|
|
1364
|
+
/
|
|
1365
|
+
negate:negate?"("expression:expressionList")" { return \`\${negate? "!" : ""}(\${expression})\`; }
|
|
1366
|
+
|
|
1367
|
+
negate = "!"
|
|
1368
|
+
|
|
1369
|
+
operator = "and"i / "or"i
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
column = left:text "." right:text { return \`\${format.ident(left)}.\${format.ident(right)}\`; }
|
|
1373
|
+
/
|
|
1374
|
+
text:text { return format.ident(text); }
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
text = text:[a-z0-9-_:@]i+ { return text.join("");}
|
|
1378
|
+
|
|
1379
|
+
type = "type:" type:typeString { return type; }
|
|
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)}'\`; } } /
|
|
1388
|
+
text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
|
|
1389
|
+
|
|
1390
|
+
value = "value:" value:text { return value; }
|
|
1391
|
+
|
|
1392
|
+
`;
|
|
1393
|
+
var filterPsqlParser = peg.generate(filterSqlGrammar, {
|
|
1394
|
+
format: "commonjs",
|
|
1395
|
+
dependencies: { format: "pg-format" }
|
|
1396
|
+
});
|
|
1397
|
+
var filterPsqlParser_default = filterPsqlParser;
|
|
1398
|
+
|
|
1399
|
+
// src/restura/sql/PsqlEngine.ts
|
|
1400
|
+
var PsqlEngine = class extends SqlEngine {
|
|
1401
|
+
constructor(psqlConnectionPool) {
|
|
1402
|
+
super();
|
|
1403
|
+
this.psqlConnectionPool = psqlConnectionPool;
|
|
1404
|
+
}
|
|
1405
|
+
async diffDatabaseToSchema(schema) {
|
|
1406
|
+
console.log(schema);
|
|
1407
|
+
return Promise.resolve("");
|
|
1408
|
+
}
|
|
1409
|
+
generateDatabaseSchemaFromSchema(schema) {
|
|
1410
|
+
console.log(schema);
|
|
1411
|
+
return "";
|
|
1412
|
+
}
|
|
1413
|
+
createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
|
|
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
|
+
), '[]')`;
|
|
1453
|
+
}
|
|
1454
|
+
async executeCreateRequest(req, routeData, schema) {
|
|
1455
|
+
const sqlParams = [];
|
|
1456
|
+
const parameterObj = {};
|
|
1457
|
+
(routeData.assignments || []).forEach((assignment) => {
|
|
1458
|
+
parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
|
|
1459
|
+
});
|
|
1460
|
+
const query = insertObjectQuery(routeData.table, __spreadValues(__spreadValues({}, req.data), parameterObj));
|
|
1461
|
+
const createdItem = await this.psqlConnectionPool.queryOne(query, sqlParams, req.requesterDetails);
|
|
1462
|
+
const insertId = createdItem == null ? void 0 : createdItem.id;
|
|
1463
|
+
const whereData = [
|
|
1464
|
+
{
|
|
1465
|
+
tableName: routeData.table,
|
|
1466
|
+
value: insertId,
|
|
1467
|
+
columnName: "id",
|
|
1468
|
+
operator: "="
|
|
1469
|
+
}
|
|
1470
|
+
];
|
|
1471
|
+
req.data = { id: insertId };
|
|
1472
|
+
return this.executeGetRequest(req, __spreadProps(__spreadValues({}, routeData), { where: whereData }), schema);
|
|
1473
|
+
}
|
|
1474
|
+
async executeGetRequest(req, routeData, schema) {
|
|
1475
|
+
const DEFAULT_PAGED_PAGE_NUMBER = 0;
|
|
1476
|
+
const DEFAULT_PAGED_PER_PAGE_NUMBER = 25;
|
|
1477
|
+
const sqlParams = [];
|
|
1478
|
+
const userRole = req.requesterDetails.role;
|
|
1479
|
+
let sqlStatement = "";
|
|
1480
|
+
const selectColumns = [];
|
|
1481
|
+
routeData.response.forEach((item) => {
|
|
1482
|
+
if (item.subquery || this.doesRoleHavePermissionToColumn(userRole, schema, item, routeData.joins))
|
|
1483
|
+
selectColumns.push(item);
|
|
1484
|
+
});
|
|
1485
|
+
if (!selectColumns.length) throw new RsError("UNAUTHORIZED", `You do not have permission to access this data.`);
|
|
1486
|
+
let selectStatement = "SELECT \n";
|
|
1487
|
+
selectStatement += ` ${selectColumns.map((item) => {
|
|
1488
|
+
if (item.subquery) {
|
|
1489
|
+
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${item.name}`;
|
|
1490
|
+
}
|
|
1491
|
+
return `${escapeColumnName(item.selector)} AS ${escapeColumnName(item.name)}`;
|
|
1492
|
+
}).join(",\n ")}
|
|
1493
|
+
`;
|
|
1494
|
+
sqlStatement += `FROM "${routeData.table}"
|
|
1495
|
+
`;
|
|
1496
|
+
sqlStatement += this.generateJoinStatements(
|
|
922
1497
|
req,
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1498
|
+
routeData.joins,
|
|
1499
|
+
routeData.table,
|
|
1500
|
+
routeData,
|
|
1501
|
+
schema,
|
|
1502
|
+
userRole,
|
|
1503
|
+
sqlParams
|
|
1504
|
+
);
|
|
1505
|
+
sqlStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1506
|
+
let groupByOrderByStatement = this.generateGroupBy(routeData);
|
|
1507
|
+
groupByOrderByStatement += this.generateOrderBy(req, routeData);
|
|
1508
|
+
if (routeData.type === "ONE") {
|
|
1509
|
+
return this.psqlConnectionPool.queryOne(
|
|
1510
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
|
|
1511
|
+
sqlParams,
|
|
1512
|
+
req.requesterDetails
|
|
1513
|
+
);
|
|
1514
|
+
} else if (routeData.type === "ARRAY") {
|
|
1515
|
+
return this.psqlConnectionPool.runQuery(
|
|
1516
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
|
|
1517
|
+
sqlParams,
|
|
1518
|
+
req.requesterDetails
|
|
1519
|
+
);
|
|
1520
|
+
} else if (routeData.type === "PAGED") {
|
|
1521
|
+
const data = req.data;
|
|
1522
|
+
const pageResults = await this.psqlConnectionPool.runQuery(
|
|
1523
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement} LIMIT ? OFFSET ?;SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
|
|
1524
|
+
${sqlStatement};`,
|
|
1525
|
+
[
|
|
1526
|
+
...sqlParams,
|
|
1527
|
+
data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER,
|
|
1528
|
+
(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER,
|
|
1529
|
+
...sqlParams
|
|
1530
|
+
],
|
|
1531
|
+
req.requesterDetails
|
|
1532
|
+
);
|
|
1533
|
+
let total = 0;
|
|
1534
|
+
if (ObjectUtils4.isArrayWithData(pageResults)) {
|
|
1535
|
+
total = pageResults[1][0].total;
|
|
929
1536
|
}
|
|
1537
|
+
return { data: pageResults[0], total };
|
|
1538
|
+
} else {
|
|
1539
|
+
throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
async executeUpdateRequest(req, routeData, schema) {
|
|
1543
|
+
const sqlParams = [];
|
|
1544
|
+
const _a = req.body, { id } = _a, bodyNoId = __objRest(_a, ["id"]);
|
|
1545
|
+
const table = schema.database.find((item) => {
|
|
1546
|
+
return item.name === routeData.table;
|
|
1547
|
+
});
|
|
1548
|
+
if (!table) throw new RsError("UNKNOWN_ERROR", "Unknown table.");
|
|
1549
|
+
if (table.columns.find((column) => column.name === "modifiedOn")) {
|
|
1550
|
+
bodyNoId.modifiedOn = (/* @__PURE__ */ new Date()).toISOString();
|
|
1551
|
+
}
|
|
1552
|
+
for (const assignment of routeData.assignments) {
|
|
1553
|
+
const column = table.columns.find((column2) => column2.name === assignment.name);
|
|
1554
|
+
if (!column) continue;
|
|
1555
|
+
const assignmentWithPrefix = escapeColumnName(`${routeData.table}.${assignment.name}`);
|
|
1556
|
+
if (SqlUtils.convertDatabaseTypeToTypescript(column.type) === "number")
|
|
1557
|
+
bodyNoId[assignmentWithPrefix] = Number(assignment.value);
|
|
1558
|
+
else bodyNoId[assignmentWithPrefix] = assignment.value;
|
|
1559
|
+
}
|
|
1560
|
+
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1561
|
+
const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
|
|
1562
|
+
await this.psqlConnectionPool.queryOne(query, [...sqlParams], req.requesterDetails);
|
|
1563
|
+
return this.executeGetRequest(req, routeData, schema);
|
|
1564
|
+
}
|
|
1565
|
+
async executeDeleteRequest(req, routeData, schema) {
|
|
1566
|
+
const sqlParams = [];
|
|
1567
|
+
const joinStatement = this.generateJoinStatements(
|
|
1568
|
+
req,
|
|
1569
|
+
routeData.joins,
|
|
1570
|
+
routeData.table,
|
|
1571
|
+
routeData,
|
|
1572
|
+
schema,
|
|
1573
|
+
req.requesterDetails.role,
|
|
1574
|
+
sqlParams
|
|
930
1575
|
);
|
|
931
|
-
|
|
932
|
-
}
|
|
1576
|
+
let deleteStatement = `DELETE
|
|
1577
|
+
FROM "${routeData.table}" ${joinStatement}`;
|
|
1578
|
+
deleteStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1579
|
+
deleteStatement += ";";
|
|
1580
|
+
await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
|
|
1581
|
+
return true;
|
|
1582
|
+
}
|
|
1583
|
+
generateJoinStatements(req, joins, baseTable, routeData, schema, userRole, sqlParams) {
|
|
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;
|
|
1600
|
+
}
|
|
1601
|
+
generateGroupBy(routeData) {
|
|
1602
|
+
let groupBy = "";
|
|
1603
|
+
if (routeData.groupBy) {
|
|
1604
|
+
groupBy = `GROUP BY ${escapeColumnName(routeData.groupBy.tableName)}.${escapeColumnName(routeData.groupBy.columnName)}
|
|
1605
|
+
`;
|
|
1606
|
+
}
|
|
1607
|
+
return groupBy;
|
|
1608
|
+
}
|
|
1609
|
+
generateOrderBy(req, routeData) {
|
|
1610
|
+
let orderBy = "";
|
|
1611
|
+
const orderOptions = {
|
|
1612
|
+
ASC: "ASC",
|
|
1613
|
+
DESC: "DESC"
|
|
1614
|
+
};
|
|
1615
|
+
const data = req.data;
|
|
1616
|
+
if (routeData.type === "PAGED" && "sortBy" in data) {
|
|
1617
|
+
const sortOrder = orderOptions[data.sortOrder] || "ASC";
|
|
1618
|
+
orderBy = `ORDER BY ${escapeColumnName(data.sortBy)} ${sortOrder}
|
|
1619
|
+
`;
|
|
1620
|
+
} else if (routeData.orderBy) {
|
|
1621
|
+
const sortOrder = orderOptions[routeData.orderBy.order] || "ASC";
|
|
1622
|
+
orderBy = `ORDER BY ${escapeColumnName(routeData.orderBy.tableName)}.${escapeColumnName(routeData.orderBy.columnName)} ${sortOrder}
|
|
1623
|
+
`;
|
|
1624
|
+
}
|
|
1625
|
+
return orderBy;
|
|
1626
|
+
}
|
|
1627
|
+
generateWhereClause(req, where, routeData, sqlParams) {
|
|
1628
|
+
let whereClause = "";
|
|
1629
|
+
where.forEach((item, index) => {
|
|
1630
|
+
if (index === 0) whereClause = "WHERE ";
|
|
1631
|
+
if (item.custom) {
|
|
1632
|
+
whereClause += this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
if (item.operator === void 0 || item.value === void 0 || item.columnName === void 0 || item.tableName === void 0)
|
|
1636
|
+
throw new RsError(
|
|
1637
|
+
"SCHEMA_ERROR",
|
|
1638
|
+
`Invalid where clause in route ${routeData.name}, missing required fields if not custom`
|
|
1639
|
+
);
|
|
1640
|
+
let operator = item.operator;
|
|
1641
|
+
if (operator === "LIKE") {
|
|
1642
|
+
sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}%`;
|
|
1643
|
+
} else if (operator === "STARTS WITH") {
|
|
1644
|
+
operator = "LIKE";
|
|
1645
|
+
sqlParams[sqlParams.length - 1] = `${sqlParams[sqlParams.length - 1]}%`;
|
|
1646
|
+
} else if (operator === "ENDS WITH") {
|
|
1647
|
+
operator = "LIKE";
|
|
1648
|
+
sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}`;
|
|
1649
|
+
}
|
|
1650
|
+
const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
|
|
1651
|
+
const escapedValue = SQL`${replacedValue}`;
|
|
1652
|
+
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator} ${["IN", "NOT IN"].includes(operator) ? `(${escapedValue})` : escapedValue}
|
|
1653
|
+
`;
|
|
1654
|
+
});
|
|
1655
|
+
const data = req.data;
|
|
1656
|
+
if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
|
|
1657
|
+
let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1658
|
+
var _a;
|
|
1659
|
+
const requestParam = routeData.request.find((item) => {
|
|
1660
|
+
return item.name === value.replace("$", "");
|
|
1661
|
+
});
|
|
1662
|
+
if (!requestParam)
|
|
1663
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1664
|
+
return ((_a = data[requestParam.name]) == null ? void 0 : _a.toString()) || "";
|
|
1665
|
+
});
|
|
1666
|
+
statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1667
|
+
var _a;
|
|
1668
|
+
const requestParam = routeData.request.find((item) => {
|
|
1669
|
+
return item.name === value.replace("#", "");
|
|
1670
|
+
});
|
|
1671
|
+
if (!requestParam)
|
|
1672
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1673
|
+
return ((_a = data[requestParam.name]) == null ? void 0 : _a.toString()) || "";
|
|
1674
|
+
});
|
|
1675
|
+
statement = filterPsqlParser_default.parse(statement);
|
|
1676
|
+
if (whereClause.startsWith("WHERE")) {
|
|
1677
|
+
whereClause += ` AND (${statement})
|
|
1678
|
+
`;
|
|
1679
|
+
} else {
|
|
1680
|
+
whereClause += `WHERE ${statement}
|
|
1681
|
+
`;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
return whereClause;
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
// src/restura/sql/PsqlPool.ts
|
|
1689
|
+
import pg from "pg";
|
|
1690
|
+
import format3 from "pg-format";
|
|
1691
|
+
var { Pool } = pg;
|
|
1692
|
+
var PsqlPool = class {
|
|
1693
|
+
constructor(poolConfig) {
|
|
1694
|
+
this.poolConfig = poolConfig;
|
|
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
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1704
|
+
async queryOne(query, options, requesterDetails) {
|
|
1705
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1706
|
+
this.logSqlStatement(formattedQuery, options, requesterDetails);
|
|
1707
|
+
try {
|
|
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");
|
|
1711
|
+
return response.rows[0];
|
|
1712
|
+
} catch (error) {
|
|
1713
|
+
console.error(error, query, options);
|
|
1714
|
+
if (RsError.isRsError(error)) throw error;
|
|
1715
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1716
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1717
|
+
}
|
|
1718
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1722
|
+
async runQuery(query, options, requesterDetails) {
|
|
1723
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1724
|
+
this.logSqlStatement(formattedQuery, options, requesterDetails);
|
|
1725
|
+
const queryUpdated = query.replace(/[\t\n]/g, " ");
|
|
1726
|
+
console.log(queryUpdated, options);
|
|
1727
|
+
try {
|
|
1728
|
+
const response = await this.pool.query(formattedQuery, options);
|
|
1729
|
+
return response.rows;
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
console.error(error, query, options);
|
|
1732
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1733
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1734
|
+
}
|
|
1735
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1736
|
+
}
|
|
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
|
+
}
|
|
1758
|
+
};
|
|
933
1759
|
|
|
934
1760
|
// src/restura/restura.ts
|
|
1761
|
+
var { types } = pg2;
|
|
935
1762
|
var ResturaEngine = class {
|
|
936
1763
|
constructor() {
|
|
937
1764
|
this.publicEndpoints = {
|
|
@@ -942,15 +1769,18 @@ var ResturaEngine = class {
|
|
|
942
1769
|
DELETE: []
|
|
943
1770
|
};
|
|
944
1771
|
}
|
|
945
|
-
// private customTypeValidation!: ValidationDictionary;
|
|
946
1772
|
/**
|
|
947
1773
|
* Initializes the Restura engine with the provided Express application.
|
|
948
1774
|
*
|
|
949
1775
|
* @param app - The Express application instance to initialize with Restura.
|
|
950
1776
|
* @returns A promise that resolves when the initialization is complete.
|
|
951
1777
|
*/
|
|
952
|
-
async init(app, authenticationHandler) {
|
|
1778
|
+
async init(app, authenticationHandler, psqlConnectionPool) {
|
|
953
1779
|
this.resturaConfig = config2.validate("restura", resturaConfigSchema);
|
|
1780
|
+
this.psqlConnectionPool = psqlConnectionPool;
|
|
1781
|
+
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool);
|
|
1782
|
+
setupPgReturnTypes();
|
|
1783
|
+
await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
|
|
954
1784
|
this.authenticationHandler = authenticationHandler;
|
|
955
1785
|
app.use(compression());
|
|
956
1786
|
app.use(bodyParser.json({ limit: "32mb" }));
|
|
@@ -958,7 +1788,7 @@ var ResturaEngine = class {
|
|
|
958
1788
|
app.use(cookieParser());
|
|
959
1789
|
app.disable("x-powered-by");
|
|
960
1790
|
app.use("/", addApiResponseFunctions);
|
|
961
|
-
app.use("/api/", authenticateUser);
|
|
1791
|
+
app.use("/api/", authenticateUser(this.authenticationHandler));
|
|
962
1792
|
app.use("/restura", this.resturaAuthentication);
|
|
963
1793
|
app.put(
|
|
964
1794
|
"/restura/v1/schema",
|
|
@@ -974,7 +1804,7 @@ var ResturaEngine = class {
|
|
|
974
1804
|
app.get("/restura/v1/schema/types", this.getSchemaAndTypes);
|
|
975
1805
|
this.expressApp = app;
|
|
976
1806
|
await this.reloadEndpoints();
|
|
977
|
-
this.validateGeneratedTypesFolder();
|
|
1807
|
+
await this.validateGeneratedTypesFolder();
|
|
978
1808
|
logger.info("Restura Engine Initialized");
|
|
979
1809
|
}
|
|
980
1810
|
/**
|
|
@@ -1013,7 +1843,7 @@ var ResturaEngine = class {
|
|
|
1013
1843
|
* @returns A promise that resolves when the API has been successfully generated and written to the output file.
|
|
1014
1844
|
*/
|
|
1015
1845
|
async generateApiFromSchema(outputFile, providedSchema) {
|
|
1016
|
-
|
|
1846
|
+
fs3.writeFileSync(
|
|
1017
1847
|
outputFile,
|
|
1018
1848
|
await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1019
1849
|
);
|
|
@@ -1026,7 +1856,7 @@ var ResturaEngine = class {
|
|
|
1026
1856
|
* @returns A promise that resolves when the model has been successfully written to the output file.
|
|
1027
1857
|
*/
|
|
1028
1858
|
async generateModelFromSchema(outputFile, providedSchema) {
|
|
1029
|
-
|
|
1859
|
+
fs3.writeFileSync(
|
|
1030
1860
|
outputFile,
|
|
1031
1861
|
await modelGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1032
1862
|
);
|
|
@@ -1038,12 +1868,12 @@ var ResturaEngine = class {
|
|
|
1038
1868
|
* @throws {Error} If the schema file is missing or the schema is not valid.
|
|
1039
1869
|
*/
|
|
1040
1870
|
async getLatestFileSystemSchema() {
|
|
1041
|
-
if (!
|
|
1871
|
+
if (!fs3.existsSync(this.resturaConfig.schemaFilePath)) {
|
|
1042
1872
|
logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
|
|
1043
1873
|
throw new Error("Missing restura schema file");
|
|
1044
1874
|
}
|
|
1045
|
-
const schemaFileData =
|
|
1046
|
-
const schema =
|
|
1875
|
+
const schemaFileData = fs3.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
|
|
1876
|
+
const schema = ObjectUtils5.safeParse(schemaFileData);
|
|
1047
1877
|
const isValid = await isSchemaValid(schema);
|
|
1048
1878
|
if (!isValid) {
|
|
1049
1879
|
logger.error("Schema is not valid");
|
|
@@ -1063,9 +1893,9 @@ var ResturaEngine = class {
|
|
|
1063
1893
|
async getHashes(providedSchema) {
|
|
1064
1894
|
var _a, _b, _c, _d;
|
|
1065
1895
|
const schemaHash = await this.generateHashForSchema(providedSchema);
|
|
1066
|
-
const apiFile =
|
|
1896
|
+
const apiFile = fs3.readFileSync(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1067
1897
|
const apiCreatedSchemaHash = (_b = (_a = apiFile.toString().match(/\((.*)\)/)) == null ? void 0 : _a[1]) != null ? _b : "";
|
|
1068
|
-
const modelFile =
|
|
1898
|
+
const modelFile = fs3.readFileSync(path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1069
1899
|
const modelCreatedSchemaHash = (_d = (_c = modelFile.toString().match(/\((.*)\)/)) == null ? void 0 : _c[1]) != null ? _d : "";
|
|
1070
1900
|
return {
|
|
1071
1901
|
schemaHash,
|
|
@@ -1075,6 +1905,7 @@ var ResturaEngine = class {
|
|
|
1075
1905
|
}
|
|
1076
1906
|
async reloadEndpoints() {
|
|
1077
1907
|
this.schema = await this.getLatestFileSystemSchema();
|
|
1908
|
+
this.customTypeValidation = customTypeValidationGenerator(this.schema);
|
|
1078
1909
|
this.resturaRouter = express.Router();
|
|
1079
1910
|
this.resetPublicEndpoints();
|
|
1080
1911
|
let routeCount = 0;
|
|
@@ -1100,27 +1931,27 @@ var ResturaEngine = class {
|
|
|
1100
1931
|
logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
|
|
1101
1932
|
}
|
|
1102
1933
|
async validateGeneratedTypesFolder() {
|
|
1103
|
-
if (!
|
|
1104
|
-
|
|
1934
|
+
if (!fs3.existsSync(this.resturaConfig.generatedTypesPath)) {
|
|
1935
|
+
fs3.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
|
|
1105
1936
|
}
|
|
1106
|
-
const hasApiFile =
|
|
1107
|
-
const hasModelsFile =
|
|
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"));
|
|
1108
1939
|
if (!hasApiFile) {
|
|
1109
|
-
await this.generateApiFromSchema(
|
|
1940
|
+
await this.generateApiFromSchema(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1110
1941
|
}
|
|
1111
1942
|
if (!hasModelsFile) {
|
|
1112
1943
|
await this.generateModelFromSchema(
|
|
1113
|
-
|
|
1944
|
+
path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1114
1945
|
this.schema
|
|
1115
1946
|
);
|
|
1116
1947
|
}
|
|
1117
1948
|
const hashes = await this.getHashes(this.schema);
|
|
1118
1949
|
if (hashes.schemaHash !== hashes.apiCreatedSchemaHash) {
|
|
1119
|
-
await this.generateApiFromSchema(
|
|
1950
|
+
await this.generateApiFromSchema(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1120
1951
|
}
|
|
1121
1952
|
if (hashes.schemaHash !== hashes.modelCreatedSchemaHash) {
|
|
1122
1953
|
await this.generateModelFromSchema(
|
|
1123
|
-
|
|
1954
|
+
path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1124
1955
|
this.schema
|
|
1125
1956
|
);
|
|
1126
1957
|
}
|
|
@@ -1150,9 +1981,9 @@ var ResturaEngine = class {
|
|
|
1150
1981
|
}
|
|
1151
1982
|
}
|
|
1152
1983
|
async updateTypes() {
|
|
1153
|
-
await this.generateApiFromSchema(
|
|
1984
|
+
await this.generateApiFromSchema(path3.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1154
1985
|
await this.generateModelFromSchema(
|
|
1155
|
-
|
|
1986
|
+
path3.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1156
1987
|
this.schema
|
|
1157
1988
|
);
|
|
1158
1989
|
}
|
|
@@ -1174,6 +2005,19 @@ var ResturaEngine = class {
|
|
|
1174
2005
|
try {
|
|
1175
2006
|
const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
|
|
1176
2007
|
this.validateAuthorization(req, routeData);
|
|
2008
|
+
validateRequestParams(req, routeData, this.customTypeValidation);
|
|
2009
|
+
if (this.isCustomRoute(routeData)) {
|
|
2010
|
+
await this.runCustomRouteLogic(req, res, routeData);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
const data = await this.psqlEngine.runQueryForRoute(
|
|
2014
|
+
req,
|
|
2015
|
+
routeData,
|
|
2016
|
+
this.schema
|
|
2017
|
+
);
|
|
2018
|
+
this.responseValidator.validateResponseParams(data, req.baseUrl, routeData);
|
|
2019
|
+
if (routeData.type === "PAGED") res.sendNoWrap(data);
|
|
2020
|
+
else res.sendData(data);
|
|
1177
2021
|
} catch (e) {
|
|
1178
2022
|
next(e);
|
|
1179
2023
|
}
|
|
@@ -1181,34 +2025,25 @@ var ResturaEngine = class {
|
|
|
1181
2025
|
isCustomRoute(route) {
|
|
1182
2026
|
return route.type === "CUSTOM_ONE" || route.type === "CUSTOM_ARRAY" || route.type === "CUSTOM_PAGED";
|
|
1183
2027
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
// // @ts-expect-error - Here we are dynamically calling the function from a custom class, not sure how to typescript this
|
|
1204
|
-
// const customFunction = customApi[functionName] as (
|
|
1205
|
-
// req: RsRequest<T>,
|
|
1206
|
-
// res: RsResponse<T>,
|
|
1207
|
-
// routeData: RouteData
|
|
1208
|
-
// ) => Promise<void>;
|
|
1209
|
-
// if (!customFunction) throw new RsError('NOT_FOUND', `API path ${routeData.path} not implemented`);
|
|
1210
|
-
// await customFunction(req, res, routeData);
|
|
1211
|
-
// }
|
|
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
|
+
}
|
|
1212
2047
|
async generateHashForSchema(providedSchema) {
|
|
1213
2048
|
const schemaPrettyStr = await prettier3.format(JSON.stringify(providedSchema), __spreadValues({
|
|
1214
2049
|
parser: "json"
|
|
@@ -1233,7 +2068,7 @@ var ResturaEngine = class {
|
|
|
1233
2068
|
printWidth: 120,
|
|
1234
2069
|
singleQuote: true
|
|
1235
2070
|
}));
|
|
1236
|
-
|
|
2071
|
+
fs3.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
|
|
1237
2072
|
}
|
|
1238
2073
|
resetPublicEndpoints() {
|
|
1239
2074
|
this.publicEndpoints = {
|
|
@@ -1250,13 +2085,13 @@ var ResturaEngine = class {
|
|
|
1250
2085
|
if (!routeData.roles.includes(role))
|
|
1251
2086
|
throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
|
|
1252
2087
|
}
|
|
1253
|
-
getRouteData(method, baseUrl,
|
|
2088
|
+
getRouteData(method, baseUrl, path4) {
|
|
1254
2089
|
const endpoint = this.schema.endpoints.find((item) => {
|
|
1255
2090
|
return item.baseUrl === baseUrl;
|
|
1256
2091
|
});
|
|
1257
2092
|
if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
|
|
1258
2093
|
const route = endpoint.routes.find((item) => {
|
|
1259
|
-
return item.method === method && item.path ===
|
|
2094
|
+
return item.method === method && item.path === path4;
|
|
1260
2095
|
});
|
|
1261
2096
|
if (!route) throw new RsError("NOT_FOUND", "Route not found");
|
|
1262
2097
|
return route;
|
|
@@ -1283,9 +2118,32 @@ __decorateClass([
|
|
|
1283
2118
|
__decorateClass([
|
|
1284
2119
|
boundMethod
|
|
1285
2120
|
], ResturaEngine.prototype, "isCustomRoute", 1);
|
|
2121
|
+
__decorateClass([
|
|
2122
|
+
boundMethod
|
|
2123
|
+
], ResturaEngine.prototype, "runCustomRouteLogic", 1);
|
|
2124
|
+
var setupPgReturnTypes = () => {
|
|
2125
|
+
const TIMESTAMPTZ_OID = 1184;
|
|
2126
|
+
types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
|
|
2127
|
+
return val === null ? null : new Date(val).toISOString();
|
|
2128
|
+
});
|
|
2129
|
+
const BIGINT_OID = 20;
|
|
2130
|
+
types.setTypeParser(BIGINT_OID, (val) => {
|
|
2131
|
+
return val === null ? null : Number(val);
|
|
2132
|
+
});
|
|
2133
|
+
};
|
|
2134
|
+
setupPgReturnTypes();
|
|
1286
2135
|
var restura = new ResturaEngine();
|
|
1287
2136
|
export {
|
|
2137
|
+
HtmlStatusCodes,
|
|
2138
|
+
PsqlPool,
|
|
2139
|
+
RsError,
|
|
2140
|
+
SQL,
|
|
2141
|
+
escapeColumnName,
|
|
2142
|
+
insertObjectQuery,
|
|
2143
|
+
isValueNumber2 as isValueNumber,
|
|
1288
2144
|
logger,
|
|
1289
|
-
|
|
2145
|
+
questionMarksToOrderedParams,
|
|
2146
|
+
restura,
|
|
2147
|
+
updateObjectQuery
|
|
1290
2148
|
};
|
|
1291
2149
|
//# sourceMappingURL=index.mjs.map
|