@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.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
8
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
7
9
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -19,6 +21,19 @@ var __spreadValues = (a, b) => {
|
|
|
19
21
|
}
|
|
20
22
|
return a;
|
|
21
23
|
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __objRest = (source, exclude) => {
|
|
26
|
+
var target = {};
|
|
27
|
+
for (var prop in source)
|
|
28
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
if (source != null && __getOwnPropSymbols)
|
|
31
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
32
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
33
|
+
target[prop] = source[prop];
|
|
34
|
+
}
|
|
35
|
+
return target;
|
|
36
|
+
};
|
|
22
37
|
var __export = (target, all) => {
|
|
23
38
|
for (var name in all)
|
|
24
39
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -52,8 +67,17 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
52
67
|
// src/index.ts
|
|
53
68
|
var src_exports = {};
|
|
54
69
|
__export(src_exports, {
|
|
70
|
+
HtmlStatusCodes: () => HtmlStatusCodes,
|
|
71
|
+
PsqlPool: () => PsqlPool,
|
|
72
|
+
RsError: () => RsError,
|
|
73
|
+
SQL: () => SQL,
|
|
74
|
+
escapeColumnName: () => escapeColumnName,
|
|
75
|
+
insertObjectQuery: () => insertObjectQuery,
|
|
76
|
+
isValueNumber: () => isValueNumber2,
|
|
55
77
|
logger: () => logger,
|
|
56
|
-
|
|
78
|
+
questionMarksToOrderedParams: () => questionMarksToOrderedParams,
|
|
79
|
+
restura: () => restura,
|
|
80
|
+
updateObjectQuery: () => updateObjectQuery
|
|
57
81
|
});
|
|
58
82
|
module.exports = __toCommonJS(src_exports);
|
|
59
83
|
|
|
@@ -65,12 +89,13 @@ var import_logform = require("logform");
|
|
|
65
89
|
// src/config.schema.ts
|
|
66
90
|
var import_zod = require("zod");
|
|
67
91
|
var loggerConfigSchema = import_zod.z.object({
|
|
68
|
-
level: import_zod.z.enum(["info", "warn", "error", "debug"]).default("info")
|
|
92
|
+
level: import_zod.z.enum(["info", "warn", "error", "debug", "silly"]).default("info")
|
|
69
93
|
});
|
|
70
94
|
var resturaConfigSchema = import_zod.z.object({
|
|
71
95
|
authToken: import_zod.z.string().min(1, "Missing Restura Auth Token"),
|
|
72
96
|
sendErrorStackTrace: import_zod.z.boolean().default(false),
|
|
73
97
|
schemaFilePath: import_zod.z.string().default(process.cwd() + "/restura.schema.json"),
|
|
98
|
+
customApiFolderPath: import_zod.z.string().default(process.cwd() + "/dist/api"),
|
|
74
99
|
generatedTypesPath: import_zod.z.string().default(process.cwd() + "/src/@types")
|
|
75
100
|
});
|
|
76
101
|
|
|
@@ -109,8 +134,74 @@ var logger = import_winston.default.createLogger({
|
|
|
109
134
|
]
|
|
110
135
|
});
|
|
111
136
|
|
|
137
|
+
// src/restura/errors.ts
|
|
138
|
+
var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
|
|
139
|
+
HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
|
140
|
+
HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
|
141
|
+
HtmlStatusCodes2[HtmlStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
|
|
142
|
+
HtmlStatusCodes2[HtmlStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
|
|
143
|
+
HtmlStatusCodes2[HtmlStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
|
|
144
|
+
HtmlStatusCodes2[HtmlStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
|
|
145
|
+
HtmlStatusCodes2[HtmlStatusCodes2["VERSION_OUT_OF_DATE"] = 418] = "VERSION_OUT_OF_DATE";
|
|
146
|
+
HtmlStatusCodes2[HtmlStatusCodes2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
|
|
147
|
+
HtmlStatusCodes2[HtmlStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
|
|
148
|
+
HtmlStatusCodes2[HtmlStatusCodes2["NETWORK_CONNECT_TIMEOUT"] = 599] = "NETWORK_CONNECT_TIMEOUT";
|
|
149
|
+
return HtmlStatusCodes2;
|
|
150
|
+
})(HtmlStatusCodes || {});
|
|
151
|
+
var RsError = class _RsError {
|
|
152
|
+
constructor(errCode, message) {
|
|
153
|
+
this.err = errCode;
|
|
154
|
+
this.msg = message || "";
|
|
155
|
+
this.status = _RsError.htmlStatus(errCode);
|
|
156
|
+
this.stack = new Error().stack || "";
|
|
157
|
+
}
|
|
158
|
+
static htmlStatus(code) {
|
|
159
|
+
return htmlStatusMap[code];
|
|
160
|
+
}
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
static isRsError(error) {
|
|
163
|
+
return error instanceof _RsError;
|
|
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
|
+
|
|
112
203
|
// src/restura/restura.ts
|
|
113
|
-
var
|
|
204
|
+
var import_core_utils6 = require("@redskytech/core-utils");
|
|
114
205
|
var import_internal2 = require("@restura/internal");
|
|
115
206
|
|
|
116
207
|
// ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
|
|
@@ -165,60 +256,12 @@ var import_compression = __toESM(require("compression"));
|
|
|
165
256
|
var import_cookie_parser = __toESM(require("cookie-parser"));
|
|
166
257
|
var import_crypto = require("crypto");
|
|
167
258
|
var express = __toESM(require("express"));
|
|
168
|
-
var
|
|
169
|
-
var
|
|
259
|
+
var import_fs3 = __toESM(require("fs"));
|
|
260
|
+
var import_path3 = __toESM(require("path"));
|
|
261
|
+
var import_pg2 = __toESM(require("pg"));
|
|
170
262
|
var prettier3 = __toESM(require("prettier"));
|
|
171
263
|
|
|
172
|
-
// src/restura/
|
|
173
|
-
var RsError = class _RsError {
|
|
174
|
-
constructor(errCode, message) {
|
|
175
|
-
this.err = errCode;
|
|
176
|
-
this.msg = message || "";
|
|
177
|
-
this.status = _RsError.htmlStatus(errCode);
|
|
178
|
-
this.stack = new Error().stack || "";
|
|
179
|
-
}
|
|
180
|
-
static htmlStatus(code) {
|
|
181
|
-
return htmlStatusMap[code];
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
var htmlStatusMap = {
|
|
185
|
-
UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
|
|
186
|
-
NOT_FOUND: 404 /* NOT_FOUND */,
|
|
187
|
-
EMAIL_TAKEN: 409 /* CONFLICT */,
|
|
188
|
-
FORBIDDEN: 403 /* FORBIDDEN */,
|
|
189
|
-
CONFLICT: 409 /* CONFLICT */,
|
|
190
|
-
UNAUTHORIZED: 401 /* UNAUTHORIZED */,
|
|
191
|
-
UPDATE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
192
|
-
CREATE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
193
|
-
DELETE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
194
|
-
DELETE_FAILURE: 500 /* SERVER_ERROR */,
|
|
195
|
-
BAD_REQUEST: 400 /* BAD_REQUEST */,
|
|
196
|
-
INVALID_TOKEN: 401 /* UNAUTHORIZED */,
|
|
197
|
-
INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
|
|
198
|
-
DUPLICATE_TOKEN: 409 /* CONFLICT */,
|
|
199
|
-
DUPLICATE_USERNAME: 409 /* CONFLICT */,
|
|
200
|
-
DUPLICATE_EMAIL: 409 /* CONFLICT */,
|
|
201
|
-
DUPLICATE: 409 /* CONFLICT */,
|
|
202
|
-
EMAIL_NOT_VERIFIED: 400 /* BAD_REQUEST */,
|
|
203
|
-
UPDATE_WITHOUT_ID: 400 /* BAD_REQUEST */,
|
|
204
|
-
CONNECTION_ERROR: 599 /* NETWORK_CONNECT_TIMEOUT */,
|
|
205
|
-
INVALID_PAYMENT: 403 /* FORBIDDEN */,
|
|
206
|
-
DECLINED_PAYMENT: 403 /* FORBIDDEN */,
|
|
207
|
-
INTEGRATION_ERROR: 500 /* SERVER_ERROR */,
|
|
208
|
-
CANNOT_RESERVE: 403 /* FORBIDDEN */,
|
|
209
|
-
REFUND_FAILURE: 403 /* FORBIDDEN */,
|
|
210
|
-
INVALID_INVOICE: 403 /* FORBIDDEN */,
|
|
211
|
-
INVALID_COUPON: 403 /* FORBIDDEN */,
|
|
212
|
-
SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
|
|
213
|
-
METHOD_UNALLOWED: 405 /* METHOD_NOT_ALLOWED */,
|
|
214
|
-
LOGIN_EXPIRED: 401 /* UNAUTHORIZED */,
|
|
215
|
-
THIRD_PARTY_ERROR: 400 /* BAD_REQUEST */,
|
|
216
|
-
ACCESS_DENIED: 403 /* FORBIDDEN */,
|
|
217
|
-
DATABASE_ERROR: 500 /* SERVER_ERROR */,
|
|
218
|
-
SCHEMA_ERROR: 500 /* SERVER_ERROR */
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// src/restura/SqlUtils.ts
|
|
264
|
+
// src/restura/sql/SqlUtils.ts
|
|
222
265
|
var SqlUtils = class _SqlUtils {
|
|
223
266
|
static convertDatabaseTypeToTypescript(type, value) {
|
|
224
267
|
type = type.toLocaleLowerCase();
|
|
@@ -309,9 +352,9 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
309
352
|
return { validator: "any" };
|
|
310
353
|
}
|
|
311
354
|
getTypeFromTable(selector, name) {
|
|
312
|
-
const
|
|
313
|
-
if (
|
|
314
|
-
const tableName =
|
|
355
|
+
const path4 = selector.split(".");
|
|
356
|
+
if (path4.length === 0 || path4.length > 2 || path4[0] === "") return { validator: "any", isOptional: false };
|
|
357
|
+
const tableName = path4.length == 2 ? path4[0] : name, columnName = path4.length == 2 ? path4[1] : path4[0];
|
|
315
358
|
const table = this.database.find((t) => t.name == tableName);
|
|
316
359
|
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
317
360
|
if (!table || !column) return { validator: "any", isOptional: false };
|
|
@@ -527,10 +570,10 @@ var ApiTree = class _ApiTree {
|
|
|
527
570
|
return `${p.name}${optional ? "?" : ""}:${responseType}${array ? "[]" : ""}`;
|
|
528
571
|
}
|
|
529
572
|
getTypeFromTable(selector, name) {
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
let tableName =
|
|
533
|
-
const columnName =
|
|
573
|
+
const path4 = selector.split(".");
|
|
574
|
+
if (path4.length === 0 || path4.length > 2 || path4[0] === "") return { responseType: "any", optional: false };
|
|
575
|
+
let tableName = path4.length == 2 ? path4[0] : name;
|
|
576
|
+
const columnName = path4.length == 2 ? path4[1] : path4[0];
|
|
534
577
|
let table = this.database.find((t) => t.name == tableName);
|
|
535
578
|
if (!table && tableName.includes("_")) {
|
|
536
579
|
const tableAliasSplit = tableName.split("_");
|
|
@@ -545,8 +588,8 @@ var ApiTree = class _ApiTree {
|
|
|
545
588
|
};
|
|
546
589
|
}
|
|
547
590
|
};
|
|
548
|
-
function pathToNamespaces(
|
|
549
|
-
return
|
|
591
|
+
function pathToNamespaces(path4) {
|
|
592
|
+
return path4.split("/").map((e) => import_core_utils.StringUtils.toPascalCasing(e)).filter((e) => e);
|
|
550
593
|
}
|
|
551
594
|
function apiGenerator(schema, schemaHash) {
|
|
552
595
|
let apiString = `/** Auto generated file from Schema Hash (${schemaHash}). DO NOT MODIFY **/`;
|
|
@@ -579,6 +622,92 @@ function apiGenerator(schema, schemaHash) {
|
|
|
579
622
|
}));
|
|
580
623
|
}
|
|
581
624
|
|
|
625
|
+
// src/restura/customApiFactory.ts
|
|
626
|
+
var import_fs = __toESM(require("fs"));
|
|
627
|
+
var import_path = __toESM(require("path"));
|
|
628
|
+
var CustomApiFactory = class {
|
|
629
|
+
constructor() {
|
|
630
|
+
this.customApis = {};
|
|
631
|
+
}
|
|
632
|
+
async loadApiFiles(baseFolderPath) {
|
|
633
|
+
const apiVersions = ["v1"];
|
|
634
|
+
for (const apiVersion of apiVersions) {
|
|
635
|
+
const apiVersionFolderPath = import_path.default.join(baseFolderPath, apiVersion);
|
|
636
|
+
if (!import_fs.default.existsSync(apiVersionFolderPath)) continue;
|
|
637
|
+
await this.addDirectory(apiVersionFolderPath, apiVersion);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
getCustomApi(customApiName) {
|
|
641
|
+
return this.customApis[customApiName];
|
|
642
|
+
}
|
|
643
|
+
async addDirectory(directoryPath, apiVersion) {
|
|
644
|
+
const entries = import_fs.default.readdirSync(directoryPath, {
|
|
645
|
+
withFileTypes: true
|
|
646
|
+
});
|
|
647
|
+
for (const entry of entries) {
|
|
648
|
+
if (entry.isFile()) {
|
|
649
|
+
if (entry.name.endsWith(`.api.${apiVersion}.js`) === false) continue;
|
|
650
|
+
try {
|
|
651
|
+
const importPath = `${import_path.default.join(directoryPath, entry.name)}`;
|
|
652
|
+
const ApiImport = await import(importPath);
|
|
653
|
+
const customApiClass = new ApiImport.default();
|
|
654
|
+
logger.info(`Registering custom API: ${ApiImport.default.name}`);
|
|
655
|
+
this.bindMethodsToInstance(customApiClass);
|
|
656
|
+
this.customApis[ApiImport.default.name] = customApiClass;
|
|
657
|
+
} catch (e) {
|
|
658
|
+
console.error(e);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
bindMethodsToInstance(instance) {
|
|
664
|
+
const proto = Object.getPrototypeOf(instance);
|
|
665
|
+
Object.getOwnPropertyNames(proto).forEach((key) => {
|
|
666
|
+
const property = instance[key];
|
|
667
|
+
if (typeof property === "function" && key !== "constructor") {
|
|
668
|
+
instance[key] = property.bind(instance);
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
var customApiFactory = new CustomApiFactory();
|
|
674
|
+
var customApiFactory_default = customApiFactory;
|
|
675
|
+
|
|
676
|
+
// src/restura/customTypeValidationGenerator.ts
|
|
677
|
+
var import_fs2 = __toESM(require("fs"));
|
|
678
|
+
var TJS = __toESM(require("typescript-json-schema"));
|
|
679
|
+
var import_path2 = __toESM(require("path"));
|
|
680
|
+
var import_tmp = __toESM(require("tmp"));
|
|
681
|
+
var process2 = __toESM(require("process"));
|
|
682
|
+
function customTypeValidationGenerator(currentSchema) {
|
|
683
|
+
const schemaObject = {};
|
|
684
|
+
const customInterfaceNames = currentSchema.customTypes.match(new RegExp("(?<=interface\\s)(\\w+)|(?<=type\\s)(\\w+)", "g"));
|
|
685
|
+
if (!customInterfaceNames) return {};
|
|
686
|
+
const temporaryFile = import_tmp.default.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
|
|
687
|
+
import_fs2.default.writeFileSync(temporaryFile.name, currentSchema.customTypes);
|
|
688
|
+
const compilerOptions = {
|
|
689
|
+
strictNullChecks: true,
|
|
690
|
+
skipLibCheck: true
|
|
691
|
+
};
|
|
692
|
+
const program = TJS.getProgramFromFiles(
|
|
693
|
+
[
|
|
694
|
+
(0, import_path2.resolve)(temporaryFile.name),
|
|
695
|
+
// find a way to remove
|
|
696
|
+
import_path2.default.join(process2.cwd(), "src/@types/models.d.ts"),
|
|
697
|
+
import_path2.default.join(process2.cwd(), "src/@types/api.d.ts")
|
|
698
|
+
],
|
|
699
|
+
compilerOptions
|
|
700
|
+
);
|
|
701
|
+
customInterfaceNames.forEach((item) => {
|
|
702
|
+
const ddlSchema = TJS.generateSchema(program, item, {
|
|
703
|
+
required: true
|
|
704
|
+
});
|
|
705
|
+
schemaObject[item] = ddlSchema || {};
|
|
706
|
+
});
|
|
707
|
+
temporaryFile.removeCallback();
|
|
708
|
+
return schemaObject;
|
|
709
|
+
}
|
|
710
|
+
|
|
582
711
|
// src/restura/middleware/addApiResponseFunctions.ts
|
|
583
712
|
function addApiResponseFunctions(req, res, next) {
|
|
584
713
|
res.sendData = function(data, statusCode = 200) {
|
|
@@ -607,121 +736,104 @@ function addApiResponseFunctions(req, res, next) {
|
|
|
607
736
|
next();
|
|
608
737
|
}
|
|
609
738
|
|
|
610
|
-
// src/restura/
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
body = "body";
|
|
619
|
-
}
|
|
620
|
-
const bodyData = req[body];
|
|
621
|
-
if (bodyData) {
|
|
622
|
-
for (const attr in bodyData) {
|
|
623
|
-
if (attr === "token") {
|
|
624
|
-
delete bodyData[attr];
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
if (bodyData[attr] instanceof Array) {
|
|
628
|
-
const attrList = [];
|
|
629
|
-
for (const value of bodyData[attr]) {
|
|
630
|
-
if (isNaN(Number(value))) continue;
|
|
631
|
-
attrList.push(Number(value));
|
|
632
|
-
}
|
|
633
|
-
if (import_core_utils2.ObjectUtils.isArrayWithData(attrList)) {
|
|
634
|
-
bodyData[attr] = attrList;
|
|
635
|
-
}
|
|
636
|
-
} else {
|
|
637
|
-
bodyData[attr] = import_core_utils2.ObjectUtils.safeParse(bodyData[attr]);
|
|
638
|
-
if (isNaN(Number(bodyData[attr]))) continue;
|
|
639
|
-
bodyData[attr] = Number(bodyData[attr]);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return bodyData;
|
|
739
|
+
// src/restura/middleware/authenticateUser.ts
|
|
740
|
+
function authenticateUser(applicationAuthenticateHandler) {
|
|
741
|
+
return (req, res, next) => {
|
|
742
|
+
applicationAuthenticateHandler(req, res, (userDetails) => {
|
|
743
|
+
req.requesterDetails = __spreadValues(__spreadValues({}, req.requesterDetails), userDetails);
|
|
744
|
+
next();
|
|
745
|
+
});
|
|
746
|
+
};
|
|
644
747
|
}
|
|
645
748
|
|
|
646
749
|
// src/restura/restura.schema.ts
|
|
750
|
+
var import_zod3 = require("zod");
|
|
751
|
+
|
|
752
|
+
// src/restura/types/validation.types.ts
|
|
647
753
|
var import_zod2 = require("zod");
|
|
648
|
-
var
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
754
|
+
var validatorDataSchemeValue = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string()), import_zod2.z.number(), import_zod2.z.array(import_zod2.z.number())]);
|
|
755
|
+
var validatorDataSchema = import_zod2.z.object({
|
|
756
|
+
type: import_zod2.z.enum(["TYPE_CHECK", "MIN", "MAX", "ONE_OF"]),
|
|
757
|
+
value: validatorDataSchemeValue
|
|
652
758
|
}).strict();
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
759
|
+
|
|
760
|
+
// src/restura/restura.schema.ts
|
|
761
|
+
var orderBySchema = import_zod3.z.object({
|
|
762
|
+
columnName: import_zod3.z.string(),
|
|
763
|
+
order: import_zod3.z.enum(["ASC", "DESC"]),
|
|
764
|
+
tableName: import_zod3.z.string()
|
|
656
765
|
}).strict();
|
|
657
|
-
var
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
operator: import_zod2.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
|
|
661
|
-
value: import_zod2.z.string().optional(),
|
|
662
|
-
custom: import_zod2.z.string().optional(),
|
|
663
|
-
conjunction: import_zod2.z.enum(["AND", "OR"]).optional()
|
|
766
|
+
var groupBySchema = import_zod3.z.object({
|
|
767
|
+
columnName: import_zod3.z.string(),
|
|
768
|
+
tableName: import_zod3.z.string()
|
|
664
769
|
}).strict();
|
|
665
|
-
var
|
|
666
|
-
|
|
667
|
-
|
|
770
|
+
var whereDataSchema = import_zod3.z.object({
|
|
771
|
+
tableName: import_zod3.z.string().optional(),
|
|
772
|
+
columnName: import_zod3.z.string().optional(),
|
|
773
|
+
operator: import_zod3.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
|
|
774
|
+
value: import_zod3.z.string().or(import_zod3.z.number()).optional(),
|
|
775
|
+
custom: import_zod3.z.string().optional(),
|
|
776
|
+
conjunction: import_zod3.z.enum(["AND", "OR"]).optional()
|
|
668
777
|
}).strict();
|
|
669
|
-
var
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
foreignColumnName: import_zod2.z.string().optional(),
|
|
673
|
-
custom: import_zod2.z.string().optional(),
|
|
674
|
-
type: import_zod2.z.enum(["LEFT", "INNER"]),
|
|
675
|
-
alias: import_zod2.z.string().optional()
|
|
778
|
+
var assignmentDataSchema = import_zod3.z.object({
|
|
779
|
+
name: import_zod3.z.string(),
|
|
780
|
+
value: import_zod3.z.string()
|
|
676
781
|
}).strict();
|
|
677
|
-
var
|
|
678
|
-
|
|
679
|
-
|
|
782
|
+
var joinDataSchema = import_zod3.z.object({
|
|
783
|
+
table: import_zod3.z.string(),
|
|
784
|
+
localColumnName: import_zod3.z.string().optional(),
|
|
785
|
+
foreignColumnName: import_zod3.z.string().optional(),
|
|
786
|
+
custom: import_zod3.z.string().optional(),
|
|
787
|
+
type: import_zod3.z.enum(["LEFT", "INNER"]),
|
|
788
|
+
alias: import_zod3.z.string().optional()
|
|
680
789
|
}).strict();
|
|
681
|
-
var requestDataSchema =
|
|
682
|
-
name:
|
|
683
|
-
required:
|
|
684
|
-
validator:
|
|
790
|
+
var requestDataSchema = import_zod3.z.object({
|
|
791
|
+
name: import_zod3.z.string(),
|
|
792
|
+
required: import_zod3.z.boolean(),
|
|
793
|
+
validator: import_zod3.z.array(validatorDataSchema)
|
|
685
794
|
}).strict();
|
|
686
|
-
var responseDataSchema =
|
|
687
|
-
name:
|
|
688
|
-
selector:
|
|
689
|
-
subquery:
|
|
690
|
-
table:
|
|
691
|
-
joins:
|
|
692
|
-
where:
|
|
693
|
-
properties:
|
|
795
|
+
var responseDataSchema = import_zod3.z.object({
|
|
796
|
+
name: import_zod3.z.string(),
|
|
797
|
+
selector: import_zod3.z.string().optional(),
|
|
798
|
+
subquery: import_zod3.z.object({
|
|
799
|
+
table: import_zod3.z.string(),
|
|
800
|
+
joins: import_zod3.z.array(joinDataSchema),
|
|
801
|
+
where: import_zod3.z.array(whereDataSchema),
|
|
802
|
+
properties: import_zod3.z.array(import_zod3.z.lazy(() => responseDataSchema)),
|
|
694
803
|
// Explicit type for the lazy schema
|
|
695
804
|
groupBy: groupBySchema.optional(),
|
|
696
805
|
orderBy: orderBySchema.optional()
|
|
697
806
|
}).optional()
|
|
698
807
|
}).strict();
|
|
699
|
-
var routeDataBaseSchema =
|
|
700
|
-
method:
|
|
701
|
-
name:
|
|
702
|
-
description:
|
|
703
|
-
path:
|
|
704
|
-
roles:
|
|
808
|
+
var routeDataBaseSchema = import_zod3.z.object({
|
|
809
|
+
method: import_zod3.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
810
|
+
name: import_zod3.z.string(),
|
|
811
|
+
description: import_zod3.z.string(),
|
|
812
|
+
path: import_zod3.z.string(),
|
|
813
|
+
roles: import_zod3.z.array(import_zod3.z.string())
|
|
705
814
|
}).strict();
|
|
706
815
|
var standardRouteSchema = routeDataBaseSchema.extend({
|
|
707
|
-
type:
|
|
708
|
-
table:
|
|
709
|
-
joins:
|
|
710
|
-
assignments:
|
|
711
|
-
where:
|
|
712
|
-
request:
|
|
713
|
-
response:
|
|
816
|
+
type: import_zod3.z.enum(["ONE", "ARRAY", "PAGED"]),
|
|
817
|
+
table: import_zod3.z.string(),
|
|
818
|
+
joins: import_zod3.z.array(joinDataSchema),
|
|
819
|
+
assignments: import_zod3.z.array(assignmentDataSchema),
|
|
820
|
+
where: import_zod3.z.array(whereDataSchema),
|
|
821
|
+
request: import_zod3.z.array(requestDataSchema),
|
|
822
|
+
response: import_zod3.z.array(responseDataSchema),
|
|
714
823
|
groupBy: groupBySchema.optional(),
|
|
715
824
|
orderBy: orderBySchema.optional()
|
|
716
825
|
}).strict();
|
|
717
826
|
var customRouteSchema = routeDataBaseSchema.extend({
|
|
718
|
-
type:
|
|
719
|
-
responseType:
|
|
720
|
-
requestType:
|
|
721
|
-
request:
|
|
722
|
-
|
|
827
|
+
type: import_zod3.z.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
|
|
828
|
+
responseType: import_zod3.z.union([import_zod3.z.string(), import_zod3.z.enum(["string", "number", "boolean"])]),
|
|
829
|
+
requestType: import_zod3.z.string().optional(),
|
|
830
|
+
request: import_zod3.z.array(requestDataSchema).optional(),
|
|
831
|
+
table: import_zod3.z.undefined(),
|
|
832
|
+
joins: import_zod3.z.undefined(),
|
|
833
|
+
assignments: import_zod3.z.undefined(),
|
|
834
|
+
fileUploadType: import_zod3.z.enum(["SINGLE", "MULTIPLE"]).optional()
|
|
723
835
|
}).strict();
|
|
724
|
-
var postgresColumnNumericTypesSchema =
|
|
836
|
+
var postgresColumnNumericTypesSchema = import_zod3.z.enum([
|
|
725
837
|
"SMALLINT",
|
|
726
838
|
// 2 bytes, -32,768 to 32,767
|
|
727
839
|
"INTEGER",
|
|
@@ -741,7 +853,7 @@ var postgresColumnNumericTypesSchema = import_zod2.z.enum([
|
|
|
741
853
|
"BIGSERIAL"
|
|
742
854
|
// auto-incrementing big integer
|
|
743
855
|
]);
|
|
744
|
-
var postgresColumnStringTypesSchema =
|
|
856
|
+
var postgresColumnStringTypesSchema = import_zod3.z.enum([
|
|
745
857
|
"CHAR",
|
|
746
858
|
// fixed-length, blank-padded
|
|
747
859
|
"VARCHAR",
|
|
@@ -751,7 +863,7 @@ var postgresColumnStringTypesSchema = import_zod2.z.enum([
|
|
|
751
863
|
"BYTEA"
|
|
752
864
|
// binary data
|
|
753
865
|
]);
|
|
754
|
-
var postgresColumnDateTypesSchema =
|
|
866
|
+
var postgresColumnDateTypesSchema = import_zod3.z.enum([
|
|
755
867
|
"DATE",
|
|
756
868
|
// calendar date (year, month, day)
|
|
757
869
|
"TIMESTAMP",
|
|
@@ -763,7 +875,7 @@ var postgresColumnDateTypesSchema = import_zod2.z.enum([
|
|
|
763
875
|
"INTERVAL"
|
|
764
876
|
// time span
|
|
765
877
|
]);
|
|
766
|
-
var mariaDbColumnNumericTypesSchema =
|
|
878
|
+
var mariaDbColumnNumericTypesSchema = import_zod3.z.enum([
|
|
767
879
|
"BOOLEAN",
|
|
768
880
|
// 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
|
|
769
881
|
"TINYINT",
|
|
@@ -783,7 +895,7 @@ var mariaDbColumnNumericTypesSchema = import_zod2.z.enum([
|
|
|
783
895
|
"DOUBLE"
|
|
784
896
|
// 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.
|
|
785
897
|
]);
|
|
786
|
-
var mariaDbColumnStringTypesSchema =
|
|
898
|
+
var mariaDbColumnStringTypesSchema = import_zod3.z.enum([
|
|
787
899
|
"CHAR",
|
|
788
900
|
// 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.
|
|
789
901
|
"VARCHAR",
|
|
@@ -809,7 +921,7 @@ var mariaDbColumnStringTypesSchema = import_zod2.z.enum([
|
|
|
809
921
|
"ENUM"
|
|
810
922
|
// Enum type
|
|
811
923
|
]);
|
|
812
|
-
var mariaDbColumnDateTypesSchema =
|
|
924
|
+
var mariaDbColumnDateTypesSchema = import_zod3.z.enum([
|
|
813
925
|
"DATE",
|
|
814
926
|
// 4-bytes Date has year, month, and day.
|
|
815
927
|
"DATETIME",
|
|
@@ -819,9 +931,9 @@ var mariaDbColumnDateTypesSchema = import_zod2.z.enum([
|
|
|
819
931
|
"TIMESTAMP"
|
|
820
932
|
// 4-bytes Values are stored as the number of seconds since 1970-01-01 00:00:00 UTC, and optionally microseconds.
|
|
821
933
|
]);
|
|
822
|
-
var columnDataSchema =
|
|
823
|
-
name:
|
|
824
|
-
type:
|
|
934
|
+
var columnDataSchema = import_zod3.z.object({
|
|
935
|
+
name: import_zod3.z.string(),
|
|
936
|
+
type: import_zod3.z.union([
|
|
825
937
|
postgresColumnNumericTypesSchema,
|
|
826
938
|
postgresColumnStringTypesSchema,
|
|
827
939
|
postgresColumnDateTypesSchema,
|
|
@@ -829,24 +941,24 @@ var columnDataSchema = import_zod2.z.object({
|
|
|
829
941
|
mariaDbColumnStringTypesSchema,
|
|
830
942
|
mariaDbColumnDateTypesSchema
|
|
831
943
|
]),
|
|
832
|
-
isNullable:
|
|
833
|
-
roles:
|
|
834
|
-
comment:
|
|
835
|
-
default:
|
|
836
|
-
value:
|
|
837
|
-
isPrimary:
|
|
838
|
-
isUnique:
|
|
839
|
-
hasAutoIncrement:
|
|
840
|
-
length:
|
|
944
|
+
isNullable: import_zod3.z.boolean(),
|
|
945
|
+
roles: import_zod3.z.array(import_zod3.z.string()),
|
|
946
|
+
comment: import_zod3.z.string().optional(),
|
|
947
|
+
default: import_zod3.z.string().optional(),
|
|
948
|
+
value: import_zod3.z.string().optional(),
|
|
949
|
+
isPrimary: import_zod3.z.boolean().optional(),
|
|
950
|
+
isUnique: import_zod3.z.boolean().optional(),
|
|
951
|
+
hasAutoIncrement: import_zod3.z.boolean().optional(),
|
|
952
|
+
length: import_zod3.z.number().optional()
|
|
841
953
|
}).strict();
|
|
842
|
-
var indexDataSchema =
|
|
843
|
-
name:
|
|
844
|
-
columns:
|
|
845
|
-
isUnique:
|
|
846
|
-
isPrimaryKey:
|
|
847
|
-
order:
|
|
954
|
+
var indexDataSchema = import_zod3.z.object({
|
|
955
|
+
name: import_zod3.z.string(),
|
|
956
|
+
columns: import_zod3.z.array(import_zod3.z.string()),
|
|
957
|
+
isUnique: import_zod3.z.boolean(),
|
|
958
|
+
isPrimaryKey: import_zod3.z.boolean(),
|
|
959
|
+
order: import_zod3.z.enum(["ASC", "DESC"])
|
|
848
960
|
}).strict();
|
|
849
|
-
var foreignKeyActionsSchema =
|
|
961
|
+
var foreignKeyActionsSchema = import_zod3.z.enum([
|
|
850
962
|
"CASCADE",
|
|
851
963
|
// CASCADE action for foreign keys
|
|
852
964
|
"SET NULL",
|
|
@@ -858,38 +970,38 @@ var foreignKeyActionsSchema = import_zod2.z.enum([
|
|
|
858
970
|
"SET DEFAULT"
|
|
859
971
|
// SET DEFAULT action for foreign keys
|
|
860
972
|
]);
|
|
861
|
-
var foreignKeyDataSchema =
|
|
862
|
-
name:
|
|
863
|
-
column:
|
|
864
|
-
refTable:
|
|
865
|
-
refColumn:
|
|
973
|
+
var foreignKeyDataSchema = import_zod3.z.object({
|
|
974
|
+
name: import_zod3.z.string(),
|
|
975
|
+
column: import_zod3.z.string(),
|
|
976
|
+
refTable: import_zod3.z.string(),
|
|
977
|
+
refColumn: import_zod3.z.string(),
|
|
866
978
|
onDelete: foreignKeyActionsSchema,
|
|
867
979
|
onUpdate: foreignKeyActionsSchema
|
|
868
980
|
}).strict();
|
|
869
|
-
var checkConstraintDataSchema =
|
|
870
|
-
name:
|
|
871
|
-
check:
|
|
981
|
+
var checkConstraintDataSchema = import_zod3.z.object({
|
|
982
|
+
name: import_zod3.z.string(),
|
|
983
|
+
check: import_zod3.z.string()
|
|
872
984
|
}).strict();
|
|
873
|
-
var tableDataSchema =
|
|
874
|
-
name:
|
|
875
|
-
columns:
|
|
876
|
-
indexes:
|
|
877
|
-
foreignKeys:
|
|
878
|
-
checkConstraints:
|
|
879
|
-
roles:
|
|
985
|
+
var tableDataSchema = import_zod3.z.object({
|
|
986
|
+
name: import_zod3.z.string(),
|
|
987
|
+
columns: import_zod3.z.array(columnDataSchema),
|
|
988
|
+
indexes: import_zod3.z.array(indexDataSchema),
|
|
989
|
+
foreignKeys: import_zod3.z.array(foreignKeyDataSchema),
|
|
990
|
+
checkConstraints: import_zod3.z.array(checkConstraintDataSchema),
|
|
991
|
+
roles: import_zod3.z.array(import_zod3.z.string())
|
|
880
992
|
}).strict();
|
|
881
|
-
var endpointDataSchema =
|
|
882
|
-
name:
|
|
883
|
-
description:
|
|
884
|
-
baseUrl:
|
|
885
|
-
routes:
|
|
993
|
+
var endpointDataSchema = import_zod3.z.object({
|
|
994
|
+
name: import_zod3.z.string(),
|
|
995
|
+
description: import_zod3.z.string(),
|
|
996
|
+
baseUrl: import_zod3.z.string(),
|
|
997
|
+
routes: import_zod3.z.array(import_zod3.z.union([standardRouteSchema, customRouteSchema]))
|
|
886
998
|
}).strict();
|
|
887
|
-
var resturaZodSchema =
|
|
888
|
-
database:
|
|
889
|
-
endpoints:
|
|
890
|
-
globalParams:
|
|
891
|
-
roles:
|
|
892
|
-
customTypes:
|
|
999
|
+
var resturaZodSchema = import_zod3.z.object({
|
|
1000
|
+
database: import_zod3.z.array(tableDataSchema),
|
|
1001
|
+
endpoints: import_zod3.z.array(endpointDataSchema),
|
|
1002
|
+
globalParams: import_zod3.z.array(import_zod3.z.string()),
|
|
1003
|
+
roles: import_zod3.z.array(import_zod3.z.string()),
|
|
1004
|
+
customTypes: import_zod3.z.string()
|
|
893
1005
|
}).strict();
|
|
894
1006
|
async function isSchemaValid(schemaToCheck) {
|
|
895
1007
|
try {
|
|
@@ -901,6 +1013,187 @@ async function isSchemaValid(schemaToCheck) {
|
|
|
901
1013
|
}
|
|
902
1014
|
}
|
|
903
1015
|
|
|
1016
|
+
// src/restura/validateRequestParams.ts
|
|
1017
|
+
var import_core_utils2 = require("@redskytech/core-utils");
|
|
1018
|
+
var import_jsonschema = __toESM(require("jsonschema"));
|
|
1019
|
+
var import_zod4 = require("zod");
|
|
1020
|
+
|
|
1021
|
+
// src/restura/utils/addQuotesToStrings.ts
|
|
1022
|
+
function addQuotesToStrings(variable) {
|
|
1023
|
+
if (typeof variable === "string") {
|
|
1024
|
+
return `'${variable}'`;
|
|
1025
|
+
} else if (Array.isArray(variable)) {
|
|
1026
|
+
const arrayWithQuotes = variable.map(addQuotesToStrings);
|
|
1027
|
+
return arrayWithQuotes;
|
|
1028
|
+
} else {
|
|
1029
|
+
return variable;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/restura/validateRequestParams.ts
|
|
1034
|
+
function validateRequestParams(req, routeData, validationSchema) {
|
|
1035
|
+
const requestData = getRequestData(req);
|
|
1036
|
+
req.data = requestData;
|
|
1037
|
+
if (routeData.request === void 0) {
|
|
1038
|
+
if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
|
|
1039
|
+
throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
|
|
1040
|
+
if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
|
|
1041
|
+
if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
|
|
1042
|
+
const currentInterface = validationSchema[routeData.requestType];
|
|
1043
|
+
const validator = new import_jsonschema.default.Validator();
|
|
1044
|
+
const executeValidation = validator.validate(req.data, currentInterface);
|
|
1045
|
+
if (!executeValidation.valid) {
|
|
1046
|
+
throw new RsError(
|
|
1047
|
+
"BAD_REQUEST",
|
|
1048
|
+
`Request custom setup has failed the following check: (${executeValidation.errors})`
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
Object.keys(req.data).forEach((requestParamName) => {
|
|
1054
|
+
const requestParam = routeData.request.find((param) => param.name === requestParamName);
|
|
1055
|
+
if (!requestParam) {
|
|
1056
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
routeData.request.forEach((requestParam) => {
|
|
1060
|
+
const requestValue = requestData[requestParam.name];
|
|
1061
|
+
if (requestParam.required && requestValue === void 0)
|
|
1062
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
|
|
1063
|
+
else if (!requestParam.required && requestValue === void 0) return;
|
|
1064
|
+
validateRequestSingleParam(requestValue, requestParam);
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
function validateRequestSingleParam(requestValue, requestParam) {
|
|
1068
|
+
requestParam.validator.forEach((validator) => {
|
|
1069
|
+
switch (validator.type) {
|
|
1070
|
+
case "TYPE_CHECK":
|
|
1071
|
+
performTypeCheck(requestValue, validator, requestParam.name);
|
|
1072
|
+
break;
|
|
1073
|
+
case "MIN":
|
|
1074
|
+
performMinCheck(requestValue, validator, requestParam.name);
|
|
1075
|
+
break;
|
|
1076
|
+
case "MAX":
|
|
1077
|
+
performMaxCheck(requestValue, validator, requestParam.name);
|
|
1078
|
+
break;
|
|
1079
|
+
case "ONE_OF":
|
|
1080
|
+
performOneOfCheck(requestValue, validator, requestParam.name);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
function isValidType(type, requestValue) {
|
|
1086
|
+
try {
|
|
1087
|
+
expectValidType(type, requestValue);
|
|
1088
|
+
return true;
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
return false;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
function expectValidType(type, requestValue) {
|
|
1094
|
+
if (type === "number") {
|
|
1095
|
+
return import_zod4.z.number().parse(requestValue);
|
|
1096
|
+
}
|
|
1097
|
+
if (type === "string") {
|
|
1098
|
+
return import_zod4.z.string().parse(requestValue);
|
|
1099
|
+
}
|
|
1100
|
+
if (type === "boolean") {
|
|
1101
|
+
return import_zod4.z.boolean().parse(requestValue);
|
|
1102
|
+
}
|
|
1103
|
+
if (type === "string[]") {
|
|
1104
|
+
return import_zod4.z.array(import_zod4.z.string()).parse(requestValue);
|
|
1105
|
+
}
|
|
1106
|
+
if (type === "number[]") {
|
|
1107
|
+
return import_zod4.z.array(import_zod4.z.number()).parse(requestValue);
|
|
1108
|
+
}
|
|
1109
|
+
if (type === "any[]") {
|
|
1110
|
+
return import_zod4.z.array(import_zod4.z.any()).parse(requestValue);
|
|
1111
|
+
}
|
|
1112
|
+
if (type === "object") {
|
|
1113
|
+
return import_zod4.z.object({}).strict().parse(requestValue);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function performTypeCheck(requestValue, validator, requestParamName) {
|
|
1117
|
+
if (!isValidType(validator.value, requestValue)) {
|
|
1118
|
+
throw new RsError(
|
|
1119
|
+
"BAD_REQUEST",
|
|
1120
|
+
`Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
validatorDataSchemeValue.parse(validator.value);
|
|
1125
|
+
} catch (e) {
|
|
1126
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function expectOnlyNumbers(requestValue, validator, requestParamName) {
|
|
1130
|
+
if (!isValueNumber(requestValue))
|
|
1131
|
+
throw new RsError(
|
|
1132
|
+
"BAD_REQUEST",
|
|
1133
|
+
`Request param (${requestParamName}) with value (${requestValue}) is not of type number`
|
|
1134
|
+
);
|
|
1135
|
+
if (!isValueNumber(validator.value))
|
|
1136
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
|
|
1137
|
+
}
|
|
1138
|
+
function performMinCheck(requestValue, validator, requestParamName) {
|
|
1139
|
+
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
1140
|
+
if (requestValue < validator.value)
|
|
1141
|
+
throw new RsError(
|
|
1142
|
+
"BAD_REQUEST",
|
|
1143
|
+
`Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
function performMaxCheck(requestValue, validator, requestParamName) {
|
|
1147
|
+
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
1148
|
+
if (requestValue > validator.value)
|
|
1149
|
+
throw new RsError(
|
|
1150
|
+
"BAD_REQUEST",
|
|
1151
|
+
`Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
function performOneOfCheck(requestValue, validator, requestParamName) {
|
|
1155
|
+
if (!import_core_utils2.ObjectUtils.isArrayWithData(validator.value))
|
|
1156
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
|
|
1157
|
+
if (typeof requestValue === "object")
|
|
1158
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
|
|
1159
|
+
if (!validator.value.includes(requestValue))
|
|
1160
|
+
throw new RsError(
|
|
1161
|
+
"BAD_REQUEST",
|
|
1162
|
+
`Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
function isValueNumber(value) {
|
|
1166
|
+
return !isNaN(Number(value));
|
|
1167
|
+
}
|
|
1168
|
+
function getRequestData(req) {
|
|
1169
|
+
let body = "";
|
|
1170
|
+
if (req.method === "GET" || req.method === "DELETE") {
|
|
1171
|
+
body = "query";
|
|
1172
|
+
} else {
|
|
1173
|
+
body = "body";
|
|
1174
|
+
}
|
|
1175
|
+
const bodyData = req[body];
|
|
1176
|
+
if (bodyData && body === "query") {
|
|
1177
|
+
for (const attr in bodyData) {
|
|
1178
|
+
if (bodyData[attr] instanceof Array) {
|
|
1179
|
+
const attrList = [];
|
|
1180
|
+
for (const value of bodyData[attr]) {
|
|
1181
|
+
if (isNaN(Number(value))) continue;
|
|
1182
|
+
attrList.push(Number(value));
|
|
1183
|
+
}
|
|
1184
|
+
if (import_core_utils2.ObjectUtils.isArrayWithData(attrList)) {
|
|
1185
|
+
bodyData[attr] = attrList;
|
|
1186
|
+
}
|
|
1187
|
+
} else {
|
|
1188
|
+
bodyData[attr] = import_core_utils2.ObjectUtils.safeParse(bodyData[attr]);
|
|
1189
|
+
if (isNaN(Number(bodyData[attr]))) continue;
|
|
1190
|
+
bodyData[attr] = Number(bodyData[attr]);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return bodyData;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
904
1197
|
// src/restura/middleware/schemaValidation.ts
|
|
905
1198
|
async function schemaValidation(req, res, next) {
|
|
906
1199
|
req.data = getRequestData(req);
|
|
@@ -948,23 +1241,566 @@ function convertTable(table) {
|
|
|
948
1241
|
return modelString;
|
|
949
1242
|
}
|
|
950
1243
|
|
|
951
|
-
// src/restura/
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1244
|
+
// src/restura/sql/PsqlEngine.ts
|
|
1245
|
+
var import_core_utils5 = require("@redskytech/core-utils");
|
|
1246
|
+
|
|
1247
|
+
// src/restura/sql/PsqlUtils.ts
|
|
1248
|
+
var import_pg_format = __toESM(require("pg-format"));
|
|
1249
|
+
function escapeColumnName(columnName) {
|
|
1250
|
+
if (columnName === void 0) return "";
|
|
1251
|
+
return `"${columnName.replace(/"/g, "")}"`.replace(".", '"."');
|
|
1252
|
+
}
|
|
1253
|
+
function questionMarksToOrderedParams(query) {
|
|
1254
|
+
let count = 1;
|
|
1255
|
+
return query.replace(/'\?'|\?/g, () => `$${count++}`);
|
|
1256
|
+
}
|
|
1257
|
+
function insertObjectQuery(table, obj) {
|
|
1258
|
+
const keys = Object.keys(obj);
|
|
1259
|
+
const params = Object.values(obj);
|
|
1260
|
+
const columns = keys.map((column) => escapeColumnName(column)).join(", ");
|
|
1261
|
+
const values = params.map((value) => SQL`${value}`).join(", ");
|
|
1262
|
+
const query = `INSERT INTO "${table}" (${columns})
|
|
1263
|
+
VALUES (${values})
|
|
1264
|
+
RETURNING *`;
|
|
1265
|
+
return query;
|
|
1266
|
+
}
|
|
1267
|
+
function updateObjectQuery(table, obj, whereStatement) {
|
|
1268
|
+
const setArray = [];
|
|
1269
|
+
for (const i in obj) {
|
|
1270
|
+
setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
|
|
1271
|
+
}
|
|
1272
|
+
return `UPDATE ${escapeColumnName(table)}
|
|
1273
|
+
SET ${setArray.join(", ")} ${whereStatement}
|
|
1274
|
+
RETURNING *`;
|
|
1275
|
+
}
|
|
1276
|
+
function isValueNumber2(value) {
|
|
1277
|
+
return !isNaN(Number(value));
|
|
1278
|
+
}
|
|
1279
|
+
function SQL(strings, ...values) {
|
|
1280
|
+
let query = strings[0];
|
|
1281
|
+
values.forEach((value, index) => {
|
|
1282
|
+
if (isValueNumber2(value)) {
|
|
1283
|
+
query += value;
|
|
1284
|
+
} else {
|
|
1285
|
+
query += import_pg_format.default.literal(value);
|
|
1286
|
+
}
|
|
1287
|
+
query += strings[index + 1];
|
|
1288
|
+
});
|
|
1289
|
+
return query;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// src/restura/sql/SqlEngine.ts
|
|
1293
|
+
var import_core_utils4 = require("@redskytech/core-utils");
|
|
1294
|
+
var SqlEngine = class {
|
|
1295
|
+
async runQueryForRoute(req, routeData, schema) {
|
|
1296
|
+
if (!this.doesRoleHavePermissionToTable(req.requesterDetails.role, schema, routeData.table))
|
|
1297
|
+
throw new RsError("UNAUTHORIZED", "You do not have permission to access this table");
|
|
1298
|
+
switch (routeData.method) {
|
|
1299
|
+
case "POST":
|
|
1300
|
+
return this.executeCreateRequest(req, routeData, schema);
|
|
1301
|
+
case "GET":
|
|
1302
|
+
return this.executeGetRequest(req, routeData, schema);
|
|
1303
|
+
case "PUT":
|
|
1304
|
+
case "PATCH":
|
|
1305
|
+
return this.executeUpdateRequest(req, routeData, schema);
|
|
1306
|
+
case "DELETE":
|
|
1307
|
+
return this.executeDeleteRequest(req, routeData, schema);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
getTableSchema(schema, tableName) {
|
|
1311
|
+
const tableSchema = schema.database.find((item) => item.name === tableName);
|
|
1312
|
+
if (!tableSchema) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1313
|
+
return tableSchema;
|
|
1314
|
+
}
|
|
1315
|
+
doesRoleHavePermissionToColumn(role, schema, item, joins) {
|
|
1316
|
+
if (item.selector) {
|
|
1317
|
+
let tableName = item.selector.split(".")[0];
|
|
1318
|
+
const columnName = item.selector.split(".")[1];
|
|
1319
|
+
let tableSchema = schema.database.find((item2) => item2.name === tableName);
|
|
1320
|
+
if (!tableSchema) {
|
|
1321
|
+
const join = joins.find((join2) => join2.alias === tableName);
|
|
1322
|
+
if (!join) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1323
|
+
tableName = join.table;
|
|
1324
|
+
tableSchema = schema.database.find((item2) => item2.name === tableName);
|
|
1325
|
+
}
|
|
1326
|
+
if (!tableSchema) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1327
|
+
const columnSchema = tableSchema.columns.find((item2) => item2.name === columnName);
|
|
1328
|
+
if (!columnSchema)
|
|
1329
|
+
throw new RsError("SCHEMA_ERROR", `Column ${columnName} not found in table ${tableName}`);
|
|
1330
|
+
const doesColumnHaveRoles = import_core_utils4.ObjectUtils.isArrayWithData(columnSchema.roles);
|
|
1331
|
+
if (!doesColumnHaveRoles) return true;
|
|
1332
|
+
if (!role) return false;
|
|
1333
|
+
return columnSchema.roles.includes(role);
|
|
1334
|
+
}
|
|
1335
|
+
if (item.subquery) {
|
|
1336
|
+
return import_core_utils4.ObjectUtils.isArrayWithData(
|
|
1337
|
+
item.subquery.properties.filter((nestedItem) => {
|
|
1338
|
+
return this.doesRoleHavePermissionToColumn(role, schema, nestedItem, joins);
|
|
1339
|
+
})
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
doesRoleHavePermissionToTable(userRole, schema, tableName) {
|
|
1345
|
+
const tableSchema = this.getTableSchema(schema, tableName);
|
|
1346
|
+
const doesTableHaveRoles = import_core_utils4.ObjectUtils.isArrayWithData(tableSchema.roles);
|
|
1347
|
+
if (!doesTableHaveRoles) return true;
|
|
1348
|
+
if (!userRole) return false;
|
|
1349
|
+
return tableSchema.roles.includes(userRole);
|
|
1350
|
+
}
|
|
1351
|
+
replaceParamKeywords(value, routeData, req, sqlParams) {
|
|
1352
|
+
let returnValue = value;
|
|
1353
|
+
returnValue = this.replaceLocalParamKeywords(returnValue, routeData, req, sqlParams);
|
|
1354
|
+
returnValue = this.replaceGlobalParamKeywords(returnValue, routeData, req, sqlParams);
|
|
1355
|
+
return returnValue;
|
|
1356
|
+
}
|
|
1357
|
+
replaceLocalParamKeywords(value, routeData, req, sqlParams) {
|
|
1358
|
+
var _a;
|
|
1359
|
+
if (!routeData.request) return value;
|
|
1360
|
+
const data = req.data;
|
|
1361
|
+
if (typeof value === "string") {
|
|
1362
|
+
(_a = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a.forEach((param) => {
|
|
1363
|
+
const requestParam = routeData.request.find((item) => {
|
|
1364
|
+
return item.name === param.replace("$", "");
|
|
1365
|
+
});
|
|
1366
|
+
if (!requestParam)
|
|
1367
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1368
|
+
sqlParams.push(data[requestParam.name]);
|
|
1369
|
+
});
|
|
1370
|
+
return value.replace(new RegExp(/\$[a-zA-Z][a-zA-Z0-9_]+/g), "?");
|
|
1371
|
+
}
|
|
1372
|
+
return value;
|
|
1373
|
+
}
|
|
1374
|
+
replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
|
|
1375
|
+
var _a;
|
|
1376
|
+
if (typeof value === "string") {
|
|
1377
|
+
(_a = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a.forEach((param) => {
|
|
1378
|
+
param = param.replace("#", "");
|
|
1379
|
+
const globalParamValue = req.requesterDetails[param];
|
|
1380
|
+
if (!globalParamValue)
|
|
1381
|
+
throw new RsError(
|
|
1382
|
+
"SCHEMA_ERROR",
|
|
1383
|
+
`Invalid global keyword clause in route (${routeData.path}) when looking for (#${param})`
|
|
1384
|
+
);
|
|
1385
|
+
sqlParams.push(globalParamValue);
|
|
1386
|
+
});
|
|
1387
|
+
return value.replace(new RegExp(/#[a-zA-Z][a-zA-Z0-9_]+/g), "?");
|
|
1388
|
+
}
|
|
1389
|
+
return value;
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
// src/restura/sql/filterPsqlParser.ts
|
|
1394
|
+
var import_pegjs = __toESM(require("pegjs"));
|
|
1395
|
+
var filterSqlGrammar = `
|
|
1396
|
+
start = expressionList
|
|
1397
|
+
|
|
1398
|
+
expressionList =
|
|
1399
|
+
leftExpression:expression operator:operator rightExpression:expressionList
|
|
1400
|
+
{ return \`\${leftExpression} \${operator} \${rightExpression}\`;}
|
|
1401
|
+
/ expression
|
|
1402
|
+
|
|
1403
|
+
expression =
|
|
1404
|
+
negate:negate?"(" "column:" column:column ","? value:value? ","? type:type? ")"
|
|
1405
|
+
{return \`\${negate? "!" : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
|
|
1406
|
+
/
|
|
1407
|
+
negate:negate?"("expression:expressionList")" { return \`\${negate? "!" : ""}(\${expression})\`; }
|
|
1408
|
+
|
|
1409
|
+
negate = "!"
|
|
1410
|
+
|
|
1411
|
+
operator = "and"i / "or"i
|
|
1412
|
+
|
|
1413
|
+
|
|
1414
|
+
column = left:text "." right:text { return \`\${format.ident(left)}.\${format.ident(right)}\`; }
|
|
1415
|
+
/
|
|
1416
|
+
text:text { return format.ident(text); }
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
text = text:[a-z0-9-_:@]i+ { return text.join("");}
|
|
1420
|
+
|
|
1421
|
+
type = "type:" type:typeString { return type; }
|
|
1422
|
+
typeString = text:"startsWith" { return function(column, value) { return \`\${column} ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
|
|
1423
|
+
text:"endsWith" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1424
|
+
text:"contains" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
|
|
1425
|
+
text:"exact" { return function(column, value) { return \`\${column} = '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1426
|
+
text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1427
|
+
text:"greaterThan" { return function(column, value) { return \`\${column} > '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1428
|
+
text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1429
|
+
text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1430
|
+
text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
|
|
1431
|
+
|
|
1432
|
+
value = "value:" value:text { return value; }
|
|
1433
|
+
|
|
1434
|
+
`;
|
|
1435
|
+
var filterPsqlParser = import_pegjs.default.generate(filterSqlGrammar, {
|
|
1436
|
+
format: "commonjs",
|
|
1437
|
+
dependencies: { format: "pg-format" }
|
|
1438
|
+
});
|
|
1439
|
+
var filterPsqlParser_default = filterPsqlParser;
|
|
1440
|
+
|
|
1441
|
+
// src/restura/sql/PsqlEngine.ts
|
|
1442
|
+
var PsqlEngine = class extends SqlEngine {
|
|
1443
|
+
constructor(psqlConnectionPool) {
|
|
1444
|
+
super();
|
|
1445
|
+
this.psqlConnectionPool = psqlConnectionPool;
|
|
1446
|
+
}
|
|
1447
|
+
async diffDatabaseToSchema(schema) {
|
|
1448
|
+
console.log(schema);
|
|
1449
|
+
return Promise.resolve("");
|
|
1450
|
+
}
|
|
1451
|
+
generateDatabaseSchemaFromSchema(schema) {
|
|
1452
|
+
console.log(schema);
|
|
1453
|
+
return "";
|
|
1454
|
+
}
|
|
1455
|
+
createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
|
|
1456
|
+
if (!item.subquery) return "";
|
|
1457
|
+
if (!import_core_utils5.ObjectUtils.isArrayWithData(
|
|
1458
|
+
item.subquery.properties.filter((nestedItem) => {
|
|
1459
|
+
return this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
|
|
1460
|
+
...routeData.joins,
|
|
1461
|
+
...item.subquery.joins
|
|
1462
|
+
]);
|
|
1463
|
+
})
|
|
1464
|
+
)) {
|
|
1465
|
+
return "'[]'";
|
|
1466
|
+
}
|
|
1467
|
+
return `COALESCE((
|
|
1468
|
+
SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
|
1469
|
+
${item.subquery.properties.map((nestedItem) => {
|
|
1470
|
+
if (!this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
|
|
1471
|
+
...routeData.joins,
|
|
1472
|
+
...item.subquery.joins
|
|
1473
|
+
])) {
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
if (nestedItem.subquery) {
|
|
1477
|
+
return `"${nestedItem.name}", ${this.createNestedSelect(
|
|
1478
|
+
// recursion
|
|
1479
|
+
req,
|
|
1480
|
+
schema,
|
|
1481
|
+
nestedItem,
|
|
1482
|
+
routeData,
|
|
1483
|
+
userRole,
|
|
1484
|
+
sqlParams
|
|
1485
|
+
)}`;
|
|
1486
|
+
}
|
|
1487
|
+
return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
|
|
1488
|
+
}).filter(Boolean).join(",")}
|
|
1489
|
+
))
|
|
1490
|
+
FROM
|
|
1491
|
+
"${item.subquery.table}"
|
|
1492
|
+
${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, userRole, sqlParams)}
|
|
1493
|
+
${this.generateWhereClause(req, item.subquery.where, routeData, sqlParams)}
|
|
1494
|
+
), '[]')`;
|
|
1495
|
+
}
|
|
1496
|
+
async executeCreateRequest(req, routeData, schema) {
|
|
1497
|
+
const sqlParams = [];
|
|
1498
|
+
const parameterObj = {};
|
|
1499
|
+
(routeData.assignments || []).forEach((assignment) => {
|
|
1500
|
+
parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
|
|
1501
|
+
});
|
|
1502
|
+
const query = insertObjectQuery(routeData.table, __spreadValues(__spreadValues({}, req.data), parameterObj));
|
|
1503
|
+
const createdItem = await this.psqlConnectionPool.queryOne(query, sqlParams, req.requesterDetails);
|
|
1504
|
+
const insertId = createdItem == null ? void 0 : createdItem.id;
|
|
1505
|
+
const whereData = [
|
|
1506
|
+
{
|
|
1507
|
+
tableName: routeData.table,
|
|
1508
|
+
value: insertId,
|
|
1509
|
+
columnName: "id",
|
|
1510
|
+
operator: "="
|
|
1511
|
+
}
|
|
1512
|
+
];
|
|
1513
|
+
req.data = { id: insertId };
|
|
1514
|
+
return this.executeGetRequest(req, __spreadProps(__spreadValues({}, routeData), { where: whereData }), schema);
|
|
1515
|
+
}
|
|
1516
|
+
async executeGetRequest(req, routeData, schema) {
|
|
1517
|
+
const DEFAULT_PAGED_PAGE_NUMBER = 0;
|
|
1518
|
+
const DEFAULT_PAGED_PER_PAGE_NUMBER = 25;
|
|
1519
|
+
const sqlParams = [];
|
|
1520
|
+
const userRole = req.requesterDetails.role;
|
|
1521
|
+
let sqlStatement = "";
|
|
1522
|
+
const selectColumns = [];
|
|
1523
|
+
routeData.response.forEach((item) => {
|
|
1524
|
+
if (item.subquery || this.doesRoleHavePermissionToColumn(userRole, schema, item, routeData.joins))
|
|
1525
|
+
selectColumns.push(item);
|
|
1526
|
+
});
|
|
1527
|
+
if (!selectColumns.length) throw new RsError("UNAUTHORIZED", `You do not have permission to access this data.`);
|
|
1528
|
+
let selectStatement = "SELECT \n";
|
|
1529
|
+
selectStatement += ` ${selectColumns.map((item) => {
|
|
1530
|
+
if (item.subquery) {
|
|
1531
|
+
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${item.name}`;
|
|
1532
|
+
}
|
|
1533
|
+
return `${escapeColumnName(item.selector)} AS ${escapeColumnName(item.name)}`;
|
|
1534
|
+
}).join(",\n ")}
|
|
1535
|
+
`;
|
|
1536
|
+
sqlStatement += `FROM "${routeData.table}"
|
|
1537
|
+
`;
|
|
1538
|
+
sqlStatement += this.generateJoinStatements(
|
|
955
1539
|
req,
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1540
|
+
routeData.joins,
|
|
1541
|
+
routeData.table,
|
|
1542
|
+
routeData,
|
|
1543
|
+
schema,
|
|
1544
|
+
userRole,
|
|
1545
|
+
sqlParams
|
|
1546
|
+
);
|
|
1547
|
+
sqlStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1548
|
+
let groupByOrderByStatement = this.generateGroupBy(routeData);
|
|
1549
|
+
groupByOrderByStatement += this.generateOrderBy(req, routeData);
|
|
1550
|
+
if (routeData.type === "ONE") {
|
|
1551
|
+
return this.psqlConnectionPool.queryOne(
|
|
1552
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
|
|
1553
|
+
sqlParams,
|
|
1554
|
+
req.requesterDetails
|
|
1555
|
+
);
|
|
1556
|
+
} else if (routeData.type === "ARRAY") {
|
|
1557
|
+
return this.psqlConnectionPool.runQuery(
|
|
1558
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
|
|
1559
|
+
sqlParams,
|
|
1560
|
+
req.requesterDetails
|
|
1561
|
+
);
|
|
1562
|
+
} else if (routeData.type === "PAGED") {
|
|
1563
|
+
const data = req.data;
|
|
1564
|
+
const pageResults = await this.psqlConnectionPool.runQuery(
|
|
1565
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement} LIMIT ? OFFSET ?;SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
|
|
1566
|
+
${sqlStatement};`,
|
|
1567
|
+
[
|
|
1568
|
+
...sqlParams,
|
|
1569
|
+
data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER,
|
|
1570
|
+
(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER,
|
|
1571
|
+
...sqlParams
|
|
1572
|
+
],
|
|
1573
|
+
req.requesterDetails
|
|
1574
|
+
);
|
|
1575
|
+
let total = 0;
|
|
1576
|
+
if (import_core_utils5.ObjectUtils.isArrayWithData(pageResults)) {
|
|
1577
|
+
total = pageResults[1][0].total;
|
|
962
1578
|
}
|
|
1579
|
+
return { data: pageResults[0], total };
|
|
1580
|
+
} else {
|
|
1581
|
+
throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
async executeUpdateRequest(req, routeData, schema) {
|
|
1585
|
+
const sqlParams = [];
|
|
1586
|
+
const _a = req.body, { id } = _a, bodyNoId = __objRest(_a, ["id"]);
|
|
1587
|
+
const table = schema.database.find((item) => {
|
|
1588
|
+
return item.name === routeData.table;
|
|
1589
|
+
});
|
|
1590
|
+
if (!table) throw new RsError("UNKNOWN_ERROR", "Unknown table.");
|
|
1591
|
+
if (table.columns.find((column) => column.name === "modifiedOn")) {
|
|
1592
|
+
bodyNoId.modifiedOn = (/* @__PURE__ */ new Date()).toISOString();
|
|
1593
|
+
}
|
|
1594
|
+
for (const assignment of routeData.assignments) {
|
|
1595
|
+
const column = table.columns.find((column2) => column2.name === assignment.name);
|
|
1596
|
+
if (!column) continue;
|
|
1597
|
+
const assignmentWithPrefix = escapeColumnName(`${routeData.table}.${assignment.name}`);
|
|
1598
|
+
if (SqlUtils.convertDatabaseTypeToTypescript(column.type) === "number")
|
|
1599
|
+
bodyNoId[assignmentWithPrefix] = Number(assignment.value);
|
|
1600
|
+
else bodyNoId[assignmentWithPrefix] = assignment.value;
|
|
1601
|
+
}
|
|
1602
|
+
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1603
|
+
const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
|
|
1604
|
+
await this.psqlConnectionPool.queryOne(query, [...sqlParams], req.requesterDetails);
|
|
1605
|
+
return this.executeGetRequest(req, routeData, schema);
|
|
1606
|
+
}
|
|
1607
|
+
async executeDeleteRequest(req, routeData, schema) {
|
|
1608
|
+
const sqlParams = [];
|
|
1609
|
+
const joinStatement = this.generateJoinStatements(
|
|
1610
|
+
req,
|
|
1611
|
+
routeData.joins,
|
|
1612
|
+
routeData.table,
|
|
1613
|
+
routeData,
|
|
1614
|
+
schema,
|
|
1615
|
+
req.requesterDetails.role,
|
|
1616
|
+
sqlParams
|
|
963
1617
|
);
|
|
964
|
-
|
|
965
|
-
}
|
|
1618
|
+
let deleteStatement = `DELETE
|
|
1619
|
+
FROM "${routeData.table}" ${joinStatement}`;
|
|
1620
|
+
deleteStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1621
|
+
deleteStatement += ";";
|
|
1622
|
+
await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
|
|
1623
|
+
return true;
|
|
1624
|
+
}
|
|
1625
|
+
generateJoinStatements(req, joins, baseTable, routeData, schema, userRole, sqlParams) {
|
|
1626
|
+
let joinStatements = "";
|
|
1627
|
+
joins.forEach((item) => {
|
|
1628
|
+
if (!this.doesRoleHavePermissionToTable(userRole, schema, item.table))
|
|
1629
|
+
throw new RsError("UNAUTHORIZED", "You do not have permission to access this table");
|
|
1630
|
+
if (item.custom) {
|
|
1631
|
+
const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
|
|
1632
|
+
joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)} ON ${customReplaced}
|
|
1633
|
+
`;
|
|
1634
|
+
} else {
|
|
1635
|
+
joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)}${item.alias ? `AS "${item.alias}"` : ""} ON "${baseTable}"."${item.localColumnName}" = ${escapeColumnName(item.alias ? item.alias : item.table)}.${escapeColumnName(
|
|
1636
|
+
item.foreignColumnName
|
|
1637
|
+
)}
|
|
1638
|
+
`;
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
return joinStatements;
|
|
1642
|
+
}
|
|
1643
|
+
generateGroupBy(routeData) {
|
|
1644
|
+
let groupBy = "";
|
|
1645
|
+
if (routeData.groupBy) {
|
|
1646
|
+
groupBy = `GROUP BY ${escapeColumnName(routeData.groupBy.tableName)}.${escapeColumnName(routeData.groupBy.columnName)}
|
|
1647
|
+
`;
|
|
1648
|
+
}
|
|
1649
|
+
return groupBy;
|
|
1650
|
+
}
|
|
1651
|
+
generateOrderBy(req, routeData) {
|
|
1652
|
+
let orderBy = "";
|
|
1653
|
+
const orderOptions = {
|
|
1654
|
+
ASC: "ASC",
|
|
1655
|
+
DESC: "DESC"
|
|
1656
|
+
};
|
|
1657
|
+
const data = req.data;
|
|
1658
|
+
if (routeData.type === "PAGED" && "sortBy" in data) {
|
|
1659
|
+
const sortOrder = orderOptions[data.sortOrder] || "ASC";
|
|
1660
|
+
orderBy = `ORDER BY ${escapeColumnName(data.sortBy)} ${sortOrder}
|
|
1661
|
+
`;
|
|
1662
|
+
} else if (routeData.orderBy) {
|
|
1663
|
+
const sortOrder = orderOptions[routeData.orderBy.order] || "ASC";
|
|
1664
|
+
orderBy = `ORDER BY ${escapeColumnName(routeData.orderBy.tableName)}.${escapeColumnName(routeData.orderBy.columnName)} ${sortOrder}
|
|
1665
|
+
`;
|
|
1666
|
+
}
|
|
1667
|
+
return orderBy;
|
|
1668
|
+
}
|
|
1669
|
+
generateWhereClause(req, where, routeData, sqlParams) {
|
|
1670
|
+
let whereClause = "";
|
|
1671
|
+
where.forEach((item, index) => {
|
|
1672
|
+
if (index === 0) whereClause = "WHERE ";
|
|
1673
|
+
if (item.custom) {
|
|
1674
|
+
whereClause += this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
if (item.operator === void 0 || item.value === void 0 || item.columnName === void 0 || item.tableName === void 0)
|
|
1678
|
+
throw new RsError(
|
|
1679
|
+
"SCHEMA_ERROR",
|
|
1680
|
+
`Invalid where clause in route ${routeData.name}, missing required fields if not custom`
|
|
1681
|
+
);
|
|
1682
|
+
let operator = item.operator;
|
|
1683
|
+
if (operator === "LIKE") {
|
|
1684
|
+
sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}%`;
|
|
1685
|
+
} else if (operator === "STARTS WITH") {
|
|
1686
|
+
operator = "LIKE";
|
|
1687
|
+
sqlParams[sqlParams.length - 1] = `${sqlParams[sqlParams.length - 1]}%`;
|
|
1688
|
+
} else if (operator === "ENDS WITH") {
|
|
1689
|
+
operator = "LIKE";
|
|
1690
|
+
sqlParams[sqlParams.length - 1] = `%${sqlParams[sqlParams.length - 1]}`;
|
|
1691
|
+
}
|
|
1692
|
+
const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
|
|
1693
|
+
const escapedValue = SQL`${replacedValue}`;
|
|
1694
|
+
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator} ${["IN", "NOT IN"].includes(operator) ? `(${escapedValue})` : escapedValue}
|
|
1695
|
+
`;
|
|
1696
|
+
});
|
|
1697
|
+
const data = req.data;
|
|
1698
|
+
if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
|
|
1699
|
+
let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1700
|
+
var _a;
|
|
1701
|
+
const requestParam = routeData.request.find((item) => {
|
|
1702
|
+
return item.name === value.replace("$", "");
|
|
1703
|
+
});
|
|
1704
|
+
if (!requestParam)
|
|
1705
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1706
|
+
return ((_a = data[requestParam.name]) == null ? void 0 : _a.toString()) || "";
|
|
1707
|
+
});
|
|
1708
|
+
statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
1709
|
+
var _a;
|
|
1710
|
+
const requestParam = routeData.request.find((item) => {
|
|
1711
|
+
return item.name === value.replace("#", "");
|
|
1712
|
+
});
|
|
1713
|
+
if (!requestParam)
|
|
1714
|
+
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1715
|
+
return ((_a = data[requestParam.name]) == null ? void 0 : _a.toString()) || "";
|
|
1716
|
+
});
|
|
1717
|
+
statement = filterPsqlParser_default.parse(statement);
|
|
1718
|
+
if (whereClause.startsWith("WHERE")) {
|
|
1719
|
+
whereClause += ` AND (${statement})
|
|
1720
|
+
`;
|
|
1721
|
+
} else {
|
|
1722
|
+
whereClause += `WHERE ${statement}
|
|
1723
|
+
`;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return whereClause;
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
|
|
1730
|
+
// src/restura/sql/PsqlPool.ts
|
|
1731
|
+
var import_pg = __toESM(require("pg"));
|
|
1732
|
+
var import_pg_format2 = __toESM(require("pg-format"));
|
|
1733
|
+
var { Pool } = import_pg.default;
|
|
1734
|
+
var PsqlPool = class {
|
|
1735
|
+
constructor(poolConfig) {
|
|
1736
|
+
this.poolConfig = poolConfig;
|
|
1737
|
+
this.pool = new Pool(poolConfig);
|
|
1738
|
+
this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
|
|
1739
|
+
logger.info("Connected to PostgreSQL database");
|
|
1740
|
+
}).catch((error) => {
|
|
1741
|
+
logger.error("Error connecting to database", error);
|
|
1742
|
+
process.exit(1);
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1746
|
+
async queryOne(query, options, requesterDetails) {
|
|
1747
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1748
|
+
this.logSqlStatement(formattedQuery, options, requesterDetails);
|
|
1749
|
+
try {
|
|
1750
|
+
const response = await this.pool.query(formattedQuery, options);
|
|
1751
|
+
if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
|
|
1752
|
+
else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
|
|
1753
|
+
return response.rows[0];
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
console.error(error, query, options);
|
|
1756
|
+
if (RsError.isRsError(error)) throw error;
|
|
1757
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1758
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1759
|
+
}
|
|
1760
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1764
|
+
async runQuery(query, options, requesterDetails) {
|
|
1765
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1766
|
+
this.logSqlStatement(formattedQuery, options, requesterDetails);
|
|
1767
|
+
const queryUpdated = query.replace(/[\t\n]/g, " ");
|
|
1768
|
+
console.log(queryUpdated, options);
|
|
1769
|
+
try {
|
|
1770
|
+
const response = await this.pool.query(formattedQuery, options);
|
|
1771
|
+
return response.rows;
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
console.error(error, query, options);
|
|
1774
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1775
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1776
|
+
}
|
|
1777
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
logSqlStatement(query, options, requesterDetails, prefix = "") {
|
|
1781
|
+
if (logger.level !== "silly") return;
|
|
1782
|
+
let sqlStatement = "";
|
|
1783
|
+
if (options.length === 0) {
|
|
1784
|
+
sqlStatement = query;
|
|
1785
|
+
} else {
|
|
1786
|
+
let stringIndex = 0;
|
|
1787
|
+
sqlStatement = query.replace(/\$\d+/g, () => {
|
|
1788
|
+
const value = options[stringIndex++];
|
|
1789
|
+
if (typeof value === "number") return value.toString();
|
|
1790
|
+
return import_pg_format2.default.literal(value);
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
let initiator = "Anonymous";
|
|
1794
|
+
if ("userId" in requesterDetails && requesterDetails.userId)
|
|
1795
|
+
initiator = `User Id (${requesterDetails.userId.toString()})`;
|
|
1796
|
+
if ("isSystemUser" in requesterDetails && requesterDetails.isSystemUser) initiator = "SYSTEM";
|
|
1797
|
+
logger.silly(`${prefix}query by ${initiator}, Query ->
|
|
1798
|
+
${sqlStatement}`);
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
966
1801
|
|
|
967
1802
|
// src/restura/restura.ts
|
|
1803
|
+
var { types } = import_pg2.default;
|
|
968
1804
|
var ResturaEngine = class {
|
|
969
1805
|
constructor() {
|
|
970
1806
|
this.publicEndpoints = {
|
|
@@ -975,15 +1811,18 @@ var ResturaEngine = class {
|
|
|
975
1811
|
DELETE: []
|
|
976
1812
|
};
|
|
977
1813
|
}
|
|
978
|
-
// private customTypeValidation!: ValidationDictionary;
|
|
979
1814
|
/**
|
|
980
1815
|
* Initializes the Restura engine with the provided Express application.
|
|
981
1816
|
*
|
|
982
1817
|
* @param app - The Express application instance to initialize with Restura.
|
|
983
1818
|
* @returns A promise that resolves when the initialization is complete.
|
|
984
1819
|
*/
|
|
985
|
-
async init(app, authenticationHandler) {
|
|
1820
|
+
async init(app, authenticationHandler, psqlConnectionPool) {
|
|
986
1821
|
this.resturaConfig = import_internal2.config.validate("restura", resturaConfigSchema);
|
|
1822
|
+
this.psqlConnectionPool = psqlConnectionPool;
|
|
1823
|
+
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool);
|
|
1824
|
+
setupPgReturnTypes();
|
|
1825
|
+
await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
|
|
987
1826
|
this.authenticationHandler = authenticationHandler;
|
|
988
1827
|
app.use((0, import_compression.default)());
|
|
989
1828
|
app.use(import_body_parser.default.json({ limit: "32mb" }));
|
|
@@ -991,7 +1830,7 @@ var ResturaEngine = class {
|
|
|
991
1830
|
app.use((0, import_cookie_parser.default)());
|
|
992
1831
|
app.disable("x-powered-by");
|
|
993
1832
|
app.use("/", addApiResponseFunctions);
|
|
994
|
-
app.use("/api/", authenticateUser);
|
|
1833
|
+
app.use("/api/", authenticateUser(this.authenticationHandler));
|
|
995
1834
|
app.use("/restura", this.resturaAuthentication);
|
|
996
1835
|
app.put(
|
|
997
1836
|
"/restura/v1/schema",
|
|
@@ -1007,7 +1846,7 @@ var ResturaEngine = class {
|
|
|
1007
1846
|
app.get("/restura/v1/schema/types", this.getSchemaAndTypes);
|
|
1008
1847
|
this.expressApp = app;
|
|
1009
1848
|
await this.reloadEndpoints();
|
|
1010
|
-
this.validateGeneratedTypesFolder();
|
|
1849
|
+
await this.validateGeneratedTypesFolder();
|
|
1011
1850
|
logger.info("Restura Engine Initialized");
|
|
1012
1851
|
}
|
|
1013
1852
|
/**
|
|
@@ -1046,7 +1885,7 @@ var ResturaEngine = class {
|
|
|
1046
1885
|
* @returns A promise that resolves when the API has been successfully generated and written to the output file.
|
|
1047
1886
|
*/
|
|
1048
1887
|
async generateApiFromSchema(outputFile, providedSchema) {
|
|
1049
|
-
|
|
1888
|
+
import_fs3.default.writeFileSync(
|
|
1050
1889
|
outputFile,
|
|
1051
1890
|
await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1052
1891
|
);
|
|
@@ -1059,7 +1898,7 @@ var ResturaEngine = class {
|
|
|
1059
1898
|
* @returns A promise that resolves when the model has been successfully written to the output file.
|
|
1060
1899
|
*/
|
|
1061
1900
|
async generateModelFromSchema(outputFile, providedSchema) {
|
|
1062
|
-
|
|
1901
|
+
import_fs3.default.writeFileSync(
|
|
1063
1902
|
outputFile,
|
|
1064
1903
|
await modelGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1065
1904
|
);
|
|
@@ -1071,12 +1910,12 @@ var ResturaEngine = class {
|
|
|
1071
1910
|
* @throws {Error} If the schema file is missing or the schema is not valid.
|
|
1072
1911
|
*/
|
|
1073
1912
|
async getLatestFileSystemSchema() {
|
|
1074
|
-
if (!
|
|
1913
|
+
if (!import_fs3.default.existsSync(this.resturaConfig.schemaFilePath)) {
|
|
1075
1914
|
logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
|
|
1076
1915
|
throw new Error("Missing restura schema file");
|
|
1077
1916
|
}
|
|
1078
|
-
const schemaFileData =
|
|
1079
|
-
const schema =
|
|
1917
|
+
const schemaFileData = import_fs3.default.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
|
|
1918
|
+
const schema = import_core_utils6.ObjectUtils.safeParse(schemaFileData);
|
|
1080
1919
|
const isValid = await isSchemaValid(schema);
|
|
1081
1920
|
if (!isValid) {
|
|
1082
1921
|
logger.error("Schema is not valid");
|
|
@@ -1096,9 +1935,9 @@ var ResturaEngine = class {
|
|
|
1096
1935
|
async getHashes(providedSchema) {
|
|
1097
1936
|
var _a, _b, _c, _d;
|
|
1098
1937
|
const schemaHash = await this.generateHashForSchema(providedSchema);
|
|
1099
|
-
const apiFile =
|
|
1938
|
+
const apiFile = import_fs3.default.readFileSync(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1100
1939
|
const apiCreatedSchemaHash = (_b = (_a = apiFile.toString().match(/\((.*)\)/)) == null ? void 0 : _a[1]) != null ? _b : "";
|
|
1101
|
-
const modelFile =
|
|
1940
|
+
const modelFile = import_fs3.default.readFileSync(import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1102
1941
|
const modelCreatedSchemaHash = (_d = (_c = modelFile.toString().match(/\((.*)\)/)) == null ? void 0 : _c[1]) != null ? _d : "";
|
|
1103
1942
|
return {
|
|
1104
1943
|
schemaHash,
|
|
@@ -1108,6 +1947,7 @@ var ResturaEngine = class {
|
|
|
1108
1947
|
}
|
|
1109
1948
|
async reloadEndpoints() {
|
|
1110
1949
|
this.schema = await this.getLatestFileSystemSchema();
|
|
1950
|
+
this.customTypeValidation = customTypeValidationGenerator(this.schema);
|
|
1111
1951
|
this.resturaRouter = express.Router();
|
|
1112
1952
|
this.resetPublicEndpoints();
|
|
1113
1953
|
let routeCount = 0;
|
|
@@ -1133,27 +1973,27 @@ var ResturaEngine = class {
|
|
|
1133
1973
|
logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
|
|
1134
1974
|
}
|
|
1135
1975
|
async validateGeneratedTypesFolder() {
|
|
1136
|
-
if (!
|
|
1137
|
-
|
|
1976
|
+
if (!import_fs3.default.existsSync(this.resturaConfig.generatedTypesPath)) {
|
|
1977
|
+
import_fs3.default.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
|
|
1138
1978
|
}
|
|
1139
|
-
const hasApiFile =
|
|
1140
|
-
const hasModelsFile =
|
|
1979
|
+
const hasApiFile = import_fs3.default.existsSync(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1980
|
+
const hasModelsFile = import_fs3.default.existsSync(import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1141
1981
|
if (!hasApiFile) {
|
|
1142
|
-
await this.generateApiFromSchema(
|
|
1982
|
+
await this.generateApiFromSchema(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1143
1983
|
}
|
|
1144
1984
|
if (!hasModelsFile) {
|
|
1145
1985
|
await this.generateModelFromSchema(
|
|
1146
|
-
|
|
1986
|
+
import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1147
1987
|
this.schema
|
|
1148
1988
|
);
|
|
1149
1989
|
}
|
|
1150
1990
|
const hashes = await this.getHashes(this.schema);
|
|
1151
1991
|
if (hashes.schemaHash !== hashes.apiCreatedSchemaHash) {
|
|
1152
|
-
await this.generateApiFromSchema(
|
|
1992
|
+
await this.generateApiFromSchema(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1153
1993
|
}
|
|
1154
1994
|
if (hashes.schemaHash !== hashes.modelCreatedSchemaHash) {
|
|
1155
1995
|
await this.generateModelFromSchema(
|
|
1156
|
-
|
|
1996
|
+
import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1157
1997
|
this.schema
|
|
1158
1998
|
);
|
|
1159
1999
|
}
|
|
@@ -1183,9 +2023,9 @@ var ResturaEngine = class {
|
|
|
1183
2023
|
}
|
|
1184
2024
|
}
|
|
1185
2025
|
async updateTypes() {
|
|
1186
|
-
await this.generateApiFromSchema(
|
|
2026
|
+
await this.generateApiFromSchema(import_path3.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1187
2027
|
await this.generateModelFromSchema(
|
|
1188
|
-
|
|
2028
|
+
import_path3.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1189
2029
|
this.schema
|
|
1190
2030
|
);
|
|
1191
2031
|
}
|
|
@@ -1207,6 +2047,19 @@ var ResturaEngine = class {
|
|
|
1207
2047
|
try {
|
|
1208
2048
|
const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
|
|
1209
2049
|
this.validateAuthorization(req, routeData);
|
|
2050
|
+
validateRequestParams(req, routeData, this.customTypeValidation);
|
|
2051
|
+
if (this.isCustomRoute(routeData)) {
|
|
2052
|
+
await this.runCustomRouteLogic(req, res, routeData);
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
const data = await this.psqlEngine.runQueryForRoute(
|
|
2056
|
+
req,
|
|
2057
|
+
routeData,
|
|
2058
|
+
this.schema
|
|
2059
|
+
);
|
|
2060
|
+
this.responseValidator.validateResponseParams(data, req.baseUrl, routeData);
|
|
2061
|
+
if (routeData.type === "PAGED") res.sendNoWrap(data);
|
|
2062
|
+
else res.sendData(data);
|
|
1210
2063
|
} catch (e) {
|
|
1211
2064
|
next(e);
|
|
1212
2065
|
}
|
|
@@ -1214,34 +2067,25 @@ var ResturaEngine = class {
|
|
|
1214
2067
|
isCustomRoute(route) {
|
|
1215
2068
|
return route.type === "CUSTOM_ONE" || route.type === "CUSTOM_ARRAY" || route.type === "CUSTOM_PAGED";
|
|
1216
2069
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
// // @ts-expect-error - Here we are dynamically calling the function from a custom class, not sure how to typescript this
|
|
1237
|
-
// const customFunction = customApi[functionName] as (
|
|
1238
|
-
// req: RsRequest<T>,
|
|
1239
|
-
// res: RsResponse<T>,
|
|
1240
|
-
// routeData: RouteData
|
|
1241
|
-
// ) => Promise<void>;
|
|
1242
|
-
// if (!customFunction) throw new RsError('NOT_FOUND', `API path ${routeData.path} not implemented`);
|
|
1243
|
-
// await customFunction(req, res, routeData);
|
|
1244
|
-
// }
|
|
2070
|
+
async runCustomRouteLogic(req, res, routeData) {
|
|
2071
|
+
const version = req.baseUrl.split("/")[2];
|
|
2072
|
+
let domain = routeData.path.split("/")[1];
|
|
2073
|
+
domain = domain.split("-").reduce((acc, value, index) => {
|
|
2074
|
+
if (index === 0) acc = value;
|
|
2075
|
+
else acc += import_core_utils6.StringUtils.capitalizeFirst(value);
|
|
2076
|
+
return acc;
|
|
2077
|
+
}, "");
|
|
2078
|
+
const customApiName = `${import_core_utils6.StringUtils.capitalizeFirst(domain)}Api${import_core_utils6.StringUtils.capitalizeFirst(version)}`;
|
|
2079
|
+
const customApi = customApiFactory_default.getCustomApi(customApiName);
|
|
2080
|
+
if (!customApi) throw new RsError("NOT_FOUND", `API domain ${domain}-${version} not found`);
|
|
2081
|
+
const functionName = `${routeData.method.toLowerCase()}${routeData.path.replace(new RegExp("-", "g"), "/").split("/").reduce((acc, cur) => {
|
|
2082
|
+
if (cur === "") return acc;
|
|
2083
|
+
return acc + import_core_utils6.StringUtils.capitalizeFirst(cur);
|
|
2084
|
+
}, "")}`;
|
|
2085
|
+
const customFunction = customApi[functionName];
|
|
2086
|
+
if (!customFunction) throw new RsError("NOT_FOUND", `API path ${routeData.path} not implemented`);
|
|
2087
|
+
await customFunction(req, res, routeData);
|
|
2088
|
+
}
|
|
1245
2089
|
async generateHashForSchema(providedSchema) {
|
|
1246
2090
|
const schemaPrettyStr = await prettier3.format(JSON.stringify(providedSchema), __spreadValues({
|
|
1247
2091
|
parser: "json"
|
|
@@ -1266,7 +2110,7 @@ var ResturaEngine = class {
|
|
|
1266
2110
|
printWidth: 120,
|
|
1267
2111
|
singleQuote: true
|
|
1268
2112
|
}));
|
|
1269
|
-
|
|
2113
|
+
import_fs3.default.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
|
|
1270
2114
|
}
|
|
1271
2115
|
resetPublicEndpoints() {
|
|
1272
2116
|
this.publicEndpoints = {
|
|
@@ -1283,13 +2127,13 @@ var ResturaEngine = class {
|
|
|
1283
2127
|
if (!routeData.roles.includes(role))
|
|
1284
2128
|
throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
|
|
1285
2129
|
}
|
|
1286
|
-
getRouteData(method, baseUrl,
|
|
2130
|
+
getRouteData(method, baseUrl, path4) {
|
|
1287
2131
|
const endpoint = this.schema.endpoints.find((item) => {
|
|
1288
2132
|
return item.baseUrl === baseUrl;
|
|
1289
2133
|
});
|
|
1290
2134
|
if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
|
|
1291
2135
|
const route = endpoint.routes.find((item) => {
|
|
1292
|
-
return item.method === method && item.path ===
|
|
2136
|
+
return item.method === method && item.path === path4;
|
|
1293
2137
|
});
|
|
1294
2138
|
if (!route) throw new RsError("NOT_FOUND", "Route not found");
|
|
1295
2139
|
return route;
|
|
@@ -1316,10 +2160,33 @@ __decorateClass([
|
|
|
1316
2160
|
__decorateClass([
|
|
1317
2161
|
boundMethod
|
|
1318
2162
|
], ResturaEngine.prototype, "isCustomRoute", 1);
|
|
2163
|
+
__decorateClass([
|
|
2164
|
+
boundMethod
|
|
2165
|
+
], ResturaEngine.prototype, "runCustomRouteLogic", 1);
|
|
2166
|
+
var setupPgReturnTypes = () => {
|
|
2167
|
+
const TIMESTAMPTZ_OID = 1184;
|
|
2168
|
+
types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
|
|
2169
|
+
return val === null ? null : new Date(val).toISOString();
|
|
2170
|
+
});
|
|
2171
|
+
const BIGINT_OID = 20;
|
|
2172
|
+
types.setTypeParser(BIGINT_OID, (val) => {
|
|
2173
|
+
return val === null ? null : Number(val);
|
|
2174
|
+
});
|
|
2175
|
+
};
|
|
2176
|
+
setupPgReturnTypes();
|
|
1319
2177
|
var restura = new ResturaEngine();
|
|
1320
2178
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1321
2179
|
0 && (module.exports = {
|
|
2180
|
+
HtmlStatusCodes,
|
|
2181
|
+
PsqlPool,
|
|
2182
|
+
RsError,
|
|
2183
|
+
SQL,
|
|
2184
|
+
escapeColumnName,
|
|
2185
|
+
insertObjectQuery,
|
|
2186
|
+
isValueNumber,
|
|
1322
2187
|
logger,
|
|
1323
|
-
|
|
2188
|
+
questionMarksToOrderedParams,
|
|
2189
|
+
restura,
|
|
2190
|
+
updateObjectQuery
|
|
1324
2191
|
});
|
|
1325
2192
|
//# sourceMappingURL=index.js.map
|