@restura/core 0.2.1 → 1.0.0-beta.0
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 +517 -472
- package/dist/index.d.ts +517 -472
- package/dist/index.js +380 -347
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +249 -217
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -14
package/dist/index.mjs
CHANGED
|
@@ -1,35 +1,5 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
-
var __defProps = Object.defineProperties;
|
|
3
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
-
var __spreadValues = (a, b) => {
|
|
10
|
-
for (var prop in b || (b = {}))
|
|
11
|
-
if (__hasOwnProp.call(b, prop))
|
|
12
|
-
__defNormalProp(a, prop, b[prop]);
|
|
13
|
-
if (__getOwnPropSymbols)
|
|
14
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
15
|
-
if (__propIsEnum.call(b, prop))
|
|
16
|
-
__defNormalProp(a, prop, b[prop]);
|
|
17
|
-
}
|
|
18
|
-
return a;
|
|
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
|
-
};
|
|
33
3
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
34
4
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
35
5
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
@@ -45,13 +15,12 @@ import winston from "winston";
|
|
|
45
15
|
import { format } from "logform";
|
|
46
16
|
|
|
47
17
|
// src/logger/loggerConfigSchema.ts
|
|
48
|
-
import { z } from "zod";
|
|
18
|
+
import { z } from "zod/v4";
|
|
49
19
|
var loggerConfigSchema = z.object({
|
|
50
20
|
level: z.enum(["info", "warn", "error", "debug", "silly"]).default("info")
|
|
51
21
|
});
|
|
52
22
|
|
|
53
23
|
// src/logger/logger.ts
|
|
54
|
-
var loggerConfig = config.validate("logger", loggerConfigSchema);
|
|
55
24
|
var consoleFormat = format.combine(
|
|
56
25
|
format.timestamp({
|
|
57
26
|
format: "YYYY-MM-DD HH:mm:ss.sss"
|
|
@@ -64,7 +33,7 @@ var consoleFormat = format.combine(
|
|
|
64
33
|
})
|
|
65
34
|
);
|
|
66
35
|
var logger = winston.createLogger({
|
|
67
|
-
level:
|
|
36
|
+
level: "info",
|
|
68
37
|
format: format.combine(
|
|
69
38
|
format.timestamp({
|
|
70
39
|
format: "YYYY-MM-DD HH:mm:ss.sss"
|
|
@@ -72,29 +41,24 @@ var logger = winston.createLogger({
|
|
|
72
41
|
format.errors({ stack: true }),
|
|
73
42
|
format.json()
|
|
74
43
|
),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// new winston.transports.File({ filename: 'combined.log' }),
|
|
84
|
-
new winston.transports.Console({ format: consoleFormat })
|
|
85
|
-
]
|
|
44
|
+
transports: [new winston.transports.Console({ format: consoleFormat })]
|
|
45
|
+
});
|
|
46
|
+
config.validate("logger", loggerConfigSchema).then((loggerConfig) => {
|
|
47
|
+
if (loggerConfig.level) {
|
|
48
|
+
logger.level = loggerConfig.level;
|
|
49
|
+
}
|
|
50
|
+
}).catch((error) => {
|
|
51
|
+
logger.error("Failed to load logger config:", error);
|
|
86
52
|
});
|
|
87
53
|
|
|
88
54
|
// src/restura/eventManager.ts
|
|
89
55
|
import Bluebird from "bluebird";
|
|
90
56
|
var EventManager = class {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
};
|
|
97
|
-
}
|
|
57
|
+
actionHandlers = {
|
|
58
|
+
DATABASE_ROW_DELETE: [],
|
|
59
|
+
DATABASE_ROW_INSERT: [],
|
|
60
|
+
DATABASE_COLUMN_UPDATE: []
|
|
61
|
+
};
|
|
98
62
|
addRowInsertHandler(onInsert, filter) {
|
|
99
63
|
this.actionHandlers.DATABASE_ROW_INSERT.push({
|
|
100
64
|
callback: onInsert,
|
|
@@ -291,6 +255,10 @@ var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
|
|
|
291
255
|
return HtmlStatusCodes2;
|
|
292
256
|
})(HtmlStatusCodes || {});
|
|
293
257
|
var RsError = class _RsError {
|
|
258
|
+
err;
|
|
259
|
+
msg;
|
|
260
|
+
status;
|
|
261
|
+
stack;
|
|
294
262
|
constructor(errCode, message) {
|
|
295
263
|
this.err = errCode;
|
|
296
264
|
this.msg = message || "";
|
|
@@ -443,9 +411,7 @@ import fs from "fs";
|
|
|
443
411
|
import path from "path";
|
|
444
412
|
import { FileUtils } from "@restura/internal";
|
|
445
413
|
var CustomApiFactory = class {
|
|
446
|
-
|
|
447
|
-
this.customApis = {};
|
|
448
|
-
}
|
|
414
|
+
customApis = {};
|
|
449
415
|
async loadApiFiles(baseFolderPath) {
|
|
450
416
|
const apiVersions = ["v1"];
|
|
451
417
|
for (const apiVersion of apiVersions) {
|
|
@@ -459,11 +425,10 @@ var CustomApiFactory = class {
|
|
|
459
425
|
return this.customApis[customApiName];
|
|
460
426
|
}
|
|
461
427
|
async addDirectory(directoryPath, apiVersion) {
|
|
462
|
-
var _a2;
|
|
463
428
|
const entries = await fs.promises.readdir(directoryPath, {
|
|
464
429
|
withFileTypes: true
|
|
465
430
|
});
|
|
466
|
-
const isTsx2 =
|
|
431
|
+
const isTsx2 = process.argv[1]?.endsWith(".ts");
|
|
467
432
|
const isTsNode2 = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
|
|
468
433
|
const extension = isTsx2 || isTsNode2 ? "ts" : "js";
|
|
469
434
|
const shouldEndWith = `.api.${apiVersion}.${extension}`;
|
|
@@ -528,6 +493,8 @@ var SqlUtils = class _SqlUtils {
|
|
|
528
493
|
|
|
529
494
|
// src/restura/validators/ResponseValidator.ts
|
|
530
495
|
var ResponseValidator = class _ResponseValidator {
|
|
496
|
+
rootMap;
|
|
497
|
+
database;
|
|
531
498
|
constructor(schema) {
|
|
532
499
|
this.database = schema.database;
|
|
533
500
|
this.rootMap = {};
|
|
@@ -603,7 +570,7 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
603
570
|
if (path5.length === 0 || path5.length > 2 || path5[0] === "") return { validator: "any", isOptional: false };
|
|
604
571
|
const tableName = path5.length == 2 ? path5[0] : name, columnName = path5.length == 2 ? path5[1] : path5[0];
|
|
605
572
|
const table = this.database.find((t) => t.name == tableName);
|
|
606
|
-
const column = table
|
|
573
|
+
const column = table?.columns.find((c) => c.name == columnName);
|
|
607
574
|
if (!table || !column) return { validator: "any", isOptional: false };
|
|
608
575
|
let validator = SqlUtils.convertDatabaseTypeToTypescript(
|
|
609
576
|
column.type,
|
|
@@ -687,10 +654,12 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
687
654
|
var ApiTree = class _ApiTree {
|
|
688
655
|
constructor(namespace, database) {
|
|
689
656
|
this.database = database;
|
|
690
|
-
this.data = [];
|
|
691
657
|
this.namespace = namespace;
|
|
692
658
|
this.children = /* @__PURE__ */ new Map();
|
|
693
659
|
}
|
|
660
|
+
namespace;
|
|
661
|
+
data = [];
|
|
662
|
+
children;
|
|
694
663
|
static createRootNode(database) {
|
|
695
664
|
return new _ApiTree(null, database);
|
|
696
665
|
}
|
|
@@ -834,7 +803,7 @@ var ApiTree = class _ApiTree {
|
|
|
834
803
|
tableName = tableAliasSplit[1];
|
|
835
804
|
table = this.database.find((t) => t.name == tableName);
|
|
836
805
|
}
|
|
837
|
-
const column = table
|
|
806
|
+
const column = table?.columns.find((c) => c.name == columnName);
|
|
838
807
|
if (!table || !column) return { responseType: "any", isNullable: false };
|
|
839
808
|
return {
|
|
840
809
|
responseType: SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value),
|
|
@@ -865,16 +834,17 @@ function apiGenerator(schema) {
|
|
|
865
834
|
${schema.customTypes.join("\n")}
|
|
866
835
|
}`;
|
|
867
836
|
}
|
|
868
|
-
return prettier.format(apiString,
|
|
869
|
-
parser: "typescript"
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
837
|
+
return prettier.format(apiString, {
|
|
838
|
+
parser: "typescript",
|
|
839
|
+
...{
|
|
840
|
+
trailingComma: "none",
|
|
841
|
+
tabWidth: 4,
|
|
842
|
+
useTabs: true,
|
|
843
|
+
endOfLine: "lf",
|
|
844
|
+
printWidth: 120,
|
|
845
|
+
singleQuote: true
|
|
846
|
+
}
|
|
847
|
+
});
|
|
878
848
|
}
|
|
879
849
|
|
|
880
850
|
// src/restura/generators/customTypeValidationGenerator.ts
|
|
@@ -885,7 +855,7 @@ import * as TJS from "typescript-json-schema";
|
|
|
885
855
|
function customTypeValidationGenerator(currentSchema) {
|
|
886
856
|
const schemaObject = {};
|
|
887
857
|
const customInterfaceNames = currentSchema.customTypes.map((customType) => {
|
|
888
|
-
const matches = customType.match(
|
|
858
|
+
const matches = customType.match(/(?<=interface\s)(\w+)|(?<=type\s)(\w+)/g);
|
|
889
859
|
if (matches && matches.length > 0) return matches[0];
|
|
890
860
|
return "";
|
|
891
861
|
}).filter(Boolean);
|
|
@@ -929,16 +899,17 @@ function modelGenerator(schema) {
|
|
|
929
899
|
modelString += convertTable(table);
|
|
930
900
|
}
|
|
931
901
|
modelString += `}`;
|
|
932
|
-
return prettier2.format(modelString,
|
|
933
|
-
parser: "typescript"
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
902
|
+
return prettier2.format(modelString, {
|
|
903
|
+
parser: "typescript",
|
|
904
|
+
...{
|
|
905
|
+
trailingComma: "none",
|
|
906
|
+
tabWidth: 4,
|
|
907
|
+
useTabs: true,
|
|
908
|
+
endOfLine: "lf",
|
|
909
|
+
printWidth: 120,
|
|
910
|
+
singleQuote: true
|
|
911
|
+
}
|
|
912
|
+
});
|
|
942
913
|
}
|
|
943
914
|
function convertTable(table) {
|
|
944
915
|
let modelString = ` export interface ${StringUtils2.capitalizeFirst(table.name)} {
|
|
@@ -990,20 +961,21 @@ function addApiResponseFunctions(req, res, next) {
|
|
|
990
961
|
htmlStatusCode = 500;
|
|
991
962
|
}
|
|
992
963
|
}
|
|
993
|
-
const errorData =
|
|
964
|
+
const errorData = {
|
|
994
965
|
err: shortError,
|
|
995
|
-
msg
|
|
996
|
-
|
|
966
|
+
msg,
|
|
967
|
+
...restura.resturaConfig.sendErrorStackTrace && stack ? { stack } : {}
|
|
968
|
+
};
|
|
997
969
|
res.status(htmlStatusCode).send(errorData);
|
|
998
970
|
};
|
|
999
971
|
next();
|
|
1000
972
|
}
|
|
1001
973
|
|
|
1002
|
-
// src/restura/middleware/
|
|
1003
|
-
function
|
|
974
|
+
// src/restura/middleware/authenticateRequester.ts
|
|
975
|
+
function authenticateRequester(applicationAuthenticateHandler) {
|
|
1004
976
|
return (req, res, next) => {
|
|
1005
|
-
applicationAuthenticateHandler(req, res, (
|
|
1006
|
-
req.requesterDetails =
|
|
977
|
+
applicationAuthenticateHandler(req, res, (authenticatedRequesterDetails) => {
|
|
978
|
+
req.requesterDetails = { host: req.hostname, ipAddress: req.ip || "", ...authenticatedRequesterDetails };
|
|
1007
979
|
next();
|
|
1008
980
|
});
|
|
1009
981
|
};
|
|
@@ -1058,22 +1030,7 @@ var groupBySchema = z3.object({
|
|
|
1058
1030
|
var whereDataSchema = z3.object({
|
|
1059
1031
|
tableName: z3.string().optional(),
|
|
1060
1032
|
columnName: z3.string().optional(),
|
|
1061
|
-
operator: z3.enum([
|
|
1062
|
-
"=",
|
|
1063
|
-
"<",
|
|
1064
|
-
">",
|
|
1065
|
-
"<=",
|
|
1066
|
-
">=",
|
|
1067
|
-
"!=",
|
|
1068
|
-
"LIKE",
|
|
1069
|
-
"NOT LIKE",
|
|
1070
|
-
"IN",
|
|
1071
|
-
"NOT IN",
|
|
1072
|
-
"STARTS WITH",
|
|
1073
|
-
"ENDS WITH",
|
|
1074
|
-
"IS",
|
|
1075
|
-
"IS NOT"
|
|
1076
|
-
]).optional(),
|
|
1033
|
+
operator: z3.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH", "IS", "IS NOT"]).optional(),
|
|
1077
1034
|
value: z3.string().or(z3.number()).optional(),
|
|
1078
1035
|
custom: z3.string().optional(),
|
|
1079
1036
|
conjunction: z3.enum(["AND", "OR"]).optional()
|
|
@@ -1109,13 +1066,15 @@ var responseDataSchema = z3.object({
|
|
|
1109
1066
|
orderBy: orderBySchema.optional()
|
|
1110
1067
|
}).optional(),
|
|
1111
1068
|
type: z3.string().optional()
|
|
1069
|
+
// Type allows you to override the type of the response, used in custom selectors
|
|
1112
1070
|
}).strict();
|
|
1113
1071
|
var routeDataBaseSchema = z3.object({
|
|
1114
1072
|
method: z3.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
1115
1073
|
name: z3.string(),
|
|
1116
1074
|
description: z3.string(),
|
|
1117
1075
|
path: z3.string(),
|
|
1118
|
-
roles: z3.array(z3.string())
|
|
1076
|
+
roles: z3.array(z3.string()),
|
|
1077
|
+
scopes: z3.array(z3.string())
|
|
1119
1078
|
}).strict();
|
|
1120
1079
|
var standardRouteSchema = routeDataBaseSchema.extend({
|
|
1121
1080
|
type: z3.enum(["ONE", "ARRAY", "PAGED"]),
|
|
@@ -1255,6 +1214,7 @@ var columnDataSchema = z3.object({
|
|
|
1255
1214
|
]),
|
|
1256
1215
|
isNullable: z3.boolean(),
|
|
1257
1216
|
roles: z3.array(z3.string()),
|
|
1217
|
+
scopes: z3.array(z3.string()),
|
|
1258
1218
|
comment: z3.string().optional(),
|
|
1259
1219
|
default: z3.string().optional(),
|
|
1260
1220
|
value: z3.string().optional(),
|
|
@@ -1301,6 +1261,7 @@ var tableDataSchema = z3.object({
|
|
|
1301
1261
|
foreignKeys: z3.array(foreignKeyDataSchema),
|
|
1302
1262
|
checkConstraints: z3.array(checkConstraintDataSchema),
|
|
1303
1263
|
roles: z3.array(z3.string()),
|
|
1264
|
+
scopes: z3.array(z3.string()),
|
|
1304
1265
|
notify: z3.union([z3.literal("ALL"), z3.array(z3.string())]).optional()
|
|
1305
1266
|
}).strict();
|
|
1306
1267
|
var endpointDataSchema = z3.object({
|
|
@@ -1314,6 +1275,7 @@ var resturaSchema = z3.object({
|
|
|
1314
1275
|
endpoints: z3.array(endpointDataSchema),
|
|
1315
1276
|
globalParams: z3.array(z3.string()),
|
|
1316
1277
|
roles: z3.array(z3.string()),
|
|
1278
|
+
scopes: z3.array(z3.string()),
|
|
1317
1279
|
customTypes: z3.array(z3.string())
|
|
1318
1280
|
}).strict();
|
|
1319
1281
|
async function isSchemaValid(schemaToCheck) {
|
|
@@ -1411,7 +1373,7 @@ function isValidType(type, requestValue) {
|
|
|
1411
1373
|
try {
|
|
1412
1374
|
expectValidType(type, requestValue);
|
|
1413
1375
|
return true;
|
|
1414
|
-
} catch
|
|
1376
|
+
} catch {
|
|
1415
1377
|
return false;
|
|
1416
1378
|
}
|
|
1417
1379
|
}
|
|
@@ -1447,7 +1409,7 @@ function performTypeCheck(requestValue, validator, requestParamName) {
|
|
|
1447
1409
|
}
|
|
1448
1410
|
try {
|
|
1449
1411
|
validatorDataSchemeValue.parse(validator.value);
|
|
1450
|
-
} catch
|
|
1412
|
+
} catch {
|
|
1451
1413
|
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
|
|
1452
1414
|
}
|
|
1453
1415
|
}
|
|
@@ -1538,9 +1500,8 @@ async function schemaValidation(req, res, next) {
|
|
|
1538
1500
|
}
|
|
1539
1501
|
|
|
1540
1502
|
// src/restura/schemas/resturaConfigSchema.ts
|
|
1541
|
-
import { z as z5 } from "zod";
|
|
1542
|
-
var
|
|
1543
|
-
var isTsx = (_a = process.argv[1]) == null ? void 0 : _a.endsWith(".ts");
|
|
1503
|
+
import { z as z5 } from "zod/v4";
|
|
1504
|
+
var isTsx = process.argv[1]?.endsWith(".ts");
|
|
1544
1505
|
var isTsNode = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
|
|
1545
1506
|
var customApiFolderPath = isTsx || isTsNode ? "/src/api" : "/dist/api";
|
|
1546
1507
|
var resturaConfigSchema = z5.object({
|
|
@@ -1549,7 +1510,8 @@ var resturaConfigSchema = z5.object({
|
|
|
1549
1510
|
schemaFilePath: z5.string().default(process.cwd() + "/restura.schema.json"),
|
|
1550
1511
|
customApiFolderPath: z5.string().default(process.cwd() + customApiFolderPath),
|
|
1551
1512
|
generatedTypesPath: z5.string().default(process.cwd() + "/src/@types"),
|
|
1552
|
-
fileTempCachePath: z5.string().optional()
|
|
1513
|
+
fileTempCachePath: z5.string().optional(),
|
|
1514
|
+
scratchDatabaseSuffix: z5.string().optional()
|
|
1553
1515
|
});
|
|
1554
1516
|
|
|
1555
1517
|
// src/restura/sql/PsqlEngine.ts
|
|
@@ -1635,13 +1597,14 @@ function SQL(strings, ...values) {
|
|
|
1635
1597
|
|
|
1636
1598
|
// src/restura/sql/PsqlConnection.ts
|
|
1637
1599
|
var PsqlConnection = class {
|
|
1600
|
+
instanceId;
|
|
1638
1601
|
constructor(instanceId) {
|
|
1639
1602
|
this.instanceId = instanceId || crypto.randomUUID();
|
|
1640
1603
|
}
|
|
1641
1604
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1642
1605
|
async queryOne(query, options, requesterDetails) {
|
|
1643
1606
|
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1644
|
-
const meta =
|
|
1607
|
+
const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
|
|
1645
1608
|
this.logSqlStatement(formattedQuery, options, meta);
|
|
1646
1609
|
const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
|
|
1647
1610
|
`;
|
|
@@ -1654,7 +1617,7 @@ var PsqlConnection = class {
|
|
|
1654
1617
|
return response.rows[0];
|
|
1655
1618
|
} catch (error) {
|
|
1656
1619
|
if (RsError.isRsError(error)) throw error;
|
|
1657
|
-
if (
|
|
1620
|
+
if (error?.routine === "_bt_check_unique") {
|
|
1658
1621
|
throw new RsError("DUPLICATE", error.message);
|
|
1659
1622
|
}
|
|
1660
1623
|
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
@@ -1663,7 +1626,7 @@ var PsqlConnection = class {
|
|
|
1663
1626
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1664
1627
|
async runQuery(query, options, requesterDetails) {
|
|
1665
1628
|
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1666
|
-
const meta =
|
|
1629
|
+
const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
|
|
1667
1630
|
this.logSqlStatement(formattedQuery, options, meta);
|
|
1668
1631
|
const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
|
|
1669
1632
|
`;
|
|
@@ -1673,7 +1636,7 @@ var PsqlConnection = class {
|
|
|
1673
1636
|
this.logQueryDuration(startTime);
|
|
1674
1637
|
return response.rows;
|
|
1675
1638
|
} catch (error) {
|
|
1676
|
-
if (
|
|
1639
|
+
if (error?.routine === "_bt_check_unique") {
|
|
1677
1640
|
throw new RsError("DUPLICATE", error.message);
|
|
1678
1641
|
}
|
|
1679
1642
|
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
@@ -1723,13 +1686,20 @@ var PsqlPool = class extends PsqlConnection {
|
|
|
1723
1686
|
super();
|
|
1724
1687
|
this.poolConfig = poolConfig;
|
|
1725
1688
|
this.pool = new Pool(poolConfig);
|
|
1726
|
-
this.queryOne("SELECT NOW();", [], {
|
|
1689
|
+
this.queryOne("SELECT NOW();", [], {
|
|
1690
|
+
isSystemUser: true,
|
|
1691
|
+
role: "",
|
|
1692
|
+
host: "localhost",
|
|
1693
|
+
ipAddress: "",
|
|
1694
|
+
scopes: []
|
|
1695
|
+
}).then(() => {
|
|
1727
1696
|
logger.info("Connected to PostgreSQL database");
|
|
1728
1697
|
}).catch((error) => {
|
|
1729
1698
|
logger.error("Error connecting to database", error);
|
|
1730
1699
|
process.exit(1);
|
|
1731
1700
|
});
|
|
1732
1701
|
}
|
|
1702
|
+
pool;
|
|
1733
1703
|
async query(query, values) {
|
|
1734
1704
|
return this.pool.query(query, values);
|
|
1735
1705
|
}
|
|
@@ -1739,7 +1709,12 @@ var PsqlPool = class extends PsqlConnection {
|
|
|
1739
1709
|
import { ObjectUtils as ObjectUtils3 } from "@redskytech/core-utils";
|
|
1740
1710
|
var SqlEngine = class {
|
|
1741
1711
|
async runQueryForRoute(req, routeData, schema) {
|
|
1742
|
-
if (!this.
|
|
1712
|
+
if (!this.canRequesterAccessTable(
|
|
1713
|
+
req.requesterDetails.role,
|
|
1714
|
+
req.requesterDetails.scopes,
|
|
1715
|
+
schema,
|
|
1716
|
+
routeData.table
|
|
1717
|
+
))
|
|
1743
1718
|
throw new RsError("FORBIDDEN", "You do not have permission to access this table");
|
|
1744
1719
|
switch (routeData.method) {
|
|
1745
1720
|
case "POST":
|
|
@@ -1758,7 +1733,7 @@ var SqlEngine = class {
|
|
|
1758
1733
|
if (!tableSchema) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
|
|
1759
1734
|
return tableSchema;
|
|
1760
1735
|
}
|
|
1761
|
-
|
|
1736
|
+
canRequesterAccessColumn(requesterRole, requesterScopes, schema, item, joins) {
|
|
1762
1737
|
if (item.type) return true;
|
|
1763
1738
|
if (item.selector) {
|
|
1764
1739
|
let tableName = item.selector.split(".")[0];
|
|
@@ -1774,26 +1749,36 @@ var SqlEngine = class {
|
|
|
1774
1749
|
const columnSchema = tableSchema.columns.find((item2) => item2.name === columnName);
|
|
1775
1750
|
if (!columnSchema)
|
|
1776
1751
|
throw new RsError("SCHEMA_ERROR", `Column ${columnName} not found in table ${tableName}`);
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1752
|
+
if (ObjectUtils3.isArrayWithData(columnSchema.roles)) {
|
|
1753
|
+
if (!requesterRole) return false;
|
|
1754
|
+
return columnSchema.roles.includes(requesterRole);
|
|
1755
|
+
}
|
|
1756
|
+
if (ObjectUtils3.isArrayWithData(columnSchema.scopes)) {
|
|
1757
|
+
if (!requesterScopes) return false;
|
|
1758
|
+
return columnSchema.scopes.some((scope) => requesterScopes.includes(scope));
|
|
1759
|
+
}
|
|
1760
|
+
return true;
|
|
1781
1761
|
}
|
|
1782
1762
|
if (item.subquery) {
|
|
1783
1763
|
return ObjectUtils3.isArrayWithData(
|
|
1784
1764
|
item.subquery.properties.filter((nestedItem) => {
|
|
1785
|
-
return this.
|
|
1765
|
+
return this.canRequesterAccessColumn(requesterRole, requesterScopes, schema, nestedItem, joins);
|
|
1786
1766
|
})
|
|
1787
1767
|
);
|
|
1788
1768
|
}
|
|
1789
1769
|
return false;
|
|
1790
1770
|
}
|
|
1791
|
-
|
|
1771
|
+
canRequesterAccessTable(requesterRole, requesterScopes, schema, tableName) {
|
|
1792
1772
|
const tableSchema = this.getTableSchema(schema, tableName);
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1773
|
+
if (ObjectUtils3.isArrayWithData(tableSchema.roles)) {
|
|
1774
|
+
if (!requesterRole) return false;
|
|
1775
|
+
return tableSchema.roles.includes(requesterRole);
|
|
1776
|
+
}
|
|
1777
|
+
if (ObjectUtils3.isArrayWithData(tableSchema.scopes)) {
|
|
1778
|
+
if (!requesterScopes) return false;
|
|
1779
|
+
return tableSchema.scopes.some((scope) => requesterScopes.includes(scope));
|
|
1780
|
+
}
|
|
1781
|
+
return true;
|
|
1797
1782
|
}
|
|
1798
1783
|
replaceParamKeywords(value, routeData, req, sqlParams) {
|
|
1799
1784
|
let returnValue = value;
|
|
@@ -1802,11 +1787,10 @@ var SqlEngine = class {
|
|
|
1802
1787
|
return returnValue;
|
|
1803
1788
|
}
|
|
1804
1789
|
replaceLocalParamKeywords(value, routeData, req, sqlParams) {
|
|
1805
|
-
var _a2;
|
|
1806
1790
|
if (!routeData.request) return value;
|
|
1807
1791
|
const data = req.data;
|
|
1808
1792
|
if (typeof value === "string") {
|
|
1809
|
-
|
|
1793
|
+
value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)?.forEach((param) => {
|
|
1810
1794
|
const requestParam = routeData.request.find((item) => {
|
|
1811
1795
|
return item.name === param.replace("$", "");
|
|
1812
1796
|
});
|
|
@@ -1819,9 +1803,8 @@ var SqlEngine = class {
|
|
|
1819
1803
|
return value;
|
|
1820
1804
|
}
|
|
1821
1805
|
replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
|
|
1822
|
-
var _a2;
|
|
1823
1806
|
if (typeof value === "string") {
|
|
1824
|
-
|
|
1807
|
+
value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)?.forEach((param) => {
|
|
1825
1808
|
param = param.replace("#", "");
|
|
1826
1809
|
const globalParamValue = req.requesterDetails[param];
|
|
1827
1810
|
if (!globalParamValue)
|
|
@@ -1911,10 +1894,32 @@ negate = "!"
|
|
|
1911
1894
|
operator = "and"i / "or"i
|
|
1912
1895
|
|
|
1913
1896
|
|
|
1914
|
-
column =
|
|
1915
|
-
|
|
1916
|
-
|
|
1897
|
+
column = first:text rest:("." text)* {
|
|
1898
|
+
const partsArray = [first];
|
|
1899
|
+
if (rest && rest.length > 0) {
|
|
1900
|
+
partsArray.push(...rest.map(item => item[1]));
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
if (partsArray.length > 3) {
|
|
1904
|
+
throw new SyntaxError('Column path cannot have more than 3 parts (table.column.jsonField)');
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
if (partsArray.length === 1) {
|
|
1908
|
+
return quoteSqlIdentity(partsArray[0]);
|
|
1909
|
+
}
|
|
1910
|
+
const tableName = quoteSqlIdentity(partsArray[0]);
|
|
1917
1911
|
|
|
1912
|
+
// If we only have two parts (table.column), use regular dot notation
|
|
1913
|
+
if (partsArray.length === 2) {
|
|
1914
|
+
return tableName + "." + quoteSqlIdentity(partsArray[1]);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// For JSON paths (more than 2 parts), first part is a column, last part uses ->>
|
|
1918
|
+
const jsonColumn = quoteSqlIdentity(partsArray[1]);
|
|
1919
|
+
const lastPart = partsArray[partsArray.length - 1];
|
|
1920
|
+
const result = tableName + "." + jsonColumn + "->>'" + lastPart + "'";
|
|
1921
|
+
return result;
|
|
1922
|
+
}
|
|
1918
1923
|
|
|
1919
1924
|
text = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ { return text.join(""); }
|
|
1920
1925
|
|
|
@@ -1944,19 +1949,24 @@ var filterPsqlParser_default = filterPsqlParser;
|
|
|
1944
1949
|
var { Client, types } = pg2;
|
|
1945
1950
|
var systemUser = {
|
|
1946
1951
|
role: "",
|
|
1952
|
+
scopes: [],
|
|
1947
1953
|
host: "",
|
|
1948
1954
|
ipAddress: "",
|
|
1949
1955
|
isSystemUser: true
|
|
1950
1956
|
};
|
|
1951
1957
|
var PsqlEngine = class extends SqlEngine {
|
|
1952
|
-
constructor(psqlConnectionPool, shouldListenForDbTriggers = false) {
|
|
1958
|
+
constructor(psqlConnectionPool, shouldListenForDbTriggers = false, scratchDatabaseSuffix = "") {
|
|
1953
1959
|
super();
|
|
1954
1960
|
this.psqlConnectionPool = psqlConnectionPool;
|
|
1955
1961
|
this.setupPgReturnTypes();
|
|
1956
1962
|
if (shouldListenForDbTriggers) {
|
|
1957
1963
|
this.setupTriggerListeners = this.listenForDbTriggers();
|
|
1958
1964
|
}
|
|
1965
|
+
this.scratchDbName = `${psqlConnectionPool.poolConfig.database}_scratch${scratchDatabaseSuffix ? `_${scratchDatabaseSuffix}` : ""}`;
|
|
1959
1966
|
}
|
|
1967
|
+
setupTriggerListeners;
|
|
1968
|
+
triggerClient;
|
|
1969
|
+
scratchDbName = "";
|
|
1960
1970
|
async close() {
|
|
1961
1971
|
if (this.triggerClient) {
|
|
1962
1972
|
await this.triggerClient.end();
|
|
@@ -2085,27 +2095,22 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2085
2095
|
sqlStatements.push(triggers.join("\n"));
|
|
2086
2096
|
return sqlStatements.join("\n\n");
|
|
2087
2097
|
}
|
|
2088
|
-
async
|
|
2089
|
-
var _a2, _b;
|
|
2098
|
+
async getNewPublicSchemaAndScratchPool() {
|
|
2090
2099
|
const scratchDbExists = await this.psqlConnectionPool.runQuery(
|
|
2091
2100
|
`SELECT *
|
|
2092
2101
|
FROM pg_database
|
|
2093
|
-
WHERE datname = '${this.
|
|
2102
|
+
WHERE datname = '${this.scratchDbName}';`,
|
|
2094
2103
|
[],
|
|
2095
2104
|
systemUser
|
|
2096
2105
|
);
|
|
2097
2106
|
if (scratchDbExists.length === 0) {
|
|
2098
|
-
await this.psqlConnectionPool.runQuery(
|
|
2099
|
-
`CREATE DATABASE ${this.psqlConnectionPool.poolConfig.database}_scratch;`,
|
|
2100
|
-
[],
|
|
2101
|
-
systemUser
|
|
2102
|
-
);
|
|
2107
|
+
await this.psqlConnectionPool.runQuery(`CREATE DATABASE ${this.scratchDbName};`, [], systemUser);
|
|
2103
2108
|
}
|
|
2104
2109
|
const scratchPool = new PsqlPool({
|
|
2105
2110
|
host: this.psqlConnectionPool.poolConfig.host,
|
|
2106
2111
|
port: this.psqlConnectionPool.poolConfig.port,
|
|
2107
2112
|
user: this.psqlConnectionPool.poolConfig.user,
|
|
2108
|
-
database: this.
|
|
2113
|
+
database: this.scratchDbName,
|
|
2109
2114
|
password: this.psqlConnectionPool.poolConfig.password,
|
|
2110
2115
|
max: this.psqlConnectionPool.poolConfig.max,
|
|
2111
2116
|
idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
|
|
@@ -2118,16 +2123,17 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2118
2123
|
systemUser
|
|
2119
2124
|
);
|
|
2120
2125
|
const schemaComment = await this.psqlConnectionPool.runQuery(
|
|
2121
|
-
`
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2126
|
+
`
|
|
2127
|
+
SELECT pg_description.description
|
|
2128
|
+
FROM pg_description
|
|
2129
|
+
JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
|
|
2130
|
+
WHERE pg_namespace.nspname = 'public';`,
|
|
2125
2131
|
[],
|
|
2126
2132
|
systemUser
|
|
2127
2133
|
);
|
|
2128
|
-
if (
|
|
2134
|
+
if (schemaComment[0]?.description) {
|
|
2129
2135
|
await scratchPool.runQuery(
|
|
2130
|
-
`COMMENT ON SCHEMA public IS '${
|
|
2136
|
+
`COMMENT ON SCHEMA public IS '${schemaComment[0]?.description}';`,
|
|
2131
2137
|
[],
|
|
2132
2138
|
systemUser
|
|
2133
2139
|
);
|
|
@@ -2135,7 +2141,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2135
2141
|
return scratchPool;
|
|
2136
2142
|
}
|
|
2137
2143
|
async diffDatabaseToSchema(schema) {
|
|
2138
|
-
const scratchPool = await this.
|
|
2144
|
+
const scratchPool = await this.getNewPublicSchemaAndScratchPool();
|
|
2139
2145
|
await this.createDatabaseFromSchema(schema, scratchPool);
|
|
2140
2146
|
const originalClient = new Client({
|
|
2141
2147
|
database: this.psqlConnectionPool.poolConfig.database,
|
|
@@ -2145,7 +2151,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2145
2151
|
port: this.psqlConnectionPool.poolConfig.port
|
|
2146
2152
|
});
|
|
2147
2153
|
const scratchClient = new Client({
|
|
2148
|
-
database: this.
|
|
2154
|
+
database: this.scratchDbName,
|
|
2149
2155
|
user: this.psqlConnectionPool.poolConfig.user,
|
|
2150
2156
|
password: this.psqlConnectionPool.poolConfig.password,
|
|
2151
2157
|
host: this.psqlConnectionPool.poolConfig.host,
|
|
@@ -2160,24 +2166,30 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2160
2166
|
await Promise.all(endPromises);
|
|
2161
2167
|
return diff.join("\n");
|
|
2162
2168
|
}
|
|
2163
|
-
createNestedSelect(req, schema, item, routeData,
|
|
2169
|
+
createNestedSelect(req, schema, item, routeData, sqlParams) {
|
|
2164
2170
|
if (!item.subquery) return "";
|
|
2165
2171
|
if (!ObjectUtils4.isArrayWithData(
|
|
2166
2172
|
item.subquery.properties.filter((nestedItem) => {
|
|
2167
|
-
return this.
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2173
|
+
return this.canRequesterAccessColumn(
|
|
2174
|
+
req.requesterDetails.role,
|
|
2175
|
+
req.requesterDetails.scopes,
|
|
2176
|
+
schema,
|
|
2177
|
+
nestedItem,
|
|
2178
|
+
[...routeData.joins, ...item.subquery.joins]
|
|
2179
|
+
);
|
|
2171
2180
|
})
|
|
2172
2181
|
)) {
|
|
2173
2182
|
return "'[]'";
|
|
2174
2183
|
}
|
|
2175
2184
|
return `COALESCE((SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
|
2176
2185
|
${item.subquery.properties.map((nestedItem) => {
|
|
2177
|
-
if (!this.
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2186
|
+
if (!this.canRequesterAccessColumn(
|
|
2187
|
+
req.requesterDetails.role,
|
|
2188
|
+
req.requesterDetails.scopes,
|
|
2189
|
+
schema,
|
|
2190
|
+
nestedItem,
|
|
2191
|
+
[...routeData.joins, ...item.subquery.joins]
|
|
2192
|
+
)) {
|
|
2181
2193
|
return;
|
|
2182
2194
|
}
|
|
2183
2195
|
if (nestedItem.subquery) {
|
|
@@ -2187,7 +2199,6 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2187
2199
|
schema,
|
|
2188
2200
|
nestedItem,
|
|
2189
2201
|
routeData,
|
|
2190
|
-
userRole,
|
|
2191
2202
|
sqlParams
|
|
2192
2203
|
)}`;
|
|
2193
2204
|
}
|
|
@@ -2196,7 +2207,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2196
2207
|
))
|
|
2197
2208
|
FROM
|
|
2198
2209
|
"${item.subquery.table}"
|
|
2199
|
-
${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema,
|
|
2210
|
+
${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, sqlParams)}
|
|
2200
2211
|
${this.generateWhereClause(req, item.subquery.where, routeData, sqlParams)}
|
|
2201
2212
|
), '[]')`;
|
|
2202
2213
|
}
|
|
@@ -2206,7 +2217,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2206
2217
|
(routeData.assignments || []).forEach((assignment) => {
|
|
2207
2218
|
parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
|
|
2208
2219
|
});
|
|
2209
|
-
const query = insertObjectQuery(routeData.table,
|
|
2220
|
+
const query = insertObjectQuery(routeData.table, { ...req.data, ...parameterObj });
|
|
2210
2221
|
const createdItem = await this.psqlConnectionPool.queryOne(
|
|
2211
2222
|
query,
|
|
2212
2223
|
sqlParams,
|
|
@@ -2221,24 +2232,29 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2221
2232
|
};
|
|
2222
2233
|
const whereData = [whereId];
|
|
2223
2234
|
req.data = { id: insertId };
|
|
2224
|
-
return this.executeGetRequest(req,
|
|
2235
|
+
return this.executeGetRequest(req, { ...routeData, where: whereData }, schema);
|
|
2225
2236
|
}
|
|
2226
2237
|
async executeGetRequest(req, routeData, schema) {
|
|
2227
2238
|
const DEFAULT_PAGED_PAGE_NUMBER = 0;
|
|
2228
2239
|
const DEFAULT_PAGED_PER_PAGE_NUMBER = 25;
|
|
2229
2240
|
const sqlParams = [];
|
|
2230
|
-
const userRole = req.requesterDetails.role;
|
|
2231
2241
|
let sqlStatement = "";
|
|
2232
2242
|
const selectColumns = [];
|
|
2233
2243
|
routeData.response.forEach((item) => {
|
|
2234
|
-
if (item.subquery || this.
|
|
2244
|
+
if (item.subquery || this.canRequesterAccessColumn(
|
|
2245
|
+
req.requesterDetails.role,
|
|
2246
|
+
req.requesterDetails.scopes,
|
|
2247
|
+
schema,
|
|
2248
|
+
item,
|
|
2249
|
+
routeData.joins
|
|
2250
|
+
))
|
|
2235
2251
|
selectColumns.push(item);
|
|
2236
2252
|
});
|
|
2237
2253
|
if (!selectColumns.length) throw new RsError("FORBIDDEN", `You do not have permission to access this data.`);
|
|
2238
2254
|
let selectStatement = "SELECT \n";
|
|
2239
2255
|
selectStatement += ` ${selectColumns.map((item) => {
|
|
2240
2256
|
if (item.subquery) {
|
|
2241
|
-
return `${this.createNestedSelect(req, schema, item, routeData,
|
|
2257
|
+
return `${this.createNestedSelect(req, schema, item, routeData, sqlParams)} AS ${escapeColumnName(
|
|
2242
2258
|
item.name
|
|
2243
2259
|
)}`;
|
|
2244
2260
|
}
|
|
@@ -2253,7 +2269,6 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2253
2269
|
routeData.table,
|
|
2254
2270
|
routeData,
|
|
2255
2271
|
schema,
|
|
2256
|
-
userRole,
|
|
2257
2272
|
sqlParams
|
|
2258
2273
|
);
|
|
2259
2274
|
sqlStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
@@ -2297,7 +2312,7 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2297
2312
|
}
|
|
2298
2313
|
async executeUpdateRequest(req, routeData, schema) {
|
|
2299
2314
|
const sqlParams = [];
|
|
2300
|
-
const
|
|
2315
|
+
const { id, ...bodyNoId } = req.body;
|
|
2301
2316
|
const table = schema.database.find((item) => {
|
|
2302
2317
|
return item.name === routeData.table;
|
|
2303
2318
|
});
|
|
@@ -2326,7 +2341,6 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
2326
2341
|
routeData.table,
|
|
2327
2342
|
routeData,
|
|
2328
2343
|
schema,
|
|
2329
|
-
req.requesterDetails.role,
|
|
2330
2344
|
sqlParams
|
|
2331
2345
|
);
|
|
2332
2346
|
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
@@ -2338,10 +2352,15 @@ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
|
|
|
2338
2352
|
await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
|
|
2339
2353
|
return true;
|
|
2340
2354
|
}
|
|
2341
|
-
generateJoinStatements(req, joins, baseTable, routeData, schema,
|
|
2355
|
+
generateJoinStatements(req, joins, baseTable, routeData, schema, sqlParams) {
|
|
2342
2356
|
let joinStatements = "";
|
|
2343
2357
|
joins.forEach((item) => {
|
|
2344
|
-
if (!this.
|
|
2358
|
+
if (!this.canRequesterAccessTable(
|
|
2359
|
+
req.requesterDetails.role,
|
|
2360
|
+
req.requesterDetails.scopes,
|
|
2361
|
+
schema,
|
|
2362
|
+
item.table
|
|
2363
|
+
))
|
|
2345
2364
|
throw new RsError("FORBIDDEN", "You do not have permission to access this table");
|
|
2346
2365
|
if (item.custom) {
|
|
2347
2366
|
const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
|
|
@@ -2396,41 +2415,36 @@ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
|
|
|
2396
2415
|
`Invalid where clause in route ${routeData.name}, missing required fields if not custom`
|
|
2397
2416
|
);
|
|
2398
2417
|
let operator = item.operator;
|
|
2399
|
-
let value = item.value;
|
|
2400
2418
|
if (operator === "LIKE") {
|
|
2401
|
-
value = `'%${value}%'`;
|
|
2402
|
-
} else if (operator === "NOT LIKE") {
|
|
2403
|
-
value = `'%${value}%'`;
|
|
2419
|
+
item.value = `'%${item.value}%'`;
|
|
2404
2420
|
} else if (operator === "STARTS WITH") {
|
|
2405
2421
|
operator = "LIKE";
|
|
2406
|
-
value = `'${value}%'`;
|
|
2422
|
+
item.value = `'${item.value}%'`;
|
|
2407
2423
|
} else if (operator === "ENDS WITH") {
|
|
2408
2424
|
operator = "LIKE";
|
|
2409
|
-
value = `'%${value}'`;
|
|
2425
|
+
item.value = `'%${item.value}'`;
|
|
2410
2426
|
}
|
|
2411
|
-
const replacedValue = this.replaceParamKeywords(value, routeData, req, sqlParams);
|
|
2427
|
+
const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
|
|
2412
2428
|
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${replacedValue})` : replacedValue}
|
|
2413
2429
|
`;
|
|
2414
2430
|
});
|
|
2415
2431
|
const data = req.data;
|
|
2416
|
-
if (routeData.type === "PAGED" && !!
|
|
2432
|
+
if (routeData.type === "PAGED" && !!data?.filter) {
|
|
2417
2433
|
let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
2418
|
-
var _a2;
|
|
2419
2434
|
const requestParam = routeData.request.find((item) => {
|
|
2420
2435
|
return item.name === value.replace("$", "");
|
|
2421
2436
|
});
|
|
2422
2437
|
if (!requestParam)
|
|
2423
2438
|
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
2424
|
-
return
|
|
2439
|
+
return data[requestParam.name]?.toString() || "";
|
|
2425
2440
|
});
|
|
2426
2441
|
statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
2427
|
-
var _a2;
|
|
2428
2442
|
const requestParam = routeData.request.find((item) => {
|
|
2429
2443
|
return item.name === value.replace("#", "");
|
|
2430
2444
|
});
|
|
2431
2445
|
if (!requestParam)
|
|
2432
2446
|
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
2433
|
-
return
|
|
2447
|
+
return data[requestParam.name]?.toString() || "";
|
|
2434
2448
|
});
|
|
2435
2449
|
statement = filterPsqlParser_default.parse(statement);
|
|
2436
2450
|
if (whereClause.startsWith("WHERE")) {
|
|
@@ -2671,8 +2685,9 @@ import { FileUtils as FileUtils2 } from "@restura/internal";
|
|
|
2671
2685
|
import Bluebird3 from "bluebird";
|
|
2672
2686
|
import * as os2 from "os";
|
|
2673
2687
|
var TempCache = class {
|
|
2688
|
+
location;
|
|
2689
|
+
maxDurationDays = 7;
|
|
2674
2690
|
constructor(location) {
|
|
2675
|
-
this.maxDurationDays = 7;
|
|
2676
2691
|
this.location = location || os2.tmpdir();
|
|
2677
2692
|
FileUtils2.ensureDir(this.location).catch((e) => {
|
|
2678
2693
|
throw e;
|
|
@@ -2697,15 +2712,24 @@ var TempCache = class {
|
|
|
2697
2712
|
|
|
2698
2713
|
// src/restura/restura.ts
|
|
2699
2714
|
var ResturaEngine = class {
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2715
|
+
// Make public so other modules can access without re-parsing the config
|
|
2716
|
+
resturaConfig;
|
|
2717
|
+
multerCommonUpload;
|
|
2718
|
+
resturaRouter;
|
|
2719
|
+
publicEndpoints = {
|
|
2720
|
+
GET: [],
|
|
2721
|
+
POST: [],
|
|
2722
|
+
PUT: [],
|
|
2723
|
+
PATCH: [],
|
|
2724
|
+
DELETE: []
|
|
2725
|
+
};
|
|
2726
|
+
expressApp;
|
|
2727
|
+
schema;
|
|
2728
|
+
responseValidator;
|
|
2729
|
+
authenticationHandler;
|
|
2730
|
+
customTypeValidation;
|
|
2731
|
+
psqlConnectionPool;
|
|
2732
|
+
psqlEngine;
|
|
2709
2733
|
/**
|
|
2710
2734
|
* Initializes the Restura engine with the provided Express application.
|
|
2711
2735
|
*
|
|
@@ -2713,11 +2737,11 @@ var ResturaEngine = class {
|
|
|
2713
2737
|
* @returns A promise that resolves when the initialization is complete.
|
|
2714
2738
|
*/
|
|
2715
2739
|
async init(app, authenticationHandler, psqlConnectionPool) {
|
|
2716
|
-
this.resturaConfig = config2.validate("restura", resturaConfigSchema);
|
|
2740
|
+
this.resturaConfig = await config2.validate("restura", resturaConfigSchema);
|
|
2717
2741
|
this.multerCommonUpload = getMulterUpload(this.resturaConfig.fileTempCachePath);
|
|
2718
2742
|
new TempCache(this.resturaConfig.fileTempCachePath);
|
|
2719
2743
|
this.psqlConnectionPool = psqlConnectionPool;
|
|
2720
|
-
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool, true);
|
|
2744
|
+
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool, true, this.resturaConfig.scratchDatabaseSuffix);
|
|
2721
2745
|
await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
|
|
2722
2746
|
this.authenticationHandler = authenticationHandler;
|
|
2723
2747
|
app.use(compression());
|
|
@@ -2726,7 +2750,7 @@ var ResturaEngine = class {
|
|
|
2726
2750
|
app.use(cookieParser());
|
|
2727
2751
|
app.disable("x-powered-by");
|
|
2728
2752
|
app.use("/", addApiResponseFunctions);
|
|
2729
|
-
app.use("/api/",
|
|
2753
|
+
app.use("/api/", authenticateRequester(this.authenticationHandler));
|
|
2730
2754
|
app.use("/restura", this.resturaAuthentication);
|
|
2731
2755
|
app.put(
|
|
2732
2756
|
"/restura/v1/schema",
|
|
@@ -2747,7 +2771,7 @@ var ResturaEngine = class {
|
|
|
2747
2771
|
}
|
|
2748
2772
|
/**
|
|
2749
2773
|
* Determines if a given endpoint is public based on the HTTP method and full URL. This
|
|
2750
|
-
* is determined on whether the endpoint in the schema has no roles assigned to it.
|
|
2774
|
+
* is determined on whether the endpoint in the schema has no roles or scopes assigned to it.
|
|
2751
2775
|
*
|
|
2752
2776
|
* @param method - The HTTP method (e.g., 'GET', 'POST', 'PUT', 'PATCH', 'DELETE').
|
|
2753
2777
|
* @param fullUrl - The full URL of the endpoint.
|
|
@@ -2836,7 +2860,8 @@ var ResturaEngine = class {
|
|
|
2836
2860
|
route.path = route.path.startsWith("/") ? route.path : `/${route.path}`;
|
|
2837
2861
|
route.path = route.path.endsWith("/") ? route.path.slice(0, -1) : route.path;
|
|
2838
2862
|
const fullUrl = `${baseUrl}${route.path}`;
|
|
2839
|
-
if (route.roles.length === 0
|
|
2863
|
+
if (route.roles.length === 0 && route.scopes.length === 0)
|
|
2864
|
+
this.publicEndpoints[route.method].push(fullUrl);
|
|
2840
2865
|
this.resturaRouter[route.method.toLowerCase()](
|
|
2841
2866
|
route.path,
|
|
2842
2867
|
// <-- Notice we only use path here since the baseUrl is already added to the router.
|
|
@@ -2886,10 +2911,10 @@ var ResturaEngine = class {
|
|
|
2886
2911
|
);
|
|
2887
2912
|
this.generateResturaGlobalTypes(path4.join(this.resturaConfig.generatedTypesPath, "restura.d.ts"));
|
|
2888
2913
|
}
|
|
2889
|
-
async getSchema(
|
|
2914
|
+
async getSchema(_req, res) {
|
|
2890
2915
|
res.send({ data: this.schema });
|
|
2891
2916
|
}
|
|
2892
|
-
async getSchemaAndTypes(
|
|
2917
|
+
async getSchemaAndTypes(_req, res) {
|
|
2893
2918
|
try {
|
|
2894
2919
|
const schema = await this.getLatestFileSystemSchema();
|
|
2895
2920
|
const apiText = await apiGenerator(schema);
|
|
@@ -2900,8 +2925,7 @@ var ResturaEngine = class {
|
|
|
2900
2925
|
}
|
|
2901
2926
|
}
|
|
2902
2927
|
async getMulterFilesIfAny(req, res, routeData) {
|
|
2903
|
-
|
|
2904
|
-
if (!((_a2 = req.header("content-type")) == null ? void 0 : _a2.includes("multipart/form-data"))) return;
|
|
2928
|
+
if (!req.header("content-type")?.includes("multipart/form-data")) return;
|
|
2905
2929
|
if (!this.isCustomRoute(routeData)) return;
|
|
2906
2930
|
if (!routeData.fileUploadType) {
|
|
2907
2931
|
throw new RsError("BAD_REQUEST", "File upload type not defined for route");
|
|
@@ -2964,16 +2988,17 @@ var ResturaEngine = class {
|
|
|
2964
2988
|
await customFunction(req, res, routeData);
|
|
2965
2989
|
}
|
|
2966
2990
|
async storeFileSystemSchema() {
|
|
2967
|
-
const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema),
|
|
2968
|
-
parser: "json"
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2991
|
+
const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema), {
|
|
2992
|
+
parser: "json",
|
|
2993
|
+
...{
|
|
2994
|
+
trailingComma: "none",
|
|
2995
|
+
tabWidth: 4,
|
|
2996
|
+
useTabs: true,
|
|
2997
|
+
endOfLine: "lf",
|
|
2998
|
+
printWidth: 120,
|
|
2999
|
+
singleQuote: true
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
2977
3002
|
fs4.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
|
|
2978
3003
|
}
|
|
2979
3004
|
resetPublicEndpoints() {
|
|
@@ -2986,9 +3011,13 @@ var ResturaEngine = class {
|
|
|
2986
3011
|
};
|
|
2987
3012
|
}
|
|
2988
3013
|
validateAuthorization(req, routeData) {
|
|
2989
|
-
const
|
|
2990
|
-
|
|
2991
|
-
if (
|
|
3014
|
+
const requesterRole = req.requesterDetails.role;
|
|
3015
|
+
const requesterScopes = req.requesterDetails.scopes;
|
|
3016
|
+
if (routeData.roles.length === 0 && routeData.scopes.length === 0) return;
|
|
3017
|
+
if (requesterRole && routeData.roles.length > 0 && !routeData.roles.includes(requesterRole))
|
|
3018
|
+
throw new RsError("FORBIDDEN", "Not authorized to access this endpoint");
|
|
3019
|
+
if (requesterScopes.length > 0 && routeData.scopes.length > 0 && !routeData.scopes.some((scope) => requesterScopes.includes(scope)))
|
|
3020
|
+
throw new RsError("FORBIDDEN", "Not authorized to access this endpoint");
|
|
2992
3021
|
}
|
|
2993
3022
|
getRouteData(method, baseUrl, path5) {
|
|
2994
3023
|
const endpoint = this.schema.endpoints.find((item) => {
|
|
@@ -3042,6 +3071,9 @@ var PsqlTransaction = class extends PsqlConnection {
|
|
|
3042
3071
|
this.connectPromise = this.client.connect();
|
|
3043
3072
|
this.beginTransactionPromise = this.beginTransaction();
|
|
3044
3073
|
}
|
|
3074
|
+
client;
|
|
3075
|
+
beginTransactionPromise;
|
|
3076
|
+
connectPromise;
|
|
3045
3077
|
async close() {
|
|
3046
3078
|
if (this.client) {
|
|
3047
3079
|
await this.client.end();
|