@restura/core 0.1.0-alpha.8 → 0.1.1
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 +1439 -177
- package/dist/index.d.ts +1439 -177
- package/dist/index.js +1767 -708
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1751 -705
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -6
- package/dist/acorn-SW5GI5G7.mjs +0 -3016
- package/dist/acorn-SW5GI5G7.mjs.map +0 -1
- package/dist/angular-FYEH6QOL.mjs +0 -1547
- package/dist/angular-FYEH6QOL.mjs.map +0 -1
- package/dist/babel-V6GZHMYX.mjs +0 -6911
- package/dist/babel-V6GZHMYX.mjs.map +0 -1
- package/dist/chunk-TL4KRYOF.mjs +0 -58
- package/dist/chunk-TL4KRYOF.mjs.map +0 -1
- package/dist/estree-67ZCSSSI.mjs +0 -4396
- package/dist/estree-67ZCSSSI.mjs.map +0 -1
- package/dist/flow-SJW7PRXX.mjs +0 -26365
- package/dist/flow-SJW7PRXX.mjs.map +0 -1
- package/dist/glimmer-PF2X22V2.mjs +0 -2995
- package/dist/glimmer-PF2X22V2.mjs.map +0 -1
- package/dist/graphql-NOJ5HX7K.mjs +0 -1253
- package/dist/graphql-NOJ5HX7K.mjs.map +0 -1
- package/dist/html-ROPIWVPQ.mjs +0 -2780
- package/dist/html-ROPIWVPQ.mjs.map +0 -1
- package/dist/index.cjs +0 -34
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -3
- package/dist/markdown-PFYT4MSP.mjs +0 -3423
- package/dist/markdown-PFYT4MSP.mjs.map +0 -1
- package/dist/meriyah-5NLZXLMA.mjs +0 -2356
- package/dist/meriyah-5NLZXLMA.mjs.map +0 -1
- package/dist/postcss-ITO6IEN5.mjs +0 -5027
- package/dist/postcss-ITO6IEN5.mjs.map +0 -1
- package/dist/typescript-6IE7K56Q.mjs +0 -13392
- package/dist/typescript-6IE7K56Q.mjs.map +0 -1
- package/dist/yaml-DB2OVPLH.mjs +0 -4230
- package/dist/yaml-DB2OVPLH.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -44,16 +44,10 @@ import { config } from "@restura/internal";
|
|
|
44
44
|
import winston from "winston";
|
|
45
45
|
import { format } from "logform";
|
|
46
46
|
|
|
47
|
-
// src/
|
|
47
|
+
// src/logger/loggerConfigSchema.ts
|
|
48
48
|
import { z } from "zod";
|
|
49
49
|
var loggerConfigSchema = z.object({
|
|
50
|
-
level: z.enum(["info", "warn", "error", "debug"]).default("info")
|
|
51
|
-
});
|
|
52
|
-
var resturaConfigSchema = z.object({
|
|
53
|
-
authToken: z.string().min(1, "Missing Restura Auth Token"),
|
|
54
|
-
sendErrorStackTrace: z.boolean().default(false),
|
|
55
|
-
schemaFilePath: z.string().default(process.cwd() + "/restura.schema.json"),
|
|
56
|
-
generatedTypesPath: z.string().default(process.cwd() + "/src/@types")
|
|
50
|
+
level: z.enum(["info", "warn", "error", "debug", "silly"]).default("info")
|
|
57
51
|
});
|
|
58
52
|
|
|
59
53
|
// src/logger/logger.ts
|
|
@@ -91,8 +85,140 @@ var logger = winston.createLogger({
|
|
|
91
85
|
]
|
|
92
86
|
});
|
|
93
87
|
|
|
88
|
+
// src/restura/eventManager.ts
|
|
89
|
+
import Bluebird from "bluebird";
|
|
90
|
+
var EventManager = class {
|
|
91
|
+
constructor() {
|
|
92
|
+
this.actionHandlers = {
|
|
93
|
+
DATABASE_ROW_DELETE: [],
|
|
94
|
+
DATABASE_ROW_INSERT: [],
|
|
95
|
+
DATABASE_COLUMN_UPDATE: []
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
addRowInsertHandler(onInsert, filter) {
|
|
99
|
+
this.actionHandlers.DATABASE_ROW_INSERT.push({
|
|
100
|
+
callback: onInsert,
|
|
101
|
+
filter
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
addColumnChangeHandler(onUpdate, filter) {
|
|
105
|
+
this.actionHandlers.DATABASE_COLUMN_UPDATE.push({
|
|
106
|
+
callback: onUpdate,
|
|
107
|
+
filter
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
addRowDeleteHandler(onDelete, filter) {
|
|
111
|
+
this.actionHandlers.DATABASE_ROW_DELETE.push({
|
|
112
|
+
callback: onDelete,
|
|
113
|
+
filter
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async fireActionFromDbTrigger(sqlMutationData, result) {
|
|
117
|
+
if (sqlMutationData.mutationType === "INSERT") {
|
|
118
|
+
await this.fireInsertActions(sqlMutationData, result);
|
|
119
|
+
} else if (sqlMutationData.mutationType === "UPDATE") {
|
|
120
|
+
await this.fireUpdateActions(sqlMutationData, result);
|
|
121
|
+
} else if (sqlMutationData.mutationType === "DELETE") {
|
|
122
|
+
await this.fireDeleteActions(sqlMutationData, result);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async fireInsertActions(data, triggerResult) {
|
|
126
|
+
await Bluebird.map(
|
|
127
|
+
this.actionHandlers.DATABASE_ROW_INSERT,
|
|
128
|
+
({ callback, filter }) => {
|
|
129
|
+
if (!this.hasHandlersForEventType("DATABASE_ROW_INSERT", filter, triggerResult)) return;
|
|
130
|
+
const insertData = {
|
|
131
|
+
tableName: triggerResult.table,
|
|
132
|
+
insertedId: triggerResult.insertedId || 0,
|
|
133
|
+
insertObject: triggerResult.record,
|
|
134
|
+
queryMetadata: data.queryMetadata
|
|
135
|
+
};
|
|
136
|
+
callback(insertData, data.queryMetadata);
|
|
137
|
+
},
|
|
138
|
+
{ concurrency: 10 }
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
async fireDeleteActions(data, triggerResult) {
|
|
142
|
+
await Bluebird.map(
|
|
143
|
+
this.actionHandlers.DATABASE_ROW_DELETE,
|
|
144
|
+
({ callback, filter }) => {
|
|
145
|
+
if (!this.hasHandlersForEventType("DATABASE_ROW_DELETE", filter, triggerResult)) return;
|
|
146
|
+
const deleteData = {
|
|
147
|
+
tableName: triggerResult.table,
|
|
148
|
+
deletedId: triggerResult.deletedId || 0,
|
|
149
|
+
deletedRow: triggerResult.previousRecord,
|
|
150
|
+
queryMetadata: data.queryMetadata
|
|
151
|
+
};
|
|
152
|
+
callback(deleteData, data.queryMetadata);
|
|
153
|
+
},
|
|
154
|
+
{ concurrency: 10 }
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
async fireUpdateActions(data, triggerResult) {
|
|
158
|
+
await Bluebird.map(
|
|
159
|
+
this.actionHandlers.DATABASE_COLUMN_UPDATE,
|
|
160
|
+
({ callback, filter }) => {
|
|
161
|
+
if (!this.hasHandlersForEventType("DATABASE_COLUMN_UPDATE", filter, triggerResult)) return;
|
|
162
|
+
const columnChangeData = {
|
|
163
|
+
tableName: triggerResult.table,
|
|
164
|
+
changedId: triggerResult.changedId || 0,
|
|
165
|
+
newData: triggerResult.record,
|
|
166
|
+
oldData: triggerResult.previousRecord,
|
|
167
|
+
queryMetadata: data.queryMetadata
|
|
168
|
+
};
|
|
169
|
+
callback(columnChangeData, data.queryMetadata);
|
|
170
|
+
},
|
|
171
|
+
{ concurrency: 10 }
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
hasHandlersForEventType(eventType, filter, triggerResult) {
|
|
175
|
+
if (filter) {
|
|
176
|
+
switch (eventType) {
|
|
177
|
+
case "DATABASE_ROW_INSERT":
|
|
178
|
+
case "DATABASE_ROW_DELETE":
|
|
179
|
+
if (filter.tableName && filter.tableName !== triggerResult.table) return false;
|
|
180
|
+
break;
|
|
181
|
+
case "DATABASE_COLUMN_UPDATE":
|
|
182
|
+
const filterColumnChange = filter;
|
|
183
|
+
if (filterColumnChange.tableName !== triggerResult.table) return false;
|
|
184
|
+
if (filterColumnChange.columns.length === 1) {
|
|
185
|
+
const firstColumn = filterColumnChange.columns[0];
|
|
186
|
+
if (firstColumn === "*") return true;
|
|
187
|
+
}
|
|
188
|
+
if (!filterColumnChange.columns.some((item) => {
|
|
189
|
+
const updatedColumns = Object.keys(
|
|
190
|
+
changedValues(triggerResult.record, triggerResult.previousRecord)
|
|
191
|
+
);
|
|
192
|
+
return updatedColumns.includes(item);
|
|
193
|
+
}))
|
|
194
|
+
return false;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var eventManager = new EventManager();
|
|
202
|
+
var eventManager_default = eventManager;
|
|
203
|
+
function changedValues(record, previousRecord) {
|
|
204
|
+
const changed = {};
|
|
205
|
+
for (const i in previousRecord) {
|
|
206
|
+
if (previousRecord[i] !== record[i]) {
|
|
207
|
+
if (typeof previousRecord[i] === "object" && typeof record[i] === "object") {
|
|
208
|
+
const nestedChanged = changedValues(record[i], previousRecord[i]);
|
|
209
|
+
if (Object.keys(nestedChanged).length > 0) {
|
|
210
|
+
changed[i] = record[i];
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
changed[i] = record[i];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return changed;
|
|
218
|
+
}
|
|
219
|
+
|
|
94
220
|
// src/restura/restura.ts
|
|
95
|
-
import { ObjectUtils as ObjectUtils5 } from "@redskytech/core-utils";
|
|
221
|
+
import { ObjectUtils as ObjectUtils5, StringUtils as StringUtils3 } from "@redskytech/core-utils";
|
|
96
222
|
import { config as config2 } from "@restura/internal";
|
|
97
223
|
|
|
98
224
|
// ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
|
|
@@ -145,13 +271,25 @@ function boundMethod(target, key, descriptor) {
|
|
|
145
271
|
import bodyParser from "body-parser";
|
|
146
272
|
import compression from "compression";
|
|
147
273
|
import cookieParser from "cookie-parser";
|
|
148
|
-
import { createHash } from "crypto";
|
|
149
274
|
import * as express from "express";
|
|
150
|
-
import
|
|
151
|
-
import
|
|
275
|
+
import fs4 from "fs";
|
|
276
|
+
import path4 from "path";
|
|
152
277
|
import * as prettier3 from "prettier";
|
|
153
278
|
|
|
154
|
-
// src/restura/
|
|
279
|
+
// src/restura/RsError.ts
|
|
280
|
+
var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
|
|
281
|
+
HtmlStatusCodes2[HtmlStatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
|
282
|
+
HtmlStatusCodes2[HtmlStatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
|
283
|
+
HtmlStatusCodes2[HtmlStatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
|
|
284
|
+
HtmlStatusCodes2[HtmlStatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
|
|
285
|
+
HtmlStatusCodes2[HtmlStatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
|
|
286
|
+
HtmlStatusCodes2[HtmlStatusCodes2["CONFLICT"] = 409] = "CONFLICT";
|
|
287
|
+
HtmlStatusCodes2[HtmlStatusCodes2["VERSION_OUT_OF_DATE"] = 418] = "VERSION_OUT_OF_DATE";
|
|
288
|
+
HtmlStatusCodes2[HtmlStatusCodes2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
|
|
289
|
+
HtmlStatusCodes2[HtmlStatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
|
|
290
|
+
HtmlStatusCodes2[HtmlStatusCodes2["NETWORK_CONNECT_TIMEOUT"] = 599] = "NETWORK_CONNECT_TIMEOUT";
|
|
291
|
+
return HtmlStatusCodes2;
|
|
292
|
+
})(HtmlStatusCodes || {});
|
|
155
293
|
var RsError = class _RsError {
|
|
156
294
|
constructor(errCode, message) {
|
|
157
295
|
this.err = errCode;
|
|
@@ -162,6 +300,9 @@ var RsError = class _RsError {
|
|
|
162
300
|
static htmlStatus(code) {
|
|
163
301
|
return htmlStatusMap[code];
|
|
164
302
|
}
|
|
303
|
+
static isRsError(error) {
|
|
304
|
+
return error instanceof _RsError;
|
|
305
|
+
}
|
|
165
306
|
};
|
|
166
307
|
var htmlStatusMap = {
|
|
167
308
|
UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
|
|
@@ -200,12 +341,171 @@ var htmlStatusMap = {
|
|
|
200
341
|
SCHEMA_ERROR: 500 /* SERVER_ERROR */
|
|
201
342
|
};
|
|
202
343
|
|
|
344
|
+
// src/restura/compareSchema.ts
|
|
345
|
+
import cloneDeep from "lodash.clonedeep";
|
|
346
|
+
var CompareSchema = class {
|
|
347
|
+
async diffSchema(newSchema, latestSchema, psqlEngine) {
|
|
348
|
+
const endPoints = this.diffEndPoints(newSchema.endpoints[0].routes, latestSchema.endpoints[0].routes);
|
|
349
|
+
const globalParams = this.diffStringArray(newSchema.globalParams, latestSchema.globalParams);
|
|
350
|
+
const roles = this.diffStringArray(newSchema.roles, latestSchema.roles);
|
|
351
|
+
let commands = "";
|
|
352
|
+
if (JSON.stringify(newSchema.database) !== JSON.stringify(latestSchema.database))
|
|
353
|
+
commands = await psqlEngine.diffDatabaseToSchema(newSchema);
|
|
354
|
+
const hasCustomTypesChanged = JSON.stringify(newSchema.customTypes) !== JSON.stringify(latestSchema.customTypes);
|
|
355
|
+
const schemaPreview = {
|
|
356
|
+
endPoints,
|
|
357
|
+
globalParams,
|
|
358
|
+
roles,
|
|
359
|
+
commands,
|
|
360
|
+
customTypes: hasCustomTypesChanged
|
|
361
|
+
};
|
|
362
|
+
return schemaPreview;
|
|
363
|
+
}
|
|
364
|
+
diffStringArray(newArray, originalArray) {
|
|
365
|
+
const stringsDiff = [];
|
|
366
|
+
const originalClone = new Set(originalArray);
|
|
367
|
+
newArray.forEach((item) => {
|
|
368
|
+
const originalIndex = originalClone.has(item);
|
|
369
|
+
if (!originalIndex) {
|
|
370
|
+
stringsDiff.push({
|
|
371
|
+
name: item,
|
|
372
|
+
changeType: "NEW"
|
|
373
|
+
});
|
|
374
|
+
} else {
|
|
375
|
+
originalClone.delete(item);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
originalClone.forEach((item) => {
|
|
379
|
+
stringsDiff.push({
|
|
380
|
+
name: item,
|
|
381
|
+
changeType: "DELETED"
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
return stringsDiff;
|
|
385
|
+
}
|
|
386
|
+
diffEndPoints(newEndPoints, originalEndpoints) {
|
|
387
|
+
const originalClone = cloneDeep(originalEndpoints);
|
|
388
|
+
const diffObj = [];
|
|
389
|
+
newEndPoints.forEach((endPoint) => {
|
|
390
|
+
const { path: path5, method } = endPoint;
|
|
391
|
+
const endPointIndex = originalClone.findIndex((original) => {
|
|
392
|
+
return original.path === endPoint.path && original.method === endPoint.method;
|
|
393
|
+
});
|
|
394
|
+
if (endPointIndex === -1) {
|
|
395
|
+
diffObj.push({
|
|
396
|
+
name: `${method} ${path5}`,
|
|
397
|
+
changeType: "NEW"
|
|
398
|
+
});
|
|
399
|
+
} else {
|
|
400
|
+
const original = originalClone.findIndex((original2) => {
|
|
401
|
+
return this.compareEndPoints(endPoint, original2);
|
|
402
|
+
});
|
|
403
|
+
if (original === -1) {
|
|
404
|
+
diffObj.push({
|
|
405
|
+
name: `${method} ${path5}`,
|
|
406
|
+
changeType: "MODIFIED"
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
originalClone.splice(endPointIndex, 1);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
originalClone.forEach((original) => {
|
|
413
|
+
const { path: path5, method } = original;
|
|
414
|
+
diffObj.push({
|
|
415
|
+
name: `${method} ${path5}`,
|
|
416
|
+
changeType: "DELETED"
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
return diffObj;
|
|
420
|
+
}
|
|
421
|
+
compareEndPoints(endPoint1, endPoint2) {
|
|
422
|
+
return JSON.stringify(endPoint1) === JSON.stringify(endPoint2);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
__decorateClass([
|
|
426
|
+
boundMethod
|
|
427
|
+
], CompareSchema.prototype, "diffSchema", 1);
|
|
428
|
+
__decorateClass([
|
|
429
|
+
boundMethod
|
|
430
|
+
], CompareSchema.prototype, "diffStringArray", 1);
|
|
431
|
+
__decorateClass([
|
|
432
|
+
boundMethod
|
|
433
|
+
], CompareSchema.prototype, "diffEndPoints", 1);
|
|
434
|
+
__decorateClass([
|
|
435
|
+
boundMethod
|
|
436
|
+
], CompareSchema.prototype, "compareEndPoints", 1);
|
|
437
|
+
var compareSchema = new CompareSchema();
|
|
438
|
+
var compareSchema_default = compareSchema;
|
|
439
|
+
|
|
440
|
+
// src/restura/customApiFactory.ts
|
|
441
|
+
import Bluebird2 from "bluebird";
|
|
442
|
+
import fs from "fs";
|
|
443
|
+
import path from "path";
|
|
444
|
+
import { FileUtils } from "@restura/internal";
|
|
445
|
+
var CustomApiFactory = class {
|
|
446
|
+
constructor() {
|
|
447
|
+
this.customApis = {};
|
|
448
|
+
}
|
|
449
|
+
async loadApiFiles(baseFolderPath) {
|
|
450
|
+
const apiVersions = ["v1"];
|
|
451
|
+
for (const apiVersion of apiVersions) {
|
|
452
|
+
const apiVersionFolderPath = path.join(baseFolderPath, apiVersion);
|
|
453
|
+
const directoryExists = await FileUtils.existDir(apiVersionFolderPath);
|
|
454
|
+
if (!directoryExists) continue;
|
|
455
|
+
await this.addDirectory(apiVersionFolderPath, apiVersion);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
getCustomApi(customApiName) {
|
|
459
|
+
return this.customApis[customApiName];
|
|
460
|
+
}
|
|
461
|
+
async addDirectory(directoryPath, apiVersion) {
|
|
462
|
+
var _a2;
|
|
463
|
+
const entries = await fs.promises.readdir(directoryPath, {
|
|
464
|
+
withFileTypes: true
|
|
465
|
+
});
|
|
466
|
+
const isTsx2 = (_a2 = process.argv[1]) == null ? void 0 : _a2.endsWith(".ts");
|
|
467
|
+
const isTsNode2 = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
|
|
468
|
+
const extension = isTsx2 || isTsNode2 ? "ts" : "js";
|
|
469
|
+
const shouldEndWith = `.api.${apiVersion}.${extension}`;
|
|
470
|
+
await Bluebird2.map(entries, async (entry) => {
|
|
471
|
+
if (entry.isFile()) {
|
|
472
|
+
if (entry.name.endsWith(shouldEndWith) === false) return;
|
|
473
|
+
try {
|
|
474
|
+
const importPath = `${path.join(directoryPath, entry.name)}`;
|
|
475
|
+
const ApiImport = await import(importPath);
|
|
476
|
+
const customApiClass = new ApiImport.default();
|
|
477
|
+
logger.info(`Registering custom API: ${ApiImport.default.name}`);
|
|
478
|
+
this.bindMethodsToInstance(customApiClass);
|
|
479
|
+
this.customApis[ApiImport.default.name] = customApiClass;
|
|
480
|
+
} catch (e) {
|
|
481
|
+
logger.error(e);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
bindMethodsToInstance(instance) {
|
|
487
|
+
const proto = Object.getPrototypeOf(instance);
|
|
488
|
+
Object.getOwnPropertyNames(proto).forEach((key) => {
|
|
489
|
+
const property = instance[key];
|
|
490
|
+
if (typeof property === "function" && key !== "constructor") {
|
|
491
|
+
instance[key] = property.bind(instance);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
var customApiFactory = new CustomApiFactory();
|
|
497
|
+
var customApiFactory_default = customApiFactory;
|
|
498
|
+
|
|
499
|
+
// src/restura/generators/apiGenerator.ts
|
|
500
|
+
import { ObjectUtils, StringUtils } from "@redskytech/core-utils";
|
|
501
|
+
import prettier from "prettier";
|
|
502
|
+
|
|
203
503
|
// src/restura/sql/SqlUtils.ts
|
|
204
504
|
var SqlUtils = class _SqlUtils {
|
|
205
505
|
static convertDatabaseTypeToTypescript(type, value) {
|
|
206
506
|
type = type.toLocaleLowerCase();
|
|
207
507
|
if (type.startsWith("tinyint") || type.startsWith("boolean")) return "boolean";
|
|
208
|
-
if (type.indexOf("int") > -1 || type.startsWith("decimal") || type.startsWith("double") || type.startsWith("float"))
|
|
508
|
+
if (type.indexOf("int") > -1 || type.startsWith("decimal") || type.startsWith("double") || type.startsWith("float") || type.indexOf("serial") > -1 || type.startsWith("decimal") || type.startsWith("real") || type.startsWith("double precision") || type.startsWith("numeric"))
|
|
209
509
|
return "number";
|
|
210
510
|
if (type === "json") {
|
|
211
511
|
if (!value) return "object";
|
|
@@ -226,7 +526,7 @@ var SqlUtils = class _SqlUtils {
|
|
|
226
526
|
}
|
|
227
527
|
};
|
|
228
528
|
|
|
229
|
-
// src/restura/ResponseValidator.ts
|
|
529
|
+
// src/restura/validators/ResponseValidator.ts
|
|
230
530
|
var ResponseValidator = class _ResponseValidator {
|
|
231
531
|
constructor(schema) {
|
|
232
532
|
this.database = schema.database;
|
|
@@ -291,9 +591,9 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
291
591
|
return { validator: "any" };
|
|
292
592
|
}
|
|
293
593
|
getTypeFromTable(selector, name) {
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
296
|
-
const tableName =
|
|
594
|
+
const path5 = selector.split(".");
|
|
595
|
+
if (path5.length === 0 || path5.length > 2 || path5[0] === "") return { validator: "any", isOptional: false };
|
|
596
|
+
const tableName = path5.length == 2 ? path5[0] : name, columnName = path5.length == 2 ? path5[1] : path5[0];
|
|
297
597
|
const table = this.database.find((t) => t.name == tableName);
|
|
298
598
|
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
299
599
|
if (!table || !column) return { validator: "any", isOptional: false };
|
|
@@ -375,9 +675,7 @@ var ResponseValidator = class _ResponseValidator {
|
|
|
375
675
|
}
|
|
376
676
|
};
|
|
377
677
|
|
|
378
|
-
// src/restura/apiGenerator.ts
|
|
379
|
-
import { ObjectUtils, StringUtils } from "@redskytech/core-utils";
|
|
380
|
-
import prettier from "prettier";
|
|
678
|
+
// src/restura/generators/apiGenerator.ts
|
|
381
679
|
var ApiTree = class _ApiTree {
|
|
382
680
|
constructor(namespace, database) {
|
|
383
681
|
this.database = database;
|
|
@@ -475,7 +773,7 @@ var ApiTree = class _ApiTree {
|
|
|
475
773
|
break;
|
|
476
774
|
}
|
|
477
775
|
}
|
|
478
|
-
return `'${p.name}'${p.required ? "" : "?"}:${requestType}`;
|
|
776
|
+
return `'${p.name}'${p.required ? "" : "?"}:${requestType}${p.isNullable ? " | null" : ""}`;
|
|
479
777
|
}).join(";\n")}${ObjectUtils.isArrayWithData(route.request) ? ";" : ""}
|
|
480
778
|
`;
|
|
481
779
|
modelString += `}`;
|
|
@@ -489,30 +787,37 @@ var ApiTree = class _ApiTree {
|
|
|
489
787
|
return `export type Res = CustomTypes.${route.responseType}[]`;
|
|
490
788
|
else return `export type Res = CustomTypes.${route.responseType}`;
|
|
491
789
|
}
|
|
492
|
-
return `export interface Res ${this.getFields(route.response)}`;
|
|
790
|
+
return `export interface Res ${this.getFields(route.response, route.table, route.joins)}`;
|
|
493
791
|
}
|
|
494
|
-
getFields(fields) {
|
|
495
|
-
const nameFields = fields.map((f) => this.getNameAndType(f));
|
|
792
|
+
getFields(fields, routeBaseTable, joins) {
|
|
793
|
+
const nameFields = fields.map((f) => this.getNameAndType(f, routeBaseTable, joins));
|
|
496
794
|
const nested = `{
|
|
497
795
|
${nameFields.join(";\n ")}${ObjectUtils.isArrayWithData(nameFields) ? ";" : ""}
|
|
498
796
|
}`;
|
|
499
797
|
return nested;
|
|
500
798
|
}
|
|
501
|
-
getNameAndType(p) {
|
|
502
|
-
let responseType = "any",
|
|
799
|
+
getNameAndType(p, routeBaseTable, joins) {
|
|
800
|
+
let responseType = "any", isNullable = false, array = false;
|
|
503
801
|
if (p.selector) {
|
|
504
|
-
({ responseType,
|
|
802
|
+
({ responseType, isNullable } = this.getTypeFromTable(p.selector, p.name));
|
|
803
|
+
const selectorKey = p.selector.split(".")[0];
|
|
804
|
+
if (selectorKey !== routeBaseTable) {
|
|
805
|
+
const join = joins.find((j) => j.alias === selectorKey);
|
|
806
|
+
if (join && join.type !== "INNER") {
|
|
807
|
+
isNullable = true;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
505
810
|
} else if (p.subquery) {
|
|
506
|
-
responseType = this.getFields(p.subquery.properties);
|
|
811
|
+
responseType = this.getFields(p.subquery.properties, p.subquery.table, p.subquery.joins);
|
|
507
812
|
array = true;
|
|
508
813
|
}
|
|
509
|
-
return `${p.name}${
|
|
814
|
+
return `${p.name}:${responseType}${array ? "[]" : ""}${isNullable ? " | null" : ""}`;
|
|
510
815
|
}
|
|
511
816
|
getTypeFromTable(selector, name) {
|
|
512
|
-
const
|
|
513
|
-
if (
|
|
514
|
-
let tableName =
|
|
515
|
-
const columnName =
|
|
817
|
+
const path5 = selector.split(".");
|
|
818
|
+
if (path5.length === 0 || path5.length > 2 || path5[0] === "") return { responseType: "any", isNullable: false };
|
|
819
|
+
let tableName = path5.length == 2 ? path5[0] : name;
|
|
820
|
+
const columnName = path5.length == 2 ? path5[1] : path5[0];
|
|
516
821
|
let table = this.database.find((t) => t.name == tableName);
|
|
517
822
|
if (!table && tableName.includes("_")) {
|
|
518
823
|
const tableAliasSplit = tableName.split("_");
|
|
@@ -520,18 +825,19 @@ var ApiTree = class _ApiTree {
|
|
|
520
825
|
table = this.database.find((t) => t.name == tableName);
|
|
521
826
|
}
|
|
522
827
|
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
523
|
-
if (!table || !column) return { responseType: "any",
|
|
828
|
+
if (!table || !column) return { responseType: "any", isNullable: false };
|
|
524
829
|
return {
|
|
525
830
|
responseType: SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value),
|
|
526
|
-
|
|
831
|
+
isNullable: column.roles.length > 0 || column.isNullable
|
|
527
832
|
};
|
|
528
833
|
}
|
|
529
834
|
};
|
|
530
|
-
function pathToNamespaces(
|
|
531
|
-
return
|
|
835
|
+
function pathToNamespaces(path5) {
|
|
836
|
+
return path5.split("/").map((e) => StringUtils.toPascalCasing(e)).filter((e) => e);
|
|
532
837
|
}
|
|
533
|
-
function apiGenerator(schema
|
|
534
|
-
let apiString = `/** Auto generated file
|
|
838
|
+
function apiGenerator(schema) {
|
|
839
|
+
let apiString = `/** Auto generated file. DO NOT MODIFY **/
|
|
840
|
+
`;
|
|
535
841
|
const rootNamespace = ApiTree.createRootNode(schema.database);
|
|
536
842
|
for (const endpoint of schema.endpoints) {
|
|
537
843
|
const endpointNamespaces = pathToNamespaces(endpoint.baseUrl);
|
|
@@ -546,7 +852,7 @@ function apiGenerator(schema, schemaHash) {
|
|
|
546
852
|
apiString += `
|
|
547
853
|
|
|
548
854
|
declare namespace CustomTypes {
|
|
549
|
-
${schema.customTypes}
|
|
855
|
+
${schema.customTypes.join("\n")}
|
|
550
856
|
}`;
|
|
551
857
|
}
|
|
552
858
|
return prettier.format(apiString, __spreadValues({
|
|
@@ -561,6 +867,100 @@ function apiGenerator(schema, schemaHash) {
|
|
|
561
867
|
}));
|
|
562
868
|
}
|
|
563
869
|
|
|
870
|
+
// src/restura/generators/customTypeValidationGenerator.ts
|
|
871
|
+
import fs2 from "fs";
|
|
872
|
+
import path2, { resolve } from "path";
|
|
873
|
+
import tmp from "tmp";
|
|
874
|
+
import * as TJS from "typescript-json-schema";
|
|
875
|
+
function customTypeValidationGenerator(currentSchema) {
|
|
876
|
+
const schemaObject = {};
|
|
877
|
+
const customInterfaceNames = currentSchema.customTypes.map((customType) => {
|
|
878
|
+
const matches = customType.match(new RegExp("(?<=interface\\s)(\\w+)|(?<=type\\s)(\\w+)", "g"));
|
|
879
|
+
if (matches && matches.length > 0) return matches[0];
|
|
880
|
+
return "";
|
|
881
|
+
}).filter(Boolean);
|
|
882
|
+
if (!customInterfaceNames) return {};
|
|
883
|
+
const temporaryFile = tmp.fileSync({ mode: 420, prefix: "prefix-", postfix: ".ts" });
|
|
884
|
+
fs2.writeFileSync(temporaryFile.name, currentSchema.customTypes.join("\n"));
|
|
885
|
+
const compilerOptions = {
|
|
886
|
+
strictNullChecks: true,
|
|
887
|
+
skipLibCheck: true
|
|
888
|
+
// Needed if we are processing ES modules
|
|
889
|
+
};
|
|
890
|
+
const program = TJS.getProgramFromFiles(
|
|
891
|
+
[
|
|
892
|
+
resolve(temporaryFile.name),
|
|
893
|
+
path2.join(restura.resturaConfig.generatedTypesPath, "restura.d.ts"),
|
|
894
|
+
path2.join(restura.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
895
|
+
path2.join(restura.resturaConfig.generatedTypesPath, "api.d.ts")
|
|
896
|
+
],
|
|
897
|
+
compilerOptions
|
|
898
|
+
);
|
|
899
|
+
customInterfaceNames.forEach((item) => {
|
|
900
|
+
const ddlSchema = TJS.generateSchema(program, item, {
|
|
901
|
+
required: true
|
|
902
|
+
});
|
|
903
|
+
schemaObject[item] = ddlSchema || {};
|
|
904
|
+
});
|
|
905
|
+
temporaryFile.removeCallback();
|
|
906
|
+
return schemaObject;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// src/restura/generators/modelGenerator.ts
|
|
910
|
+
import { StringUtils as StringUtils2 } from "@redskytech/core-utils";
|
|
911
|
+
import prettier2 from "prettier";
|
|
912
|
+
function modelGenerator(schema) {
|
|
913
|
+
let modelString = `/** Auto generated file. DO NOT MODIFY **/
|
|
914
|
+
|
|
915
|
+
`;
|
|
916
|
+
modelString += `declare namespace Model {
|
|
917
|
+
`;
|
|
918
|
+
for (const table of schema.database) {
|
|
919
|
+
modelString += convertTable(table);
|
|
920
|
+
}
|
|
921
|
+
modelString += `}`;
|
|
922
|
+
return prettier2.format(modelString, __spreadValues({
|
|
923
|
+
parser: "typescript"
|
|
924
|
+
}, {
|
|
925
|
+
trailingComma: "none",
|
|
926
|
+
tabWidth: 4,
|
|
927
|
+
useTabs: true,
|
|
928
|
+
endOfLine: "lf",
|
|
929
|
+
printWidth: 120,
|
|
930
|
+
singleQuote: true
|
|
931
|
+
}));
|
|
932
|
+
}
|
|
933
|
+
function convertTable(table) {
|
|
934
|
+
let modelString = ` export interface ${StringUtils2.capitalizeFirst(table.name)} {
|
|
935
|
+
`;
|
|
936
|
+
for (const column of table.columns) {
|
|
937
|
+
modelString += ` ${column.name}${column.isNullable ? "?" : ""}: ${SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value)};
|
|
938
|
+
`;
|
|
939
|
+
}
|
|
940
|
+
modelString += ` }
|
|
941
|
+
`;
|
|
942
|
+
return modelString;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// src/restura/generators/resturaGlobalTypesGenerator.ts
|
|
946
|
+
function resturaGlobalTypesGenerator() {
|
|
947
|
+
return `/** Auto generated file. DO NOT MODIFY **/
|
|
948
|
+
/** This file contains types that may be used in the CustomTypes of Restura **/
|
|
949
|
+
/** For example export interface MyPagedQuery extends Restura.PageQuery { } **/
|
|
950
|
+
|
|
951
|
+
declare namespace Restura {
|
|
952
|
+
export type StandardOrderTypes = 'ASC' | 'DESC' | 'RAND' | 'NONE';
|
|
953
|
+
export interface PageQuery {
|
|
954
|
+
page?: number;
|
|
955
|
+
perPage?: number;
|
|
956
|
+
sortBy?: string;
|
|
957
|
+
sortOrder?: StandardOrderTypes;
|
|
958
|
+
filter?: string;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
`;
|
|
962
|
+
}
|
|
963
|
+
|
|
564
964
|
// src/restura/middleware/addApiResponseFunctions.ts
|
|
565
965
|
function addApiResponseFunctions(req, res, next) {
|
|
566
966
|
res.sendData = function(data, statusCode = 200) {
|
|
@@ -589,12 +989,45 @@ function addApiResponseFunctions(req, res, next) {
|
|
|
589
989
|
next();
|
|
590
990
|
}
|
|
591
991
|
|
|
592
|
-
// src/restura/
|
|
593
|
-
|
|
594
|
-
|
|
992
|
+
// src/restura/middleware/authenticateUser.ts
|
|
993
|
+
function authenticateUser(applicationAuthenticateHandler) {
|
|
994
|
+
return (req, res, next) => {
|
|
995
|
+
applicationAuthenticateHandler(req, res, (userDetails) => {
|
|
996
|
+
req.requesterDetails = __spreadValues({ host: req.hostname, ipAddress: req.ip || "" }, userDetails);
|
|
997
|
+
next();
|
|
998
|
+
});
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/restura/middleware/getMulterUpload.ts
|
|
1003
|
+
import multer from "multer";
|
|
1004
|
+
import * as os from "os";
|
|
1005
|
+
import { extname } from "path";
|
|
1006
|
+
var OneHundredMB = 100 * 1024 * 1024;
|
|
1007
|
+
var commonUpload = null;
|
|
1008
|
+
var getMulterUpload = (directory) => {
|
|
1009
|
+
if (commonUpload) return commonUpload;
|
|
1010
|
+
const storage = multer.diskStorage({
|
|
1011
|
+
destination: directory || os.tmpdir(),
|
|
1012
|
+
filename: function(request, file, cb) {
|
|
1013
|
+
const extension = extname(file.originalname);
|
|
1014
|
+
const uniqueName = Date.now() + "-" + Math.round(Math.random() * 1e3);
|
|
1015
|
+
cb(null, `${uniqueName}${extension}`);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
commonUpload = multer({
|
|
1019
|
+
storage,
|
|
1020
|
+
limits: {
|
|
1021
|
+
fileSize: OneHundredMB
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
return commonUpload;
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/restura/schemas/resturaSchema.ts
|
|
595
1028
|
import { z as z3 } from "zod";
|
|
596
1029
|
|
|
597
|
-
// src/restura/
|
|
1030
|
+
// src/restura/schemas/validatorDataSchema.ts
|
|
598
1031
|
import { z as z2 } from "zod";
|
|
599
1032
|
var validatorDataSchemeValue = z2.union([z2.string(), z2.array(z2.string()), z2.number(), z2.array(z2.number())]);
|
|
600
1033
|
var validatorDataSchema = z2.object({
|
|
@@ -602,264 +1035,84 @@ var validatorDataSchema = z2.object({
|
|
|
602
1035
|
value: validatorDataSchemeValue
|
|
603
1036
|
}).strict();
|
|
604
1037
|
|
|
605
|
-
// src/restura/
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const arrayWithQuotes = variable.map(addQuotesToStrings);
|
|
611
|
-
return arrayWithQuotes;
|
|
612
|
-
} else {
|
|
613
|
-
return variable;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// src/restura/validateRequestParams.ts
|
|
618
|
-
function validateRequestParams(req, routeData, validationSchema) {
|
|
619
|
-
const requestData = getRequestData(req);
|
|
620
|
-
req.data = requestData;
|
|
621
|
-
if (routeData.request === void 0) {
|
|
622
|
-
if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
|
|
623
|
-
throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
|
|
624
|
-
if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
|
|
625
|
-
if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
|
|
626
|
-
const currentInterface = validationSchema[routeData.requestType];
|
|
627
|
-
const validator = new jsonschema.Validator();
|
|
628
|
-
const executeValidation = validator.validate(req.data, currentInterface);
|
|
629
|
-
if (!executeValidation.valid) {
|
|
630
|
-
throw new RsError(
|
|
631
|
-
"BAD_REQUEST",
|
|
632
|
-
`Request custom setup has failed the following check: (${executeValidation.errors})`
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
Object.keys(req.data).forEach((requestParamName) => {
|
|
638
|
-
const requestParam = routeData.request.find((param) => param.name === requestParamName);
|
|
639
|
-
if (!requestParam) {
|
|
640
|
-
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
routeData.request.forEach((requestParam) => {
|
|
644
|
-
const requestValue = requestData[requestParam.name];
|
|
645
|
-
if (requestParam.required && requestValue === void 0)
|
|
646
|
-
throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
|
|
647
|
-
else if (!requestParam.required && requestValue === void 0) return;
|
|
648
|
-
validateRequestSingleParam(requestValue, requestParam);
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
function validateRequestSingleParam(requestValue, requestParam) {
|
|
652
|
-
requestParam.validator.forEach((validator) => {
|
|
653
|
-
switch (validator.type) {
|
|
654
|
-
case "TYPE_CHECK":
|
|
655
|
-
performTypeCheck(requestValue, validator, requestParam.name);
|
|
656
|
-
break;
|
|
657
|
-
case "MIN":
|
|
658
|
-
performMinCheck(requestValue, validator, requestParam.name);
|
|
659
|
-
break;
|
|
660
|
-
case "MAX":
|
|
661
|
-
performMaxCheck(requestValue, validator, requestParam.name);
|
|
662
|
-
break;
|
|
663
|
-
case "ONE_OF":
|
|
664
|
-
performOneOfCheck(requestValue, validator, requestParam.name);
|
|
665
|
-
break;
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
function isValidType(type, requestValue) {
|
|
670
|
-
try {
|
|
671
|
-
expectValidType(type, requestValue);
|
|
672
|
-
return true;
|
|
673
|
-
} catch (e) {
|
|
674
|
-
return false;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
function expectValidType(type, requestValue) {
|
|
678
|
-
if (type === "number") {
|
|
679
|
-
return z3.number().parse(requestValue);
|
|
680
|
-
}
|
|
681
|
-
if (type === "string") {
|
|
682
|
-
return z3.string().parse(requestValue);
|
|
683
|
-
}
|
|
684
|
-
if (type === "boolean") {
|
|
685
|
-
return z3.boolean().parse(requestValue);
|
|
686
|
-
}
|
|
687
|
-
if (type === "string[]") {
|
|
688
|
-
return z3.array(z3.string()).parse(requestValue);
|
|
689
|
-
}
|
|
690
|
-
if (type === "number[]") {
|
|
691
|
-
return z3.array(z3.number()).parse(requestValue);
|
|
692
|
-
}
|
|
693
|
-
if (type === "any[]") {
|
|
694
|
-
return z3.array(z3.any()).parse(requestValue);
|
|
695
|
-
}
|
|
696
|
-
if (type === "object") {
|
|
697
|
-
return z3.object({}).strict().parse(requestValue);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
function performTypeCheck(requestValue, validator, requestParamName) {
|
|
701
|
-
if (!isValidType(validator.value, requestValue)) {
|
|
702
|
-
throw new RsError(
|
|
703
|
-
"BAD_REQUEST",
|
|
704
|
-
`Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
|
|
705
|
-
);
|
|
706
|
-
}
|
|
707
|
-
try {
|
|
708
|
-
validatorDataSchemeValue.parse(validator.value);
|
|
709
|
-
} catch (e) {
|
|
710
|
-
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
function expectOnlyNumbers(requestValue, validator, requestParamName) {
|
|
714
|
-
if (!isValueNumber(requestValue))
|
|
715
|
-
throw new RsError(
|
|
716
|
-
"BAD_REQUEST",
|
|
717
|
-
`Request param (${requestParamName}) with value (${requestValue}) is not of type number`
|
|
718
|
-
);
|
|
719
|
-
if (!isValueNumber(validator.value))
|
|
720
|
-
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
|
|
721
|
-
}
|
|
722
|
-
function performMinCheck(requestValue, validator, requestParamName) {
|
|
723
|
-
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
724
|
-
if (requestValue < validator.value)
|
|
725
|
-
throw new RsError(
|
|
726
|
-
"BAD_REQUEST",
|
|
727
|
-
`Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
function performMaxCheck(requestValue, validator, requestParamName) {
|
|
731
|
-
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
732
|
-
if (requestValue > validator.value)
|
|
733
|
-
throw new RsError(
|
|
734
|
-
"BAD_REQUEST",
|
|
735
|
-
`Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
function performOneOfCheck(requestValue, validator, requestParamName) {
|
|
739
|
-
if (!ObjectUtils2.isArrayWithData(validator.value))
|
|
740
|
-
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
|
|
741
|
-
if (typeof requestValue === "object")
|
|
742
|
-
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
|
|
743
|
-
if (!validator.value.includes(requestValue))
|
|
744
|
-
throw new RsError(
|
|
745
|
-
"BAD_REQUEST",
|
|
746
|
-
`Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
function isValueNumber(value) {
|
|
750
|
-
return !isNaN(Number(value));
|
|
751
|
-
}
|
|
752
|
-
function getRequestData(req) {
|
|
753
|
-
let body = "";
|
|
754
|
-
if (req.method === "GET" || req.method === "DELETE") {
|
|
755
|
-
body = "query";
|
|
756
|
-
} else {
|
|
757
|
-
body = "body";
|
|
758
|
-
}
|
|
759
|
-
const bodyData = req[body];
|
|
760
|
-
if (bodyData) {
|
|
761
|
-
for (const attr in bodyData) {
|
|
762
|
-
if (attr === "token") {
|
|
763
|
-
delete bodyData[attr];
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (bodyData[attr] instanceof Array) {
|
|
767
|
-
const attrList = [];
|
|
768
|
-
for (const value of bodyData[attr]) {
|
|
769
|
-
if (isNaN(Number(value))) continue;
|
|
770
|
-
attrList.push(Number(value));
|
|
771
|
-
}
|
|
772
|
-
if (ObjectUtils2.isArrayWithData(attrList)) {
|
|
773
|
-
bodyData[attr] = attrList;
|
|
774
|
-
}
|
|
775
|
-
} else {
|
|
776
|
-
bodyData[attr] = ObjectUtils2.safeParse(bodyData[attr]);
|
|
777
|
-
if (isNaN(Number(bodyData[attr]))) continue;
|
|
778
|
-
bodyData[attr] = Number(bodyData[attr]);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
return bodyData;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// src/restura/restura.schema.ts
|
|
786
|
-
import { z as z4 } from "zod";
|
|
787
|
-
var orderBySchema = z4.object({
|
|
788
|
-
columnName: z4.string(),
|
|
789
|
-
order: z4.enum(["ASC", "DESC"]),
|
|
790
|
-
tableName: z4.string()
|
|
1038
|
+
// src/restura/schemas/resturaSchema.ts
|
|
1039
|
+
var orderBySchema = z3.object({
|
|
1040
|
+
columnName: z3.string(),
|
|
1041
|
+
order: z3.enum(["ASC", "DESC"]),
|
|
1042
|
+
tableName: z3.string()
|
|
791
1043
|
}).strict();
|
|
792
|
-
var groupBySchema =
|
|
793
|
-
columnName:
|
|
794
|
-
tableName:
|
|
1044
|
+
var groupBySchema = z3.object({
|
|
1045
|
+
columnName: z3.string(),
|
|
1046
|
+
tableName: z3.string()
|
|
795
1047
|
}).strict();
|
|
796
|
-
var whereDataSchema =
|
|
797
|
-
tableName:
|
|
798
|
-
columnName:
|
|
799
|
-
operator:
|
|
800
|
-
value:
|
|
801
|
-
custom:
|
|
802
|
-
conjunction:
|
|
1048
|
+
var whereDataSchema = z3.object({
|
|
1049
|
+
tableName: z3.string().optional(),
|
|
1050
|
+
columnName: z3.string().optional(),
|
|
1051
|
+
operator: z3.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH", "IS", "IS NOT"]).optional(),
|
|
1052
|
+
value: z3.string().or(z3.number()).optional(),
|
|
1053
|
+
custom: z3.string().optional(),
|
|
1054
|
+
conjunction: z3.enum(["AND", "OR"]).optional()
|
|
803
1055
|
}).strict();
|
|
804
|
-
var assignmentDataSchema =
|
|
805
|
-
name:
|
|
806
|
-
value:
|
|
1056
|
+
var assignmentDataSchema = z3.object({
|
|
1057
|
+
name: z3.string(),
|
|
1058
|
+
value: z3.string()
|
|
807
1059
|
}).strict();
|
|
808
|
-
var joinDataSchema =
|
|
809
|
-
table:
|
|
810
|
-
localColumnName:
|
|
811
|
-
foreignColumnName:
|
|
812
|
-
custom:
|
|
813
|
-
type:
|
|
814
|
-
alias:
|
|
1060
|
+
var joinDataSchema = z3.object({
|
|
1061
|
+
table: z3.string(),
|
|
1062
|
+
localColumnName: z3.string().optional(),
|
|
1063
|
+
foreignColumnName: z3.string().optional(),
|
|
1064
|
+
custom: z3.string().optional(),
|
|
1065
|
+
type: z3.enum(["LEFT", "INNER"]),
|
|
1066
|
+
alias: z3.string().optional()
|
|
815
1067
|
}).strict();
|
|
816
|
-
var requestDataSchema =
|
|
817
|
-
name:
|
|
818
|
-
required:
|
|
819
|
-
|
|
1068
|
+
var requestDataSchema = z3.object({
|
|
1069
|
+
name: z3.string(),
|
|
1070
|
+
required: z3.boolean(),
|
|
1071
|
+
isNullable: z3.boolean().optional(),
|
|
1072
|
+
validator: z3.array(validatorDataSchema)
|
|
820
1073
|
}).strict();
|
|
821
|
-
var responseDataSchema =
|
|
822
|
-
name:
|
|
823
|
-
selector:
|
|
824
|
-
subquery:
|
|
825
|
-
table:
|
|
826
|
-
joins:
|
|
827
|
-
where:
|
|
828
|
-
properties:
|
|
1074
|
+
var responseDataSchema = z3.object({
|
|
1075
|
+
name: z3.string(),
|
|
1076
|
+
selector: z3.string().optional(),
|
|
1077
|
+
subquery: z3.object({
|
|
1078
|
+
table: z3.string(),
|
|
1079
|
+
joins: z3.array(joinDataSchema),
|
|
1080
|
+
where: z3.array(whereDataSchema),
|
|
1081
|
+
properties: z3.array(z3.lazy(() => responseDataSchema)),
|
|
829
1082
|
// Explicit type for the lazy schema
|
|
830
1083
|
groupBy: groupBySchema.optional(),
|
|
831
1084
|
orderBy: orderBySchema.optional()
|
|
832
1085
|
}).optional()
|
|
833
1086
|
}).strict();
|
|
834
|
-
var routeDataBaseSchema =
|
|
835
|
-
method:
|
|
836
|
-
name:
|
|
837
|
-
description:
|
|
838
|
-
path:
|
|
839
|
-
roles:
|
|
1087
|
+
var routeDataBaseSchema = z3.object({
|
|
1088
|
+
method: z3.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
1089
|
+
name: z3.string(),
|
|
1090
|
+
description: z3.string(),
|
|
1091
|
+
path: z3.string(),
|
|
1092
|
+
roles: z3.array(z3.string())
|
|
840
1093
|
}).strict();
|
|
841
1094
|
var standardRouteSchema = routeDataBaseSchema.extend({
|
|
842
|
-
type:
|
|
843
|
-
table:
|
|
844
|
-
joins:
|
|
845
|
-
assignments:
|
|
846
|
-
where:
|
|
847
|
-
request:
|
|
848
|
-
response:
|
|
1095
|
+
type: z3.enum(["ONE", "ARRAY", "PAGED"]),
|
|
1096
|
+
table: z3.string(),
|
|
1097
|
+
joins: z3.array(joinDataSchema),
|
|
1098
|
+
assignments: z3.array(assignmentDataSchema),
|
|
1099
|
+
where: z3.array(whereDataSchema),
|
|
1100
|
+
request: z3.array(requestDataSchema),
|
|
1101
|
+
response: z3.array(responseDataSchema),
|
|
849
1102
|
groupBy: groupBySchema.optional(),
|
|
850
1103
|
orderBy: orderBySchema.optional()
|
|
851
1104
|
}).strict();
|
|
852
1105
|
var customRouteSchema = routeDataBaseSchema.extend({
|
|
853
|
-
type:
|
|
854
|
-
responseType:
|
|
855
|
-
requestType:
|
|
856
|
-
request:
|
|
857
|
-
table:
|
|
858
|
-
joins:
|
|
859
|
-
assignments:
|
|
860
|
-
fileUploadType:
|
|
1106
|
+
type: z3.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
|
|
1107
|
+
responseType: z3.union([z3.string(), z3.enum(["string", "number", "boolean"])]),
|
|
1108
|
+
requestType: z3.string().optional(),
|
|
1109
|
+
request: z3.array(requestDataSchema).optional(),
|
|
1110
|
+
table: z3.undefined(),
|
|
1111
|
+
joins: z3.undefined(),
|
|
1112
|
+
assignments: z3.undefined(),
|
|
1113
|
+
fileUploadType: z3.enum(["SINGLE", "MULTIPLE"]).optional()
|
|
861
1114
|
}).strict();
|
|
862
|
-
var postgresColumnNumericTypesSchema =
|
|
1115
|
+
var postgresColumnNumericTypesSchema = z3.enum([
|
|
863
1116
|
"SMALLINT",
|
|
864
1117
|
// 2 bytes, -32,768 to 32,767
|
|
865
1118
|
"INTEGER",
|
|
@@ -879,7 +1132,7 @@ var postgresColumnNumericTypesSchema = z4.enum([
|
|
|
879
1132
|
"BIGSERIAL"
|
|
880
1133
|
// auto-incrementing big integer
|
|
881
1134
|
]);
|
|
882
|
-
var postgresColumnStringTypesSchema =
|
|
1135
|
+
var postgresColumnStringTypesSchema = z3.enum([
|
|
883
1136
|
"CHAR",
|
|
884
1137
|
// fixed-length, blank-padded
|
|
885
1138
|
"VARCHAR",
|
|
@@ -889,7 +1142,7 @@ var postgresColumnStringTypesSchema = z4.enum([
|
|
|
889
1142
|
"BYTEA"
|
|
890
1143
|
// binary data
|
|
891
1144
|
]);
|
|
892
|
-
var postgresColumnDateTypesSchema =
|
|
1145
|
+
var postgresColumnDateTypesSchema = z3.enum([
|
|
893
1146
|
"DATE",
|
|
894
1147
|
// calendar date (year, month, day)
|
|
895
1148
|
"TIMESTAMP",
|
|
@@ -901,7 +1154,13 @@ var postgresColumnDateTypesSchema = z4.enum([
|
|
|
901
1154
|
"INTERVAL"
|
|
902
1155
|
// time span
|
|
903
1156
|
]);
|
|
904
|
-
var
|
|
1157
|
+
var postgresColumnJsonTypesSchema = z3.enum([
|
|
1158
|
+
"JSON",
|
|
1159
|
+
// stores JSON data as raw text
|
|
1160
|
+
"JSONB"
|
|
1161
|
+
// stores JSON data in a binary format, optimized for query performance
|
|
1162
|
+
]);
|
|
1163
|
+
var mariaDbColumnNumericTypesSchema = z3.enum([
|
|
905
1164
|
"BOOLEAN",
|
|
906
1165
|
// 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
|
|
907
1166
|
"TINYINT",
|
|
@@ -921,7 +1180,7 @@ var mariaDbColumnNumericTypesSchema = z4.enum([
|
|
|
921
1180
|
"DOUBLE"
|
|
922
1181
|
// 8 bytes Stored in 64-bit IEEE-754 floating point format. As such, the number of significant digits is about 15 and the range of values is approximately +/-1e308.
|
|
923
1182
|
]);
|
|
924
|
-
var mariaDbColumnStringTypesSchema =
|
|
1183
|
+
var mariaDbColumnStringTypesSchema = z3.enum([
|
|
925
1184
|
"CHAR",
|
|
926
1185
|
// 1, 2, 4, or 8 bytes Holds letters and special characters of fixed length. Max length is 255. Default and minimum size is 1 byte.
|
|
927
1186
|
"VARCHAR",
|
|
@@ -947,7 +1206,7 @@ var mariaDbColumnStringTypesSchema = z4.enum([
|
|
|
947
1206
|
"ENUM"
|
|
948
1207
|
// Enum type
|
|
949
1208
|
]);
|
|
950
|
-
var mariaDbColumnDateTypesSchema =
|
|
1209
|
+
var mariaDbColumnDateTypesSchema = z3.enum([
|
|
951
1210
|
"DATE",
|
|
952
1211
|
// 4-bytes Date has year, month, and day.
|
|
953
1212
|
"DATETIME",
|
|
@@ -957,34 +1216,35 @@ var mariaDbColumnDateTypesSchema = z4.enum([
|
|
|
957
1216
|
"TIMESTAMP"
|
|
958
1217
|
// 4-bytes Values are stored as the number of seconds since 1970-01-01 00:00:00 UTC, and optionally microseconds.
|
|
959
1218
|
]);
|
|
960
|
-
var columnDataSchema =
|
|
961
|
-
name:
|
|
962
|
-
type:
|
|
1219
|
+
var columnDataSchema = z3.object({
|
|
1220
|
+
name: z3.string(),
|
|
1221
|
+
type: z3.union([
|
|
963
1222
|
postgresColumnNumericTypesSchema,
|
|
964
1223
|
postgresColumnStringTypesSchema,
|
|
965
1224
|
postgresColumnDateTypesSchema,
|
|
1225
|
+
postgresColumnJsonTypesSchema,
|
|
966
1226
|
mariaDbColumnNumericTypesSchema,
|
|
967
1227
|
mariaDbColumnStringTypesSchema,
|
|
968
1228
|
mariaDbColumnDateTypesSchema
|
|
969
1229
|
]),
|
|
970
|
-
isNullable:
|
|
971
|
-
roles:
|
|
972
|
-
comment:
|
|
973
|
-
default:
|
|
974
|
-
value:
|
|
975
|
-
isPrimary:
|
|
976
|
-
isUnique:
|
|
977
|
-
hasAutoIncrement:
|
|
978
|
-
length:
|
|
1230
|
+
isNullable: z3.boolean(),
|
|
1231
|
+
roles: z3.array(z3.string()),
|
|
1232
|
+
comment: z3.string().optional(),
|
|
1233
|
+
default: z3.string().optional(),
|
|
1234
|
+
value: z3.string().optional(),
|
|
1235
|
+
isPrimary: z3.boolean().optional(),
|
|
1236
|
+
isUnique: z3.boolean().optional(),
|
|
1237
|
+
hasAutoIncrement: z3.boolean().optional(),
|
|
1238
|
+
length: z3.number().optional()
|
|
979
1239
|
}).strict();
|
|
980
|
-
var indexDataSchema =
|
|
981
|
-
name:
|
|
982
|
-
columns:
|
|
983
|
-
isUnique:
|
|
984
|
-
isPrimaryKey:
|
|
985
|
-
order:
|
|
1240
|
+
var indexDataSchema = z3.object({
|
|
1241
|
+
name: z3.string(),
|
|
1242
|
+
columns: z3.array(z3.string()),
|
|
1243
|
+
isUnique: z3.boolean(),
|
|
1244
|
+
isPrimaryKey: z3.boolean(),
|
|
1245
|
+
order: z3.enum(["ASC", "DESC"])
|
|
986
1246
|
}).strict();
|
|
987
|
-
var foreignKeyActionsSchema =
|
|
1247
|
+
var foreignKeyActionsSchema = z3.enum([
|
|
988
1248
|
"CASCADE",
|
|
989
1249
|
// CASCADE action for foreign keys
|
|
990
1250
|
"SET NULL",
|
|
@@ -996,42 +1256,43 @@ var foreignKeyActionsSchema = z4.enum([
|
|
|
996
1256
|
"SET DEFAULT"
|
|
997
1257
|
// SET DEFAULT action for foreign keys
|
|
998
1258
|
]);
|
|
999
|
-
var foreignKeyDataSchema =
|
|
1000
|
-
name:
|
|
1001
|
-
column:
|
|
1002
|
-
refTable:
|
|
1003
|
-
refColumn:
|
|
1259
|
+
var foreignKeyDataSchema = z3.object({
|
|
1260
|
+
name: z3.string(),
|
|
1261
|
+
column: z3.string(),
|
|
1262
|
+
refTable: z3.string(),
|
|
1263
|
+
refColumn: z3.string(),
|
|
1004
1264
|
onDelete: foreignKeyActionsSchema,
|
|
1005
1265
|
onUpdate: foreignKeyActionsSchema
|
|
1006
1266
|
}).strict();
|
|
1007
|
-
var checkConstraintDataSchema =
|
|
1008
|
-
name:
|
|
1009
|
-
check:
|
|
1267
|
+
var checkConstraintDataSchema = z3.object({
|
|
1268
|
+
name: z3.string(),
|
|
1269
|
+
check: z3.string()
|
|
1010
1270
|
}).strict();
|
|
1011
|
-
var tableDataSchema =
|
|
1012
|
-
name:
|
|
1013
|
-
columns:
|
|
1014
|
-
indexes:
|
|
1015
|
-
foreignKeys:
|
|
1016
|
-
checkConstraints:
|
|
1017
|
-
roles:
|
|
1271
|
+
var tableDataSchema = z3.object({
|
|
1272
|
+
name: z3.string(),
|
|
1273
|
+
columns: z3.array(columnDataSchema),
|
|
1274
|
+
indexes: z3.array(indexDataSchema),
|
|
1275
|
+
foreignKeys: z3.array(foreignKeyDataSchema),
|
|
1276
|
+
checkConstraints: z3.array(checkConstraintDataSchema),
|
|
1277
|
+
roles: z3.array(z3.string()),
|
|
1278
|
+
notify: z3.union([z3.literal("ALL"), z3.array(z3.string())]).optional()
|
|
1018
1279
|
}).strict();
|
|
1019
|
-
var endpointDataSchema =
|
|
1020
|
-
name:
|
|
1021
|
-
description:
|
|
1022
|
-
baseUrl:
|
|
1023
|
-
routes:
|
|
1280
|
+
var endpointDataSchema = z3.object({
|
|
1281
|
+
name: z3.string(),
|
|
1282
|
+
description: z3.string(),
|
|
1283
|
+
baseUrl: z3.string(),
|
|
1284
|
+
routes: z3.array(z3.union([standardRouteSchema, customRouteSchema]))
|
|
1024
1285
|
}).strict();
|
|
1025
|
-
var
|
|
1026
|
-
database:
|
|
1027
|
-
endpoints:
|
|
1028
|
-
globalParams:
|
|
1029
|
-
roles:
|
|
1030
|
-
customTypes:
|
|
1286
|
+
var resturaSchema = z3.object({
|
|
1287
|
+
database: z3.array(tableDataSchema),
|
|
1288
|
+
endpoints: z3.array(endpointDataSchema),
|
|
1289
|
+
globalParams: z3.array(z3.string()),
|
|
1290
|
+
roles: z3.array(z3.string()),
|
|
1291
|
+
customTypes: z3.array(z3.string())
|
|
1031
1292
|
}).strict();
|
|
1032
1293
|
async function isSchemaValid(schemaToCheck) {
|
|
1033
1294
|
try {
|
|
1034
|
-
|
|
1295
|
+
resturaSchema.parse(schemaToCheck);
|
|
1035
1296
|
return true;
|
|
1036
1297
|
} catch (error) {
|
|
1037
1298
|
logger.error(error);
|
|
@@ -1039,11 +1300,210 @@ async function isSchemaValid(schemaToCheck) {
|
|
|
1039
1300
|
}
|
|
1040
1301
|
}
|
|
1041
1302
|
|
|
1303
|
+
// src/restura/validators/requestValidator.ts
|
|
1304
|
+
import { ObjectUtils as ObjectUtils2 } from "@redskytech/core-utils";
|
|
1305
|
+
import jsonschema from "jsonschema";
|
|
1306
|
+
import { z as z4 } from "zod";
|
|
1307
|
+
|
|
1308
|
+
// src/restura/utils/utils.ts
|
|
1309
|
+
function addQuotesToStrings(variable) {
|
|
1310
|
+
if (typeof variable === "string") {
|
|
1311
|
+
return `'${variable}'`;
|
|
1312
|
+
} else if (Array.isArray(variable)) {
|
|
1313
|
+
const arrayWithQuotes = variable.map(addQuotesToStrings);
|
|
1314
|
+
return arrayWithQuotes;
|
|
1315
|
+
} else {
|
|
1316
|
+
return variable;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
function sortObjectKeysAlphabetically(obj) {
|
|
1320
|
+
if (Array.isArray(obj)) {
|
|
1321
|
+
return obj.map(sortObjectKeysAlphabetically);
|
|
1322
|
+
} else if (obj !== null && typeof obj === "object") {
|
|
1323
|
+
return Object.keys(obj).sort().reduce((sorted, key) => {
|
|
1324
|
+
sorted[key] = sortObjectKeysAlphabetically(obj[key]);
|
|
1325
|
+
return sorted;
|
|
1326
|
+
}, {});
|
|
1327
|
+
}
|
|
1328
|
+
return obj;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// src/restura/validators/requestValidator.ts
|
|
1332
|
+
function requestValidator(req, routeData, validationSchema) {
|
|
1333
|
+
const requestData = getRequestData(req);
|
|
1334
|
+
req.data = requestData;
|
|
1335
|
+
if (routeData.request === void 0) {
|
|
1336
|
+
if (routeData.type !== "CUSTOM_ONE" && routeData.type !== "CUSTOM_ARRAY" && routeData.type !== "CUSTOM_PAGED")
|
|
1337
|
+
throw new RsError("BAD_REQUEST", `No request parameters provided for standard request.`);
|
|
1338
|
+
if (!routeData.responseType) throw new RsError("BAD_REQUEST", `No response type defined for custom request.`);
|
|
1339
|
+
if (!routeData.requestType) throw new RsError("BAD_REQUEST", `No request type defined for custom request.`);
|
|
1340
|
+
const currentInterface = validationSchema[routeData.requestType];
|
|
1341
|
+
const validator = new jsonschema.Validator();
|
|
1342
|
+
const executeValidation = validator.validate(req.data, currentInterface);
|
|
1343
|
+
if (!executeValidation.valid) {
|
|
1344
|
+
throw new RsError(
|
|
1345
|
+
"BAD_REQUEST",
|
|
1346
|
+
`Request custom setup has failed the following check: (${executeValidation.errors})`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
Object.keys(req.data).forEach((requestParamName) => {
|
|
1352
|
+
const requestParam = routeData.request.find((param) => param.name === requestParamName);
|
|
1353
|
+
if (!requestParam) {
|
|
1354
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not allowed`);
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
routeData.request.forEach((requestParam) => {
|
|
1358
|
+
const requestValue = requestData[requestParam.name];
|
|
1359
|
+
if (requestParam.required && requestValue === void 0)
|
|
1360
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParam.name}) is required but missing`);
|
|
1361
|
+
else if (!requestParam.required && requestValue === void 0) return;
|
|
1362
|
+
validateRequestSingleParam(requestValue, requestParam);
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
function validateRequestSingleParam(requestValue, requestParam) {
|
|
1366
|
+
if (requestParam.isNullable && requestValue === null) return;
|
|
1367
|
+
requestParam.validator.forEach((validator) => {
|
|
1368
|
+
switch (validator.type) {
|
|
1369
|
+
case "TYPE_CHECK":
|
|
1370
|
+
performTypeCheck(requestValue, validator, requestParam.name);
|
|
1371
|
+
break;
|
|
1372
|
+
case "MIN":
|
|
1373
|
+
performMinCheck(requestValue, validator, requestParam.name);
|
|
1374
|
+
break;
|
|
1375
|
+
case "MAX":
|
|
1376
|
+
performMaxCheck(requestValue, validator, requestParam.name);
|
|
1377
|
+
break;
|
|
1378
|
+
case "ONE_OF":
|
|
1379
|
+
performOneOfCheck(requestValue, validator, requestParam.name);
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
function isValidType(type, requestValue) {
|
|
1385
|
+
try {
|
|
1386
|
+
expectValidType(type, requestValue);
|
|
1387
|
+
return true;
|
|
1388
|
+
} catch (e) {
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
function expectValidType(type, requestValue) {
|
|
1393
|
+
if (type === "number") {
|
|
1394
|
+
return z4.number().parse(requestValue);
|
|
1395
|
+
}
|
|
1396
|
+
if (type === "string") {
|
|
1397
|
+
return z4.string().parse(requestValue);
|
|
1398
|
+
}
|
|
1399
|
+
if (type === "boolean") {
|
|
1400
|
+
return z4.boolean().parse(requestValue);
|
|
1401
|
+
}
|
|
1402
|
+
if (type === "string[]") {
|
|
1403
|
+
return z4.array(z4.string()).parse(requestValue);
|
|
1404
|
+
}
|
|
1405
|
+
if (type === "number[]") {
|
|
1406
|
+
return z4.array(z4.number()).parse(requestValue);
|
|
1407
|
+
}
|
|
1408
|
+
if (type === "any[]") {
|
|
1409
|
+
return z4.array(z4.any()).parse(requestValue);
|
|
1410
|
+
}
|
|
1411
|
+
if (type === "object") {
|
|
1412
|
+
return z4.object({}).strict().parse(requestValue);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function performTypeCheck(requestValue, validator, requestParamName) {
|
|
1416
|
+
if (!isValidType(validator.value, requestValue)) {
|
|
1417
|
+
throw new RsError(
|
|
1418
|
+
"BAD_REQUEST",
|
|
1419
|
+
`Request param (${requestParamName}) with value (${addQuotesToStrings(requestValue)}) is not of type (${validator.value})`
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
try {
|
|
1423
|
+
validatorDataSchemeValue.parse(validator.value);
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
function expectOnlyNumbers(requestValue, validator, requestParamName) {
|
|
1429
|
+
if (!isValueNumber(requestValue))
|
|
1430
|
+
throw new RsError(
|
|
1431
|
+
"BAD_REQUEST",
|
|
1432
|
+
`Request param (${requestParamName}) with value (${requestValue}) is not of type number`
|
|
1433
|
+
);
|
|
1434
|
+
if (!isValueNumber(validator.value))
|
|
1435
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value} is not of type number`);
|
|
1436
|
+
}
|
|
1437
|
+
function performMinCheck(requestValue, validator, requestParamName) {
|
|
1438
|
+
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
1439
|
+
if (requestValue < validator.value)
|
|
1440
|
+
throw new RsError(
|
|
1441
|
+
"BAD_REQUEST",
|
|
1442
|
+
`Request param (${requestParamName}) with value (${requestValue}) is less than (${validator.value})`
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
function performMaxCheck(requestValue, validator, requestParamName) {
|
|
1446
|
+
expectOnlyNumbers(requestValue, validator, requestParamName);
|
|
1447
|
+
if (requestValue > validator.value)
|
|
1448
|
+
throw new RsError(
|
|
1449
|
+
"BAD_REQUEST",
|
|
1450
|
+
`Request param (${requestParamName}) with value (${requestValue}) is more than (${validator.value})`
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
function performOneOfCheck(requestValue, validator, requestParamName) {
|
|
1454
|
+
if (!ObjectUtils2.isArrayWithData(validator.value))
|
|
1455
|
+
throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not of type array`);
|
|
1456
|
+
if (typeof requestValue === "object")
|
|
1457
|
+
throw new RsError("BAD_REQUEST", `Request param (${requestParamName}) is not of type string or number`);
|
|
1458
|
+
if (!validator.value.includes(requestValue))
|
|
1459
|
+
throw new RsError(
|
|
1460
|
+
"BAD_REQUEST",
|
|
1461
|
+
`Request param (${requestParamName}) with value (${requestValue}) is not one of (${validator.value.join(", ")})`
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
function isValueNumber(value) {
|
|
1465
|
+
return !isNaN(Number(value));
|
|
1466
|
+
}
|
|
1467
|
+
function getRequestData(req) {
|
|
1468
|
+
let body = "";
|
|
1469
|
+
if (req.method === "GET" || req.method === "DELETE") {
|
|
1470
|
+
body = "query";
|
|
1471
|
+
} else {
|
|
1472
|
+
body = "body";
|
|
1473
|
+
}
|
|
1474
|
+
const bodyData = req[body];
|
|
1475
|
+
if (bodyData && body === "query") {
|
|
1476
|
+
for (const attr in bodyData) {
|
|
1477
|
+
if (bodyData[attr] instanceof Array) {
|
|
1478
|
+
const attrList = [];
|
|
1479
|
+
for (const value of bodyData[attr]) {
|
|
1480
|
+
if (isNaN(Number(value))) continue;
|
|
1481
|
+
attrList.push(Number(value));
|
|
1482
|
+
}
|
|
1483
|
+
if (ObjectUtils2.isArrayWithData(attrList)) {
|
|
1484
|
+
bodyData[attr] = attrList;
|
|
1485
|
+
}
|
|
1486
|
+
} else {
|
|
1487
|
+
if (bodyData[attr] === "true") {
|
|
1488
|
+
bodyData[attr] = true;
|
|
1489
|
+
} else if (bodyData[attr] === "false") {
|
|
1490
|
+
bodyData[attr] = false;
|
|
1491
|
+
} else {
|
|
1492
|
+
bodyData[attr] = ObjectUtils2.safeParse(bodyData[attr]);
|
|
1493
|
+
if (isNaN(Number(bodyData[attr]))) continue;
|
|
1494
|
+
bodyData[attr] = Number(bodyData[attr]);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return bodyData;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1042
1502
|
// src/restura/middleware/schemaValidation.ts
|
|
1043
1503
|
async function schemaValidation(req, res, next) {
|
|
1044
1504
|
req.data = getRequestData(req);
|
|
1045
1505
|
try {
|
|
1046
|
-
|
|
1506
|
+
resturaSchema.parse(req.data);
|
|
1047
1507
|
next();
|
|
1048
1508
|
} catch (error) {
|
|
1049
1509
|
logger.error(error);
|
|
@@ -1051,91 +1511,183 @@ async function schemaValidation(req, res, next) {
|
|
|
1051
1511
|
}
|
|
1052
1512
|
}
|
|
1053
1513
|
|
|
1054
|
-
// src/restura/
|
|
1055
|
-
import {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1514
|
+
// src/restura/schemas/resturaConfigSchema.ts
|
|
1515
|
+
import { z as z5 } from "zod";
|
|
1516
|
+
var _a;
|
|
1517
|
+
var isTsx = (_a = process.argv[1]) == null ? void 0 : _a.endsWith(".ts");
|
|
1518
|
+
var isTsNode = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
|
|
1519
|
+
var customApiFolderPath = isTsx || isTsNode ? "/src/api" : "/dist/api";
|
|
1520
|
+
var resturaConfigSchema = z5.object({
|
|
1521
|
+
authToken: z5.string().min(1, "Missing Restura Auth Token"),
|
|
1522
|
+
sendErrorStackTrace: z5.boolean().default(false),
|
|
1523
|
+
schemaFilePath: z5.string().default(process.cwd() + "/restura.schema.json"),
|
|
1524
|
+
customApiFolderPath: z5.string().default(process.cwd() + customApiFolderPath),
|
|
1525
|
+
generatedTypesPath: z5.string().default(process.cwd() + "/src/@types"),
|
|
1526
|
+
fileTempCachePath: z5.string().optional()
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
// src/restura/sql/PsqlEngine.ts
|
|
1530
|
+
import { ObjectUtils as ObjectUtils4 } from "@redskytech/core-utils";
|
|
1531
|
+
import getDiff from "@wmfs/pg-diff-sync";
|
|
1532
|
+
import pgInfo from "@wmfs/pg-info";
|
|
1533
|
+
import pg2 from "pg";
|
|
1534
|
+
|
|
1535
|
+
// src/restura/sql/PsqlPool.ts
|
|
1536
|
+
import pg from "pg";
|
|
1537
|
+
|
|
1538
|
+
// src/restura/sql/PsqlConnection.ts
|
|
1539
|
+
import crypto from "crypto";
|
|
1540
|
+
import format3 from "pg-format";
|
|
1541
|
+
|
|
1542
|
+
// src/restura/sql/PsqlUtils.ts
|
|
1543
|
+
import format2 from "pg-format";
|
|
1544
|
+
function escapeColumnName(columnName) {
|
|
1545
|
+
if (columnName === void 0) return "";
|
|
1546
|
+
return `"${columnName.replace(/"/g, "")}"`.replace(".", '"."');
|
|
1076
1547
|
}
|
|
1077
|
-
function
|
|
1078
|
-
let
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1548
|
+
function questionMarksToOrderedParams(query) {
|
|
1549
|
+
let count = 1;
|
|
1550
|
+
let inSingleQuote = false;
|
|
1551
|
+
let inDoubleQuote = false;
|
|
1552
|
+
return query.replace(/('|"|\?)/g, (char) => {
|
|
1553
|
+
if (char === "'") {
|
|
1554
|
+
inSingleQuote = !inSingleQuote && !inDoubleQuote;
|
|
1555
|
+
return char;
|
|
1556
|
+
}
|
|
1557
|
+
if (char === '"') {
|
|
1558
|
+
inDoubleQuote = !inDoubleQuote && !inSingleQuote;
|
|
1559
|
+
return char;
|
|
1560
|
+
}
|
|
1561
|
+
if (char === "?" && !inSingleQuote && !inDoubleQuote) {
|
|
1562
|
+
return `$${count++}`;
|
|
1563
|
+
}
|
|
1564
|
+
return char;
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
function insertObjectQuery(table, obj) {
|
|
1568
|
+
const keys = Object.keys(obj);
|
|
1569
|
+
const params = Object.values(obj);
|
|
1570
|
+
const columns = keys.map((column) => escapeColumnName(column)).join(", ");
|
|
1571
|
+
const values = params.map((value) => SQL`${value}`).join(", ");
|
|
1572
|
+
let query = `
|
|
1573
|
+
INSERT INTO "${table}" (${columns})
|
|
1574
|
+
VALUES (${values})
|
|
1575
|
+
RETURNING *`;
|
|
1576
|
+
query = query.replace(/'(\?)'/, "?");
|
|
1577
|
+
return query;
|
|
1578
|
+
}
|
|
1579
|
+
function updateObjectQuery(table, obj, whereStatement) {
|
|
1580
|
+
const setArray = [];
|
|
1581
|
+
for (const i in obj) {
|
|
1582
|
+
setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
|
|
1083
1583
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1584
|
+
return `
|
|
1585
|
+
UPDATE ${escapeColumnName(table)}
|
|
1586
|
+
SET ${setArray.join(", ")} ${whereStatement}
|
|
1587
|
+
RETURNING *`;
|
|
1087
1588
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1589
|
+
function isValueNumber2(value) {
|
|
1590
|
+
return !isNaN(Number(value));
|
|
1591
|
+
}
|
|
1592
|
+
function SQL(strings, ...values) {
|
|
1593
|
+
let query = strings[0];
|
|
1594
|
+
values.forEach((value, index) => {
|
|
1595
|
+
if (typeof value === "boolean") {
|
|
1596
|
+
query += value;
|
|
1597
|
+
} else if (typeof value === "number") {
|
|
1598
|
+
query += value;
|
|
1599
|
+
} else if (Array.isArray(value)) {
|
|
1600
|
+
query += format2.literal(JSON.stringify(value)) + "::jsonb";
|
|
1601
|
+
} else {
|
|
1602
|
+
query += format2.literal(value);
|
|
1603
|
+
}
|
|
1604
|
+
query += strings[index + 1];
|
|
1605
|
+
});
|
|
1606
|
+
return query;
|
|
1103
1607
|
}
|
|
1104
1608
|
|
|
1105
|
-
// src/restura/
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1609
|
+
// src/restura/sql/PsqlConnection.ts
|
|
1610
|
+
var PsqlConnection = class {
|
|
1611
|
+
constructor(instanceId) {
|
|
1612
|
+
this.instanceId = instanceId || crypto.randomUUID();
|
|
1613
|
+
}
|
|
1614
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1615
|
+
async queryOne(query, options, requesterDetails) {
|
|
1616
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1617
|
+
const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
|
|
1618
|
+
this.logSqlStatement(formattedQuery, options, meta);
|
|
1619
|
+
const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
|
|
1620
|
+
`;
|
|
1621
|
+
try {
|
|
1622
|
+
const response = await this.query(queryMetadata + formattedQuery, options);
|
|
1623
|
+
if (response.rows.length === 0) throw new RsError("NOT_FOUND", "No results found");
|
|
1624
|
+
else if (response.rows.length > 1) throw new RsError("DUPLICATE", "More than one result found");
|
|
1625
|
+
return response.rows[0];
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
if (RsError.isRsError(error)) throw error;
|
|
1628
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1629
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1630
|
+
}
|
|
1631
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1635
|
+
async runQuery(query, options, requesterDetails) {
|
|
1636
|
+
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1637
|
+
const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
|
|
1638
|
+
this.logSqlStatement(formattedQuery, options, meta);
|
|
1639
|
+
const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
|
|
1640
|
+
`;
|
|
1641
|
+
try {
|
|
1642
|
+
const response = await this.query(queryMetadata + formattedQuery, options);
|
|
1643
|
+
return response.rows;
|
|
1644
|
+
} catch (error) {
|
|
1645
|
+
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1646
|
+
throw new RsError("DUPLICATE", error.message);
|
|
1647
|
+
}
|
|
1648
|
+
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
logSqlStatement(query, options, queryMetadata, prefix = "") {
|
|
1652
|
+
if (logger.level !== "silly") return;
|
|
1653
|
+
let sqlStatement = "";
|
|
1654
|
+
if (options.length === 0) {
|
|
1655
|
+
sqlStatement = query;
|
|
1656
|
+
} else {
|
|
1657
|
+
let stringIndex = 0;
|
|
1658
|
+
sqlStatement = query.replace(/\$\d+/g, () => {
|
|
1659
|
+
const value = options[stringIndex++];
|
|
1660
|
+
if (typeof value === "number") return value.toString();
|
|
1661
|
+
return format3.literal(value);
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
let initiator = "Anonymous";
|
|
1665
|
+
if ("userId" in queryMetadata && queryMetadata.userId)
|
|
1666
|
+
initiator = `User Id (${queryMetadata.userId.toString()})`;
|
|
1667
|
+
if ("isSystemUser" in queryMetadata && queryMetadata.isSystemUser) initiator = "SYSTEM";
|
|
1668
|
+
logger.silly(`${prefix}query by ${initiator}, Query ->
|
|
1669
|
+
${sqlStatement}`);
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
// src/restura/sql/PsqlPool.ts
|
|
1674
|
+
var { Pool } = pg;
|
|
1675
|
+
var PsqlPool = class extends PsqlConnection {
|
|
1676
|
+
constructor(poolConfig) {
|
|
1677
|
+
super();
|
|
1678
|
+
this.poolConfig = poolConfig;
|
|
1679
|
+
this.pool = new Pool(poolConfig);
|
|
1680
|
+
this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
|
|
1681
|
+
logger.info("Connected to PostgreSQL database");
|
|
1682
|
+
}).catch((error) => {
|
|
1683
|
+
logger.error("Error connecting to database", error);
|
|
1684
|
+
process.exit(1);
|
|
1133
1685
|
});
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1686
|
+
}
|
|
1687
|
+
async query(query, values) {
|
|
1688
|
+
return this.pool.query(query, values);
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1139
1691
|
|
|
1140
1692
|
// src/restura/sql/SqlEngine.ts
|
|
1141
1693
|
import { ObjectUtils as ObjectUtils3 } from "@redskytech/core-utils";
|
|
@@ -1203,11 +1755,11 @@ var SqlEngine = class {
|
|
|
1203
1755
|
return returnValue;
|
|
1204
1756
|
}
|
|
1205
1757
|
replaceLocalParamKeywords(value, routeData, req, sqlParams) {
|
|
1206
|
-
var
|
|
1758
|
+
var _a2;
|
|
1207
1759
|
if (!routeData.request) return value;
|
|
1208
1760
|
const data = req.data;
|
|
1209
1761
|
if (typeof value === "string") {
|
|
1210
|
-
(
|
|
1762
|
+
(_a2 = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
|
|
1211
1763
|
const requestParam = routeData.request.find((item) => {
|
|
1212
1764
|
return item.name === param.replace("$", "");
|
|
1213
1765
|
});
|
|
@@ -1220,13 +1772,16 @@ var SqlEngine = class {
|
|
|
1220
1772
|
return value;
|
|
1221
1773
|
}
|
|
1222
1774
|
replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
|
|
1223
|
-
var
|
|
1775
|
+
var _a2;
|
|
1224
1776
|
if (typeof value === "string") {
|
|
1225
|
-
(
|
|
1777
|
+
(_a2 = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
|
|
1226
1778
|
param = param.replace("#", "");
|
|
1227
1779
|
const globalParamValue = req.requesterDetails[param];
|
|
1228
1780
|
if (!globalParamValue)
|
|
1229
|
-
throw new RsError(
|
|
1781
|
+
throw new RsError(
|
|
1782
|
+
"SCHEMA_ERROR",
|
|
1783
|
+
`Invalid global keyword clause in route (${routeData.path}) when looking for (#${param})`
|
|
1784
|
+
);
|
|
1230
1785
|
sqlParams.push(globalParamValue);
|
|
1231
1786
|
});
|
|
1232
1787
|
return value.replace(new RegExp(/#[a-zA-Z][a-zA-Z0-9_]+/g), "?");
|
|
@@ -1235,118 +1790,368 @@ var SqlEngine = class {
|
|
|
1235
1790
|
}
|
|
1236
1791
|
};
|
|
1237
1792
|
|
|
1238
|
-
// src/restura/sql/
|
|
1793
|
+
// src/restura/sql/filterPsqlParser.ts
|
|
1239
1794
|
import peg from "pegjs";
|
|
1240
1795
|
var filterSqlGrammar = `
|
|
1796
|
+
{
|
|
1797
|
+
// ported from pg-format but intentionally will add double quotes to every column
|
|
1798
|
+
function quoteSqlIdentity(value) {
|
|
1799
|
+
if (value === undefined || value === null) {
|
|
1800
|
+
throw new Error('SQL identifier cannot be null or undefined');
|
|
1801
|
+
} else if (value === false) {
|
|
1802
|
+
return '"f"';
|
|
1803
|
+
} else if (value === true) {
|
|
1804
|
+
return '"t"';
|
|
1805
|
+
} else if (value instanceof Date) {
|
|
1806
|
+
// return '"' + formatDate(value.toISOString()) + '"';
|
|
1807
|
+
} else if (value instanceof Buffer) {
|
|
1808
|
+
throw new Error('SQL identifier cannot be a buffer');
|
|
1809
|
+
} else if (Array.isArray(value) === true) {
|
|
1810
|
+
var temp = [];
|
|
1811
|
+
for (var i = 0; i < value.length; i++) {
|
|
1812
|
+
if (Array.isArray(value[i]) === true) {
|
|
1813
|
+
throw new Error('Nested array to grouped list conversion is not supported for SQL identifier');
|
|
1814
|
+
} else {
|
|
1815
|
+
// temp.push(quoteIdent(value[i]));
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
return temp.toString();
|
|
1819
|
+
} else if (value === Object(value)) {
|
|
1820
|
+
throw new Error('SQL identifier cannot be an object');
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
var ident = value.toString().slice(0); // create copy
|
|
1824
|
+
|
|
1825
|
+
// do not quote a valid, unquoted identifier
|
|
1826
|
+
// if (/^[a-z_][a-z0-9_$]*$/.test(ident) === true && isReserved(ident) === false) {
|
|
1827
|
+
// return ident;
|
|
1828
|
+
// }
|
|
1829
|
+
|
|
1830
|
+
var quoted = '"';
|
|
1831
|
+
|
|
1832
|
+
for (var i = 0; i < ident.length; i++) {
|
|
1833
|
+
var c = ident[i];
|
|
1834
|
+
if (c === '"') {
|
|
1835
|
+
quoted += c + c;
|
|
1836
|
+
} else {
|
|
1837
|
+
quoted += c;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
quoted += '"';
|
|
1842
|
+
|
|
1843
|
+
return quoted;
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1241
1847
|
start = expressionList
|
|
1242
1848
|
|
|
1849
|
+
_ = [ \\t\\r\\n]* // Matches spaces, tabs, and line breaks
|
|
1850
|
+
|
|
1243
1851
|
expressionList =
|
|
1244
|
-
|
|
1852
|
+
leftExpression:expression _ operator:operator _ rightExpression:expressionList
|
|
1245
1853
|
{ return \`\${leftExpression} \${operator} \${rightExpression}\`;}
|
|
1246
1854
|
/ expression
|
|
1247
1855
|
|
|
1248
1856
|
expression =
|
|
1249
|
-
negate:negate?"(" "column:"
|
|
1250
|
-
{return \`\${negate? "
|
|
1857
|
+
negate:negate? _ "(" _ "column" _ ":" column:column _ ","? _ value:value? ","? _ type:type? _ ")"_
|
|
1858
|
+
{return \`\${negate? " NOT " : ""}(\${type? type(column, value) : \`\${column} = \${format.literal(value)}\`})\`;}
|
|
1251
1859
|
/
|
|
1252
|
-
negate:negate?"("expression:expressionList")" { return \`\${negate? "
|
|
1860
|
+
negate:negate?"("expression:expressionList")" { return \`\${negate? " NOT " : ""}(\${expression})\`; }
|
|
1253
1861
|
|
|
1254
1862
|
negate = "!"
|
|
1255
1863
|
|
|
1256
1864
|
operator = "and"i / "or"i
|
|
1257
1865
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1866
|
+
|
|
1867
|
+
column = left:text "." right:text { return \`\${quoteSqlIdentity(left)}.\${quoteSqlIdentity(right)}\`; }
|
|
1868
|
+
/
|
|
1869
|
+
text:text { return quoteSqlIdentity(text); }
|
|
1870
|
+
|
|
1261
1871
|
|
|
1872
|
+
text = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ { return text.join(""); }
|
|
1262
1873
|
|
|
1263
|
-
text = text:[ a-z0-9-_:.@]i+ { return text.join("");}
|
|
1264
1874
|
|
|
1265
|
-
type = "type:" type:typeString { return type; }
|
|
1266
|
-
typeString = text:"startsWith" { return function(column, value) { return \`\${column}
|
|
1267
|
-
text:"endsWith" { return function(column, value) { return \`\${column}
|
|
1268
|
-
text:"contains" { return function(column, value) { return \`\${column}
|
|
1269
|
-
text:"exact" { return function(column, value) { return \`\${column} = '\${
|
|
1270
|
-
text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${
|
|
1271
|
-
text:"greaterThan" { return function(column, value) { return \`\${column} > '\${
|
|
1272
|
-
text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${
|
|
1273
|
-
text:"lessThan" { return function(column, value) { return \`\${column} < '\${
|
|
1875
|
+
type = "type" _ ":" _ type:typeString { return type; }
|
|
1876
|
+
typeString = text:"startsWith" { return function(column, value) { return \`\${column} ILIKE '\${format.literal(value).slice(1,-1)}%'\`; } } /
|
|
1877
|
+
text:"endsWith" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1878
|
+
text:"contains" { return function(column, value) { return \`\${column} ILIKE '%\${format.literal(value).slice(1,-1)}%'\`; } } /
|
|
1879
|
+
text:"exact" { return function(column, value) { return \`\${column} = '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1880
|
+
text:"greaterThanEqual" { return function(column, value) { return \`\${column} >= '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1881
|
+
text:"greaterThan" { return function(column, value) { return \`\${column} > '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1882
|
+
text:"lessThanEqual" { return function(column, value) { return \`\${column} <= '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1883
|
+
text:"lessThan" { return function(column, value) { return \`\${column} < '\${format.literal(value).slice(1,-1)}'\`; } } /
|
|
1274
1884
|
text:"isNull" { return function(column, value) { return \`isNull(\${column})\`; } }
|
|
1275
|
-
|
|
1276
|
-
value = "value:" value:text { return value; }
|
|
1277
1885
|
|
|
1278
|
-
|
|
1279
|
-
var filterSqlParser = peg.generate(filterSqlGrammar, {
|
|
1280
|
-
format: "commonjs"
|
|
1281
|
-
// dependencies: { mysql: 'mysql' } // todo: figure out a better way to escape values depending on the database type
|
|
1282
|
-
});
|
|
1283
|
-
var filterSqlParser_default = filterSqlParser;
|
|
1886
|
+
value = "value" _ ":" value:text { return value; }
|
|
1284
1887
|
|
|
1285
|
-
// src/restura/sql/PsqlEngine.ts
|
|
1286
|
-
import { ObjectUtils as ObjectUtils4 } from "@redskytech/core-utils";
|
|
1287
1888
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
function questionMarksToOrderedParams(query) {
|
|
1295
|
-
let count = 1;
|
|
1296
|
-
return query.replace(/'\?'/g, () => `$${count++}`);
|
|
1297
|
-
}
|
|
1298
|
-
function insertObjectQuery(table, obj) {
|
|
1299
|
-
const keys = Object.keys(obj);
|
|
1300
|
-
const params = Object.values(obj);
|
|
1301
|
-
const columns = keys.map((column) => escapeColumnName(column)).join(", ");
|
|
1302
|
-
const values = params.map((value) => SQL`${value}`).join(", ");
|
|
1303
|
-
const query = `INSERT INTO "${table}" (${columns})
|
|
1304
|
-
VALUES (${values})
|
|
1305
|
-
RETURNING *`;
|
|
1306
|
-
return query;
|
|
1307
|
-
}
|
|
1308
|
-
function updateObjectQuery(table, obj, whereStatement) {
|
|
1309
|
-
const setArray = [];
|
|
1310
|
-
for (const i in obj) {
|
|
1311
|
-
setArray.push(`${escapeColumnName(i)} = ` + SQL`${obj[i]}`);
|
|
1312
|
-
}
|
|
1313
|
-
return `UPDATE ${escapeColumnName(table)}
|
|
1314
|
-
SET ${setArray.join(", ")} ${whereStatement}
|
|
1315
|
-
RETURNING *`;
|
|
1316
|
-
}
|
|
1317
|
-
function isValueNumber2(value) {
|
|
1318
|
-
return !isNaN(Number(value));
|
|
1319
|
-
}
|
|
1320
|
-
function SQL(strings, ...values) {
|
|
1321
|
-
let query = strings[0];
|
|
1322
|
-
values.forEach((value, index) => {
|
|
1323
|
-
if (isValueNumber2(value)) {
|
|
1324
|
-
query += value;
|
|
1325
|
-
} else {
|
|
1326
|
-
query += format2.literal(value);
|
|
1327
|
-
}
|
|
1328
|
-
query += strings[index + 1];
|
|
1329
|
-
});
|
|
1330
|
-
return query;
|
|
1331
|
-
}
|
|
1889
|
+
`;
|
|
1890
|
+
var filterPsqlParser = peg.generate(filterSqlGrammar, {
|
|
1891
|
+
format: "commonjs",
|
|
1892
|
+
dependencies: { format: "pg-format" }
|
|
1893
|
+
});
|
|
1894
|
+
var filterPsqlParser_default = filterPsqlParser;
|
|
1332
1895
|
|
|
1333
1896
|
// src/restura/sql/PsqlEngine.ts
|
|
1897
|
+
var { Client, types } = pg2;
|
|
1898
|
+
var systemUser = {
|
|
1899
|
+
role: "",
|
|
1900
|
+
host: "",
|
|
1901
|
+
ipAddress: "",
|
|
1902
|
+
isSystemUser: true
|
|
1903
|
+
};
|
|
1334
1904
|
var PsqlEngine = class extends SqlEngine {
|
|
1335
|
-
constructor(psqlConnectionPool) {
|
|
1905
|
+
constructor(psqlConnectionPool, shouldListenForDbTriggers = false) {
|
|
1336
1906
|
super();
|
|
1337
1907
|
this.psqlConnectionPool = psqlConnectionPool;
|
|
1908
|
+
this.setupPgReturnTypes();
|
|
1909
|
+
if (shouldListenForDbTriggers) {
|
|
1910
|
+
this.setupTriggerListeners = this.listenForDbTriggers();
|
|
1911
|
+
}
|
|
1338
1912
|
}
|
|
1339
|
-
async
|
|
1340
|
-
|
|
1341
|
-
|
|
1913
|
+
async close() {
|
|
1914
|
+
if (this.triggerClient) {
|
|
1915
|
+
await this.triggerClient.end();
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
setupPgReturnTypes() {
|
|
1919
|
+
const TIMESTAMPTZ_OID = 1184;
|
|
1920
|
+
types.setTypeParser(TIMESTAMPTZ_OID, (val) => {
|
|
1921
|
+
return val === null ? null : new Date(val).toISOString();
|
|
1922
|
+
});
|
|
1923
|
+
const BIGINT_OID = 20;
|
|
1924
|
+
types.setTypeParser(BIGINT_OID, (val) => {
|
|
1925
|
+
return val === null ? null : Number(val);
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
async listenForDbTriggers() {
|
|
1929
|
+
this.triggerClient = new Client({
|
|
1930
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
1931
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
1932
|
+
database: this.psqlConnectionPool.poolConfig.database,
|
|
1933
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
1934
|
+
port: this.psqlConnectionPool.poolConfig.port,
|
|
1935
|
+
connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
|
|
1936
|
+
});
|
|
1937
|
+
await this.triggerClient.connect();
|
|
1938
|
+
const promises = [];
|
|
1939
|
+
promises.push(this.triggerClient.query("LISTEN insert"));
|
|
1940
|
+
promises.push(this.triggerClient.query("LISTEN update"));
|
|
1941
|
+
promises.push(this.triggerClient.query("LISTEN delete"));
|
|
1942
|
+
await Promise.all(promises);
|
|
1943
|
+
this.triggerClient.on("notification", async (msg) => {
|
|
1944
|
+
if (msg.channel === "insert" || msg.channel === "update" || msg.channel === "delete") {
|
|
1945
|
+
const payload = ObjectUtils4.safeParse(msg.payload);
|
|
1946
|
+
await this.handleTrigger(payload, msg.channel.toUpperCase());
|
|
1947
|
+
}
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
async handleTrigger(payload, mutationType) {
|
|
1951
|
+
if (payload.queryMetadata && payload.queryMetadata.connectionInstanceId === this.psqlConnectionPool.instanceId) {
|
|
1952
|
+
await eventManager_default.fireActionFromDbTrigger({ queryMetadata: payload.queryMetadata, mutationType }, payload);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
async createDatabaseFromSchema(schema, connection) {
|
|
1956
|
+
const sqlFullStatement = this.generateDatabaseSchemaFromSchema(schema);
|
|
1957
|
+
await connection.runQuery(sqlFullStatement, [], systemUser);
|
|
1958
|
+
return sqlFullStatement;
|
|
1342
1959
|
}
|
|
1343
1960
|
generateDatabaseSchemaFromSchema(schema) {
|
|
1344
|
-
|
|
1345
|
-
|
|
1961
|
+
const sqlStatements = [];
|
|
1962
|
+
const indexes = [];
|
|
1963
|
+
const triggers = [];
|
|
1964
|
+
for (const table of schema.database) {
|
|
1965
|
+
if (table.notify) {
|
|
1966
|
+
triggers.push(this.createInsertTriggers(table.name, table.notify));
|
|
1967
|
+
triggers.push(this.createUpdateTrigger(table.name, table.notify));
|
|
1968
|
+
triggers.push(this.createDeleteTrigger(table.name, table.notify));
|
|
1969
|
+
}
|
|
1970
|
+
let sql = `CREATE TABLE "${table.name}"
|
|
1971
|
+
( `;
|
|
1972
|
+
const tableColumns = [];
|
|
1973
|
+
for (const column of table.columns) {
|
|
1974
|
+
let columnSql = "";
|
|
1975
|
+
columnSql += ` "${column.name}" ${this.schemaToPsqlType(column)}`;
|
|
1976
|
+
let value = column.value;
|
|
1977
|
+
if (column.type === "JSON") value = "";
|
|
1978
|
+
if (column.type === "JSONB") value = "";
|
|
1979
|
+
if (column.type === "DECIMAL" && value) {
|
|
1980
|
+
value = value.replace("-", ",").replace(/['"]/g, "");
|
|
1981
|
+
}
|
|
1982
|
+
if (value && column.type !== "ENUM") {
|
|
1983
|
+
columnSql += `(${value})`;
|
|
1984
|
+
} else if (column.length) columnSql += `(${column.length})`;
|
|
1985
|
+
if (column.isPrimary) {
|
|
1986
|
+
columnSql += " PRIMARY KEY ";
|
|
1987
|
+
}
|
|
1988
|
+
if (column.isUnique) {
|
|
1989
|
+
columnSql += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE `;
|
|
1990
|
+
}
|
|
1991
|
+
if (column.isNullable) columnSql += " NULL";
|
|
1992
|
+
else columnSql += " NOT NULL";
|
|
1993
|
+
if (column.default) columnSql += ` DEFAULT ${column.default}`;
|
|
1994
|
+
if (value && column.type === "ENUM") {
|
|
1995
|
+
columnSql += ` CHECK ("${column.name}" IN (${value}))`;
|
|
1996
|
+
}
|
|
1997
|
+
tableColumns.push(columnSql);
|
|
1998
|
+
}
|
|
1999
|
+
sql += tableColumns.join(", \n");
|
|
2000
|
+
for (const index of table.indexes) {
|
|
2001
|
+
if (!index.isPrimaryKey) {
|
|
2002
|
+
let unique = " ";
|
|
2003
|
+
if (index.isUnique) unique = "UNIQUE ";
|
|
2004
|
+
indexes.push(
|
|
2005
|
+
` CREATE ${unique}INDEX "${index.name}" ON "${table.name}" (${index.columns.map((item) => {
|
|
2006
|
+
return `"${item}" ${index.order}`;
|
|
2007
|
+
}).join(", ")});`
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
sql += "\n);";
|
|
2012
|
+
sqlStatements.push(sql);
|
|
2013
|
+
}
|
|
2014
|
+
for (const table of schema.database) {
|
|
2015
|
+
if (!table.foreignKeys.length) continue;
|
|
2016
|
+
const sql = `ALTER TABLE "${table.name}" `;
|
|
2017
|
+
const constraints = [];
|
|
2018
|
+
for (const foreignKey of table.foreignKeys) {
|
|
2019
|
+
let constraint = ` ADD CONSTRAINT "${foreignKey.name}"
|
|
2020
|
+
FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}")`;
|
|
2021
|
+
constraint += ` ON DELETE ${foreignKey.onDelete}`;
|
|
2022
|
+
constraint += ` ON UPDATE ${foreignKey.onUpdate}`;
|
|
2023
|
+
constraints.push(constraint);
|
|
2024
|
+
}
|
|
2025
|
+
sqlStatements.push(sql + constraints.join(",\n") + ";");
|
|
2026
|
+
}
|
|
2027
|
+
for (const table of schema.database) {
|
|
2028
|
+
if (!table.checkConstraints.length) continue;
|
|
2029
|
+
const sql = `ALTER TABLE "${table.name}" `;
|
|
2030
|
+
const constraints = [];
|
|
2031
|
+
for (const check of table.checkConstraints) {
|
|
2032
|
+
const constraint = `ADD CONSTRAINT "${check.name}" CHECK (${check.check})`;
|
|
2033
|
+
constraints.push(constraint);
|
|
2034
|
+
}
|
|
2035
|
+
sqlStatements.push(sql + constraints.join(",\n") + ";");
|
|
2036
|
+
}
|
|
2037
|
+
sqlStatements.push(indexes.join("\n"));
|
|
2038
|
+
sqlStatements.push(triggers.join("\n"));
|
|
2039
|
+
return sqlStatements.join("\n\n");
|
|
2040
|
+
}
|
|
2041
|
+
async getScratchPool() {
|
|
2042
|
+
var _a2, _b;
|
|
2043
|
+
const scratchDbExists = await this.psqlConnectionPool.runQuery(
|
|
2044
|
+
`SELECT *
|
|
2045
|
+
FROM pg_database
|
|
2046
|
+
WHERE datname = '${this.psqlConnectionPool.poolConfig.database}_scratch';`,
|
|
2047
|
+
[],
|
|
2048
|
+
systemUser
|
|
2049
|
+
);
|
|
2050
|
+
if (scratchDbExists.length === 0) {
|
|
2051
|
+
await this.psqlConnectionPool.runQuery(
|
|
2052
|
+
`CREATE DATABASE ${this.psqlConnectionPool.poolConfig.database}_scratch;`,
|
|
2053
|
+
[],
|
|
2054
|
+
systemUser
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
const scratchPool = new PsqlPool({
|
|
2058
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2059
|
+
port: this.psqlConnectionPool.poolConfig.port,
|
|
2060
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2061
|
+
database: this.psqlConnectionPool.poolConfig.database + "_scratch",
|
|
2062
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2063
|
+
max: this.psqlConnectionPool.poolConfig.max,
|
|
2064
|
+
idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
|
|
2065
|
+
connectionTimeoutMillis: this.psqlConnectionPool.poolConfig.connectionTimeoutMillis
|
|
2066
|
+
});
|
|
2067
|
+
await scratchPool.runQuery(`DROP SCHEMA public CASCADE;`, [], systemUser);
|
|
2068
|
+
await scratchPool.runQuery(
|
|
2069
|
+
`CREATE SCHEMA public AUTHORIZATION ${this.psqlConnectionPool.poolConfig.user};`,
|
|
2070
|
+
[],
|
|
2071
|
+
systemUser
|
|
2072
|
+
);
|
|
2073
|
+
const schemaComment = await this.psqlConnectionPool.runQuery(
|
|
2074
|
+
`SELECT pg_description.description
|
|
2075
|
+
FROM pg_description
|
|
2076
|
+
JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
|
|
2077
|
+
WHERE pg_namespace.nspname = 'public';`,
|
|
2078
|
+
[],
|
|
2079
|
+
systemUser
|
|
2080
|
+
);
|
|
2081
|
+
if ((_a2 = schemaComment[0]) == null ? void 0 : _a2.description) {
|
|
2082
|
+
await scratchPool.runQuery(
|
|
2083
|
+
`COMMENT ON SCHEMA public IS '${(_b = schemaComment[0]) == null ? void 0 : _b.description}';`,
|
|
2084
|
+
[],
|
|
2085
|
+
systemUser
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
return scratchPool;
|
|
2089
|
+
}
|
|
2090
|
+
async diffDatabaseToSchema(schema) {
|
|
2091
|
+
const scratchPool = await this.getScratchPool();
|
|
2092
|
+
await this.createDatabaseFromSchema(schema, scratchPool);
|
|
2093
|
+
const originalClient = new Client({
|
|
2094
|
+
database: this.psqlConnectionPool.poolConfig.database,
|
|
2095
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2096
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2097
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2098
|
+
port: this.psqlConnectionPool.poolConfig.port
|
|
2099
|
+
});
|
|
2100
|
+
const scratchClient = new Client({
|
|
2101
|
+
database: this.psqlConnectionPool.poolConfig.database + "_scratch",
|
|
2102
|
+
user: this.psqlConnectionPool.poolConfig.user,
|
|
2103
|
+
password: this.psqlConnectionPool.poolConfig.password,
|
|
2104
|
+
host: this.psqlConnectionPool.poolConfig.host,
|
|
2105
|
+
port: this.psqlConnectionPool.poolConfig.port
|
|
2106
|
+
});
|
|
2107
|
+
const promises = [originalClient.connect(), scratchClient.connect()];
|
|
2108
|
+
await Promise.all(promises);
|
|
2109
|
+
const infoPromises = [pgInfo({ client: originalClient }), pgInfo({ client: scratchClient })];
|
|
2110
|
+
const [info1, info2] = await Promise.all(infoPromises);
|
|
2111
|
+
const diff = getDiff(info1, info2);
|
|
2112
|
+
const endPromises = [originalClient.end(), scratchClient.end()];
|
|
2113
|
+
await Promise.all(endPromises);
|
|
2114
|
+
return diff.join("\n");
|
|
1346
2115
|
}
|
|
1347
2116
|
createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
|
|
1348
|
-
|
|
1349
|
-
|
|
2117
|
+
if (!item.subquery) return "";
|
|
2118
|
+
if (!ObjectUtils4.isArrayWithData(
|
|
2119
|
+
item.subquery.properties.filter((nestedItem) => {
|
|
2120
|
+
return this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
|
|
2121
|
+
...routeData.joins,
|
|
2122
|
+
...item.subquery.joins
|
|
2123
|
+
]);
|
|
2124
|
+
})
|
|
2125
|
+
)) {
|
|
2126
|
+
return "'[]'";
|
|
2127
|
+
}
|
|
2128
|
+
return `COALESCE((SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
|
2129
|
+
${item.subquery.properties.map((nestedItem) => {
|
|
2130
|
+
if (!this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
|
|
2131
|
+
...routeData.joins,
|
|
2132
|
+
...item.subquery.joins
|
|
2133
|
+
])) {
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
if (nestedItem.subquery) {
|
|
2137
|
+
return `'${nestedItem.name}', ${this.createNestedSelect(
|
|
2138
|
+
// recursion
|
|
2139
|
+
req,
|
|
2140
|
+
schema,
|
|
2141
|
+
nestedItem,
|
|
2142
|
+
routeData,
|
|
2143
|
+
userRole,
|
|
2144
|
+
sqlParams
|
|
2145
|
+
)}`;
|
|
2146
|
+
}
|
|
2147
|
+
return `'${nestedItem.name}', ${escapeColumnName(nestedItem.selector)}`;
|
|
2148
|
+
}).filter(Boolean).join(", ")}
|
|
2149
|
+
))
|
|
2150
|
+
FROM
|
|
2151
|
+
"${item.subquery.table}"
|
|
2152
|
+
${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, userRole, sqlParams)}
|
|
2153
|
+
${this.generateWhereClause(req, item.subquery.where, routeData, sqlParams)}
|
|
2154
|
+
), '[]')`;
|
|
1350
2155
|
}
|
|
1351
2156
|
async executeCreateRequest(req, routeData, schema) {
|
|
1352
2157
|
const sqlParams = [];
|
|
@@ -1355,16 +2160,19 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1355
2160
|
parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
|
|
1356
2161
|
});
|
|
1357
2162
|
const query = insertObjectQuery(routeData.table, __spreadValues(__spreadValues({}, req.data), parameterObj));
|
|
1358
|
-
const createdItem = await this.psqlConnectionPool.queryOne(
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
2163
|
+
const createdItem = await this.psqlConnectionPool.queryOne(
|
|
2164
|
+
query,
|
|
2165
|
+
sqlParams,
|
|
2166
|
+
req.requesterDetails
|
|
2167
|
+
);
|
|
2168
|
+
const insertId = createdItem.id;
|
|
2169
|
+
const whereId = {
|
|
2170
|
+
tableName: routeData.table,
|
|
2171
|
+
value: insertId,
|
|
2172
|
+
columnName: "id",
|
|
2173
|
+
operator: "="
|
|
2174
|
+
};
|
|
2175
|
+
const whereData = [whereId];
|
|
1368
2176
|
req.data = { id: insertId };
|
|
1369
2177
|
return this.executeGetRequest(req, __spreadProps(__spreadValues({}, routeData), { where: whereData }), schema);
|
|
1370
2178
|
}
|
|
@@ -1383,7 +2191,9 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1383
2191
|
let selectStatement = "SELECT \n";
|
|
1384
2192
|
selectStatement += ` ${selectColumns.map((item) => {
|
|
1385
2193
|
if (item.subquery) {
|
|
1386
|
-
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${
|
|
2194
|
+
return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${escapeColumnName(
|
|
2195
|
+
item.name
|
|
2196
|
+
)}`;
|
|
1387
2197
|
}
|
|
1388
2198
|
return `${escapeColumnName(item.selector)} AS ${escapeColumnName(item.name)}`;
|
|
1389
2199
|
}).join(",\n ")}
|
|
@@ -1405,37 +2215,42 @@ var PsqlEngine = class extends SqlEngine {
|
|
|
1405
2215
|
if (routeData.type === "ONE") {
|
|
1406
2216
|
return this.psqlConnectionPool.queryOne(
|
|
1407
2217
|
`${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
|
|
1408
|
-
sqlParams
|
|
2218
|
+
sqlParams,
|
|
2219
|
+
req.requesterDetails
|
|
1409
2220
|
);
|
|
1410
2221
|
} else if (routeData.type === "ARRAY") {
|
|
1411
2222
|
return this.psqlConnectionPool.runQuery(
|
|
1412
2223
|
`${selectStatement}${sqlStatement}${groupByOrderByStatement};`,
|
|
1413
|
-
sqlParams
|
|
2224
|
+
sqlParams,
|
|
2225
|
+
req.requesterDetails
|
|
1414
2226
|
);
|
|
1415
2227
|
} else if (routeData.type === "PAGED") {
|
|
1416
2228
|
const data = req.data;
|
|
1417
|
-
const
|
|
1418
|
-
`${selectStatement}${sqlStatement}${groupByOrderByStatement} LIMIT
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
...sqlParams,
|
|
1422
|
-
data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER,
|
|
1423
|
-
(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER,
|
|
1424
|
-
...sqlParams
|
|
1425
|
-
]
|
|
2229
|
+
const pagePromise = this.psqlConnectionPool.runQuery(
|
|
2230
|
+
`${selectStatement}${sqlStatement}${groupByOrderByStatement}` + SQL`LIMIT ${data.perPage || DEFAULT_PAGED_PER_PAGE_NUMBER} OFFSET ${(data.page - 1) * data.perPage || DEFAULT_PAGED_PAGE_NUMBER};`,
|
|
2231
|
+
sqlParams,
|
|
2232
|
+
req.requesterDetails
|
|
1426
2233
|
);
|
|
2234
|
+
const totalQuery = `SELECT COUNT(${routeData.groupBy ? `DISTINCT ${routeData.groupBy.tableName}.${routeData.groupBy.columnName}` : "*"}) AS total
|
|
2235
|
+
${sqlStatement};`;
|
|
2236
|
+
const totalPromise = this.psqlConnectionPool.runQuery(
|
|
2237
|
+
totalQuery,
|
|
2238
|
+
sqlParams,
|
|
2239
|
+
req.requesterDetails
|
|
2240
|
+
);
|
|
2241
|
+
const [pageResults, totalResponse] = await Promise.all([pagePromise, totalPromise]);
|
|
1427
2242
|
let total = 0;
|
|
1428
|
-
if (ObjectUtils4.isArrayWithData(
|
|
1429
|
-
total =
|
|
2243
|
+
if (ObjectUtils4.isArrayWithData(totalResponse)) {
|
|
2244
|
+
total = totalResponse[0].total;
|
|
1430
2245
|
}
|
|
1431
|
-
return { data: pageResults
|
|
2246
|
+
return { data: pageResults, total };
|
|
1432
2247
|
} else {
|
|
1433
2248
|
throw new RsError("UNKNOWN_ERROR", "Unknown route type.");
|
|
1434
2249
|
}
|
|
1435
2250
|
}
|
|
1436
2251
|
async executeUpdateRequest(req, routeData, schema) {
|
|
1437
2252
|
const sqlParams = [];
|
|
1438
|
-
const
|
|
2253
|
+
const _a2 = req.body, { id } = _a2, bodyNoId = __objRest(_a2, ["id"]);
|
|
1439
2254
|
const table = schema.database.find((item) => {
|
|
1440
2255
|
return item.name === routeData.table;
|
|
1441
2256
|
});
|
|
@@ -1453,7 +2268,7 @@ ${sqlStatement};`,
|
|
|
1453
2268
|
}
|
|
1454
2269
|
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
1455
2270
|
const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
|
|
1456
|
-
await this.psqlConnectionPool.queryOne(query, [...sqlParams]);
|
|
2271
|
+
await this.psqlConnectionPool.queryOne(query, [...sqlParams], req.requesterDetails);
|
|
1457
2272
|
return this.executeGetRequest(req, routeData, schema);
|
|
1458
2273
|
}
|
|
1459
2274
|
async executeDeleteRequest(req, routeData, schema) {
|
|
@@ -1467,20 +2282,32 @@ ${sqlStatement};`,
|
|
|
1467
2282
|
req.requesterDetails.role,
|
|
1468
2283
|
sqlParams
|
|
1469
2284
|
);
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
2285
|
+
const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
|
|
2286
|
+
if (whereClause.replace(/\s/g, "") === "") {
|
|
2287
|
+
throw new RsError("DELETE_FORBIDDEN", "Deletes need a where clause");
|
|
2288
|
+
}
|
|
2289
|
+
const deleteStatement = `
|
|
2290
|
+
DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
|
|
2291
|
+
await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
|
|
1475
2292
|
return true;
|
|
1476
2293
|
}
|
|
1477
2294
|
generateJoinStatements(req, joins, baseTable, routeData, schema, userRole, sqlParams) {
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
2295
|
+
let joinStatements = "";
|
|
2296
|
+
joins.forEach((item) => {
|
|
2297
|
+
if (!this.doesRoleHavePermissionToTable(userRole, schema, item.table))
|
|
2298
|
+
throw new RsError("UNAUTHORIZED", "You do not have permission to access this table");
|
|
2299
|
+
if (item.custom) {
|
|
2300
|
+
const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
|
|
2301
|
+
joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)} ON ${customReplaced}
|
|
2302
|
+
`;
|
|
2303
|
+
} else {
|
|
2304
|
+
joinStatements += ` ${item.type} JOIN ${escapeColumnName(item.table)}${item.alias ? `AS "${item.alias}"` : ""} ON "${baseTable}"."${item.localColumnName}" = ${escapeColumnName(item.alias ? item.alias : item.table)}.${escapeColumnName(
|
|
2305
|
+
item.foreignColumnName
|
|
2306
|
+
)}
|
|
2307
|
+
`;
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
return joinStatements;
|
|
1484
2311
|
}
|
|
1485
2312
|
generateGroupBy(routeData) {
|
|
1486
2313
|
let groupBy = "";
|
|
@@ -1523,38 +2350,39 @@ ${sqlStatement};`,
|
|
|
1523
2350
|
);
|
|
1524
2351
|
let operator = item.operator;
|
|
1525
2352
|
if (operator === "LIKE") {
|
|
1526
|
-
|
|
2353
|
+
item.value = `'%${item.value}%'`;
|
|
1527
2354
|
} else if (operator === "STARTS WITH") {
|
|
1528
2355
|
operator = "LIKE";
|
|
1529
|
-
|
|
2356
|
+
item.value = `'${item.value}%'`;
|
|
1530
2357
|
} else if (operator === "ENDS WITH") {
|
|
1531
2358
|
operator = "LIKE";
|
|
1532
|
-
|
|
2359
|
+
item.value = `'%${item.value}'`;
|
|
1533
2360
|
}
|
|
1534
2361
|
const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
|
|
1535
|
-
|
|
1536
|
-
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator} ${["IN", "NOT IN"].includes(operator) ? `(${escapedValue})` : escapedValue}
|
|
2362
|
+
whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${replacedValue})` : replacedValue}
|
|
1537
2363
|
`;
|
|
1538
2364
|
});
|
|
1539
2365
|
const data = req.data;
|
|
1540
2366
|
if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
|
|
1541
2367
|
let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
2368
|
+
var _a2;
|
|
1542
2369
|
const requestParam = routeData.request.find((item) => {
|
|
1543
2370
|
return item.name === value.replace("$", "");
|
|
1544
2371
|
});
|
|
1545
2372
|
if (!requestParam)
|
|
1546
2373
|
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1547
|
-
return data[requestParam.name];
|
|
2374
|
+
return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
|
|
1548
2375
|
});
|
|
1549
2376
|
statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
|
|
2377
|
+
var _a2;
|
|
1550
2378
|
const requestParam = routeData.request.find((item) => {
|
|
1551
2379
|
return item.name === value.replace("#", "");
|
|
1552
2380
|
});
|
|
1553
2381
|
if (!requestParam)
|
|
1554
2382
|
throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
|
|
1555
|
-
return data[requestParam.name];
|
|
2383
|
+
return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
|
|
1556
2384
|
});
|
|
1557
|
-
statement =
|
|
2385
|
+
statement = filterPsqlParser_default.parse(statement);
|
|
1558
2386
|
if (whereClause.startsWith("WHERE")) {
|
|
1559
2387
|
whereClause += ` AND (${statement})
|
|
1560
2388
|
`;
|
|
@@ -1565,47 +2393,255 @@ ${sqlStatement};`,
|
|
|
1565
2393
|
}
|
|
1566
2394
|
return whereClause;
|
|
1567
2395
|
}
|
|
1568
|
-
|
|
2396
|
+
createUpdateTrigger(tableName, notify) {
|
|
2397
|
+
if (!notify) return "";
|
|
2398
|
+
if (notify === "ALL") {
|
|
2399
|
+
return `
|
|
2400
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_update()
|
|
2401
|
+
RETURNS TRIGGER AS $$
|
|
2402
|
+
DECLARE
|
|
2403
|
+
query_metadata JSON;
|
|
2404
|
+
BEGIN
|
|
2405
|
+
SELECT INTO query_metadata
|
|
2406
|
+
(regexp_match(
|
|
2407
|
+
current_query(),
|
|
2408
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2409
|
+
))[1]::json;
|
|
1569
2410
|
|
|
1570
|
-
|
|
1571
|
-
|
|
2411
|
+
PERFORM pg_notify(
|
|
2412
|
+
'update',
|
|
2413
|
+
json_build_object(
|
|
2414
|
+
'table', '${tableName}',
|
|
2415
|
+
'queryMetadata', query_metadata,
|
|
2416
|
+
'changedId', NEW.id,
|
|
2417
|
+
'record', NEW,
|
|
2418
|
+
'previousRecord', OLD
|
|
2419
|
+
)::text
|
|
2420
|
+
);
|
|
2421
|
+
RETURN NEW;
|
|
2422
|
+
END;
|
|
2423
|
+
$$ LANGUAGE plpgsql;
|
|
1572
2424
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
this.pool = new Pool(poolConfig);
|
|
1579
|
-
}
|
|
1580
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1581
|
-
async queryOne(query, options) {
|
|
1582
|
-
const formattedQuery = questionMarksToOrderedParams(query);
|
|
1583
|
-
try {
|
|
1584
|
-
const response = await this.pool.query(formattedQuery, options);
|
|
1585
|
-
return response.rows[0];
|
|
1586
|
-
} catch (error) {
|
|
1587
|
-
console.error(error, query, options);
|
|
1588
|
-
if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
|
|
1589
|
-
throw new RsError("DUPLICATE", error.message);
|
|
1590
|
-
}
|
|
1591
|
-
throw new RsError("DATABASE_ERROR", `${error.message}`);
|
|
2425
|
+
CREATE OR REPLACE TRIGGER ${tableName}_update
|
|
2426
|
+
AFTER UPDATE ON "${tableName}"
|
|
2427
|
+
FOR EACH ROW
|
|
2428
|
+
EXECUTE FUNCTION notify_${tableName}_update();
|
|
2429
|
+
`;
|
|
1592
2430
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2431
|
+
const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
|
|
2432
|
+
const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
|
|
2433
|
+
return `
|
|
2434
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_update()
|
|
2435
|
+
RETURNS TRIGGER AS $$
|
|
2436
|
+
DECLARE
|
|
2437
|
+
query_metadata JSON;
|
|
2438
|
+
BEGIN
|
|
2439
|
+
SELECT INTO query_metadata
|
|
2440
|
+
(regexp_match(
|
|
2441
|
+
current_query(),
|
|
2442
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2443
|
+
))[1]::json;
|
|
2444
|
+
|
|
2445
|
+
PERFORM pg_notify(
|
|
2446
|
+
'update',
|
|
2447
|
+
json_build_object(
|
|
2448
|
+
'table', '${tableName}',
|
|
2449
|
+
'queryMetadata', query_metadata,
|
|
2450
|
+
'changedId', NEW.id,
|
|
2451
|
+
'record', json_build_object(
|
|
2452
|
+
${notifyColumnNewBuildString}
|
|
2453
|
+
),
|
|
2454
|
+
'previousRecord', json_build_object(
|
|
2455
|
+
${notifyColumnOldBuildString}
|
|
2456
|
+
)
|
|
2457
|
+
)::text
|
|
2458
|
+
);
|
|
2459
|
+
RETURN NEW;
|
|
2460
|
+
END;
|
|
2461
|
+
$$ LANGUAGE plpgsql;
|
|
2462
|
+
|
|
2463
|
+
CREATE OR REPLACE TRIGGER ${tableName}_update
|
|
2464
|
+
AFTER UPDATE ON "${tableName}"
|
|
2465
|
+
FOR EACH ROW
|
|
2466
|
+
EXECUTE FUNCTION notify_${tableName}_update();
|
|
2467
|
+
`;
|
|
2468
|
+
}
|
|
2469
|
+
createDeleteTrigger(tableName, notify) {
|
|
2470
|
+
if (!notify) return "";
|
|
2471
|
+
if (notify === "ALL") {
|
|
2472
|
+
return `
|
|
2473
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
|
|
2474
|
+
RETURNS TRIGGER AS $$
|
|
2475
|
+
DECLARE
|
|
2476
|
+
query_metadata JSON;
|
|
2477
|
+
BEGIN
|
|
2478
|
+
SELECT INTO query_metadata
|
|
2479
|
+
(regexp_match(
|
|
2480
|
+
current_query(),
|
|
2481
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2482
|
+
))[1]::json;
|
|
2483
|
+
|
|
2484
|
+
PERFORM pg_notify(
|
|
2485
|
+
'delete',
|
|
2486
|
+
json_build_object(
|
|
2487
|
+
'table', '${tableName}',
|
|
2488
|
+
'queryMetadata', query_metadata,
|
|
2489
|
+
'deletedId', OLD.id,
|
|
2490
|
+
'previousRecord', OLD
|
|
2491
|
+
)::text
|
|
2492
|
+
);
|
|
2493
|
+
RETURN NEW;
|
|
2494
|
+
END;
|
|
2495
|
+
$$ LANGUAGE plpgsql;
|
|
2496
|
+
|
|
2497
|
+
CREATE OR REPLACE TRIGGER "${tableName}_delete"
|
|
2498
|
+
AFTER DELETE ON "${tableName}"
|
|
2499
|
+
FOR EACH ROW
|
|
2500
|
+
EXECUTE FUNCTION notify_${tableName}_delete();
|
|
2501
|
+
`;
|
|
2502
|
+
}
|
|
2503
|
+
const notifyColumnOldBuildString = notify.map((column) => `'${column}', OLD."${column}"`).join(",\n");
|
|
2504
|
+
return `
|
|
2505
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_delete()
|
|
2506
|
+
RETURNS TRIGGER AS $$
|
|
2507
|
+
DECLARE
|
|
2508
|
+
query_metadata JSON;
|
|
2509
|
+
BEGIN
|
|
2510
|
+
SELECT INTO query_metadata
|
|
2511
|
+
(regexp_match(
|
|
2512
|
+
current_query(),
|
|
2513
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2514
|
+
))[1]::json;
|
|
2515
|
+
|
|
2516
|
+
PERFORM pg_notify(
|
|
2517
|
+
'delete',
|
|
2518
|
+
json_build_object(
|
|
2519
|
+
'table', '${tableName}',
|
|
2520
|
+
'queryMetadata', query_metadata,
|
|
2521
|
+
'deletedId', OLD.id,
|
|
2522
|
+
'previousRecord', json_build_object(
|
|
2523
|
+
${notifyColumnOldBuildString}
|
|
2524
|
+
)
|
|
2525
|
+
)::text
|
|
2526
|
+
);
|
|
2527
|
+
RETURN NEW;
|
|
2528
|
+
END;
|
|
2529
|
+
$$ LANGUAGE plpgsql;
|
|
2530
|
+
|
|
2531
|
+
CREATE OR REPLACE TRIGGER "${tableName}_delete"
|
|
2532
|
+
AFTER DELETE ON "${tableName}"
|
|
2533
|
+
FOR EACH ROW
|
|
2534
|
+
EXECUTE FUNCTION notify_${tableName}_delete();
|
|
2535
|
+
`;
|
|
2536
|
+
}
|
|
2537
|
+
createInsertTriggers(tableName, notify) {
|
|
2538
|
+
if (!notify) return "";
|
|
2539
|
+
if (notify === "ALL") {
|
|
2540
|
+
return `
|
|
2541
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
|
|
2542
|
+
RETURNS TRIGGER AS $$
|
|
2543
|
+
DECLARE
|
|
2544
|
+
query_metadata JSON;
|
|
2545
|
+
BEGIN
|
|
2546
|
+
SELECT INTO query_metadata
|
|
2547
|
+
(regexp_match(
|
|
2548
|
+
current_query(),
|
|
2549
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2550
|
+
))[1]::json;
|
|
2551
|
+
|
|
2552
|
+
PERFORM pg_notify(
|
|
2553
|
+
'insert',
|
|
2554
|
+
json_build_object(
|
|
2555
|
+
'table', '${tableName}',
|
|
2556
|
+
'queryMetadata', query_metadata,
|
|
2557
|
+
'insertedId', NEW.id,
|
|
2558
|
+
'record', NEW
|
|
2559
|
+
)::text
|
|
2560
|
+
);
|
|
2561
|
+
|
|
2562
|
+
RETURN NEW;
|
|
2563
|
+
END;
|
|
2564
|
+
$$ LANGUAGE plpgsql;
|
|
2565
|
+
|
|
2566
|
+
CREATE OR REPLACE TRIGGER "${tableName}_insert"
|
|
2567
|
+
AFTER INSERT ON "${tableName}"
|
|
2568
|
+
FOR EACH ROW
|
|
2569
|
+
EXECUTE FUNCTION notify_${tableName}_insert();
|
|
2570
|
+
`;
|
|
1608
2571
|
}
|
|
2572
|
+
const notifyColumnNewBuildString = notify.map((column) => `'${column}', NEW."${column}"`).join(",\n");
|
|
2573
|
+
return `
|
|
2574
|
+
CREATE OR REPLACE FUNCTION notify_${tableName}_insert()
|
|
2575
|
+
RETURNS TRIGGER AS $$
|
|
2576
|
+
DECLARE
|
|
2577
|
+
query_metadata JSON;
|
|
2578
|
+
BEGIN
|
|
2579
|
+
SELECT INTO query_metadata
|
|
2580
|
+
(regexp_match(
|
|
2581
|
+
current_query(),
|
|
2582
|
+
'^--QUERY_METADATA\\(({.*})', 'n'
|
|
2583
|
+
))[1]::json;
|
|
2584
|
+
|
|
2585
|
+
PERFORM pg_notify(
|
|
2586
|
+
'insert',
|
|
2587
|
+
json_build_object(
|
|
2588
|
+
'table', '${tableName}',
|
|
2589
|
+
'queryMetadata', query_metadata,
|
|
2590
|
+
'insertedId', NEW.id,
|
|
2591
|
+
'record', json_build_object(
|
|
2592
|
+
${notifyColumnNewBuildString}
|
|
2593
|
+
)
|
|
2594
|
+
)::text
|
|
2595
|
+
);
|
|
2596
|
+
|
|
2597
|
+
RETURN NEW;
|
|
2598
|
+
END;
|
|
2599
|
+
$$ LANGUAGE plpgsql;
|
|
2600
|
+
|
|
2601
|
+
CREATE OR REPLACE TRIGGER "${tableName}_insert"
|
|
2602
|
+
AFTER INSERT ON "${tableName}"
|
|
2603
|
+
FOR EACH ROW
|
|
2604
|
+
EXECUTE FUNCTION notify_${tableName}_insert();
|
|
2605
|
+
`;
|
|
2606
|
+
}
|
|
2607
|
+
schemaToPsqlType(column) {
|
|
2608
|
+
if (column.hasAutoIncrement) return "BIGSERIAL";
|
|
2609
|
+
if (column.type === "ENUM") return `TEXT`;
|
|
2610
|
+
if (column.type === "DATETIME") return "TIMESTAMPTZ";
|
|
2611
|
+
if (column.type === "MEDIUMINT") return "INT";
|
|
2612
|
+
return column.type;
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
|
|
2616
|
+
// src/restura/utils/TempCache.ts
|
|
2617
|
+
import fs3 from "fs";
|
|
2618
|
+
import path3 from "path";
|
|
2619
|
+
import { DateUtils } from "@redskytech/core-utils";
|
|
2620
|
+
import { FileUtils as FileUtils2 } from "@restura/internal";
|
|
2621
|
+
import Bluebird3 from "bluebird";
|
|
2622
|
+
import * as os2 from "os";
|
|
2623
|
+
var TempCache = class {
|
|
2624
|
+
constructor(location) {
|
|
2625
|
+
this.maxDurationDays = 7;
|
|
2626
|
+
this.location = location || os2.tmpdir();
|
|
2627
|
+
FileUtils2.ensureDir(this.location).catch((e) => {
|
|
2628
|
+
throw e;
|
|
2629
|
+
});
|
|
2630
|
+
}
|
|
2631
|
+
async cleanup() {
|
|
2632
|
+
const fileList = await fs3.promises.readdir(this.location);
|
|
2633
|
+
await Bluebird3.map(
|
|
2634
|
+
fileList,
|
|
2635
|
+
async (file) => {
|
|
2636
|
+
const fullFilePath = path3.join(this.location, file);
|
|
2637
|
+
const fileStats = await fs3.promises.stat(fullFilePath);
|
|
2638
|
+
if (DateUtils.daysBetweenStartAndEndDates(new Date(fileStats.mtimeMs), /* @__PURE__ */ new Date()) > this.maxDurationDays) {
|
|
2639
|
+
logger.info(`Deleting old temp file: ${file}`);
|
|
2640
|
+
await fs3.promises.unlink(fullFilePath);
|
|
2641
|
+
}
|
|
2642
|
+
},
|
|
2643
|
+
{ concurrency: 10 }
|
|
2644
|
+
);
|
|
1609
2645
|
}
|
|
1610
2646
|
};
|
|
1611
2647
|
|
|
@@ -1627,10 +2663,12 @@ var ResturaEngine = class {
|
|
|
1627
2663
|
* @returns A promise that resolves when the initialization is complete.
|
|
1628
2664
|
*/
|
|
1629
2665
|
async init(app, authenticationHandler, psqlConnectionPool) {
|
|
1630
|
-
this.psqlConnectionPool = psqlConnectionPool;
|
|
1631
|
-
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool);
|
|
1632
|
-
setupPgReturnTypes();
|
|
1633
2666
|
this.resturaConfig = config2.validate("restura", resturaConfigSchema);
|
|
2667
|
+
this.multerCommonUpload = getMulterUpload(this.resturaConfig.fileTempCachePath);
|
|
2668
|
+
new TempCache(this.resturaConfig.fileTempCachePath);
|
|
2669
|
+
this.psqlConnectionPool = psqlConnectionPool;
|
|
2670
|
+
this.psqlEngine = new PsqlEngine(this.psqlConnectionPool, true);
|
|
2671
|
+
await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
|
|
1634
2672
|
this.authenticationHandler = authenticationHandler;
|
|
1635
2673
|
app.use(compression());
|
|
1636
2674
|
app.use(bodyParser.json({ limit: "32mb" }));
|
|
@@ -1693,10 +2731,7 @@ var ResturaEngine = class {
|
|
|
1693
2731
|
* @returns A promise that resolves when the API has been successfully generated and written to the output file.
|
|
1694
2732
|
*/
|
|
1695
2733
|
async generateApiFromSchema(outputFile, providedSchema) {
|
|
1696
|
-
|
|
1697
|
-
outputFile,
|
|
1698
|
-
await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1699
|
-
);
|
|
2734
|
+
fs4.writeFileSync(outputFile, await apiGenerator(providedSchema));
|
|
1700
2735
|
}
|
|
1701
2736
|
/**
|
|
1702
2737
|
* Generates a model from the provided schema and writes it to the specified output file.
|
|
@@ -1706,10 +2741,15 @@ var ResturaEngine = class {
|
|
|
1706
2741
|
* @returns A promise that resolves when the model has been successfully written to the output file.
|
|
1707
2742
|
*/
|
|
1708
2743
|
async generateModelFromSchema(outputFile, providedSchema) {
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2744
|
+
fs4.writeFileSync(outputFile, await modelGenerator(providedSchema));
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Generates the ambient module declaration for Restura global types and writes it to the specified output file.
|
|
2748
|
+
* These types are used sometimes in the CustomTypes
|
|
2749
|
+
* @param outputFile
|
|
2750
|
+
*/
|
|
2751
|
+
generateResturaGlobalTypes(outputFile) {
|
|
2752
|
+
fs4.writeFileSync(outputFile, resturaGlobalTypesGenerator());
|
|
1713
2753
|
}
|
|
1714
2754
|
/**
|
|
1715
2755
|
* Retrieves the latest file system schema for Restura.
|
|
@@ -1718,11 +2758,11 @@ var ResturaEngine = class {
|
|
|
1718
2758
|
* @throws {Error} If the schema file is missing or the schema is not valid.
|
|
1719
2759
|
*/
|
|
1720
2760
|
async getLatestFileSystemSchema() {
|
|
1721
|
-
if (!
|
|
2761
|
+
if (!fs4.existsSync(this.resturaConfig.schemaFilePath)) {
|
|
1722
2762
|
logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
|
|
1723
2763
|
throw new Error("Missing restura schema file");
|
|
1724
2764
|
}
|
|
1725
|
-
const schemaFileData =
|
|
2765
|
+
const schemaFileData = fs4.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
|
|
1726
2766
|
const schema = ObjectUtils5.safeParse(schemaFileData);
|
|
1727
2767
|
const isValid = await isSchemaValid(schema);
|
|
1728
2768
|
if (!isValid) {
|
|
@@ -1731,28 +2771,6 @@ var ResturaEngine = class {
|
|
|
1731
2771
|
}
|
|
1732
2772
|
return schema;
|
|
1733
2773
|
}
|
|
1734
|
-
/**
|
|
1735
|
-
* Asynchronously generates and retrieves hashes for the provided schema and related generated files.
|
|
1736
|
-
*
|
|
1737
|
-
* @param providedSchema - The schema for which hashes need to be generated.
|
|
1738
|
-
* @returns A promise that resolves to an object containing:
|
|
1739
|
-
* - `schemaHash`: The hash of the provided schema.
|
|
1740
|
-
* - `apiCreatedSchemaHash`: The hash extracted from the generated `api.d.ts` file.
|
|
1741
|
-
* - `modelCreatedSchemaHash`: The hash extracted from the generated `models.d.ts` file.
|
|
1742
|
-
*/
|
|
1743
|
-
async getHashes(providedSchema) {
|
|
1744
|
-
var _a, _b, _c, _d;
|
|
1745
|
-
const schemaHash = await this.generateHashForSchema(providedSchema);
|
|
1746
|
-
const apiFile = fs2.readFileSync(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1747
|
-
const apiCreatedSchemaHash = (_b = (_a = apiFile.toString().match(/\((.*)\)/)) == null ? void 0 : _a[1]) != null ? _b : "";
|
|
1748
|
-
const modelFile = fs2.readFileSync(path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1749
|
-
const modelCreatedSchemaHash = (_d = (_c = modelFile.toString().match(/\((.*)\)/)) == null ? void 0 : _c[1]) != null ? _d : "";
|
|
1750
|
-
return {
|
|
1751
|
-
schemaHash,
|
|
1752
|
-
apiCreatedSchemaHash,
|
|
1753
|
-
modelCreatedSchemaHash
|
|
1754
|
-
};
|
|
1755
|
-
}
|
|
1756
2774
|
async reloadEndpoints() {
|
|
1757
2775
|
this.schema = await this.getLatestFileSystemSchema();
|
|
1758
2776
|
this.customTypeValidation = customTypeValidationGenerator(this.schema);
|
|
@@ -1781,30 +2799,10 @@ var ResturaEngine = class {
|
|
|
1781
2799
|
logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
|
|
1782
2800
|
}
|
|
1783
2801
|
async validateGeneratedTypesFolder() {
|
|
1784
|
-
if (!
|
|
1785
|
-
|
|
1786
|
-
}
|
|
1787
|
-
const hasApiFile = fs2.existsSync(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1788
|
-
const hasModelsFile = fs2.existsSync(path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1789
|
-
if (!hasApiFile) {
|
|
1790
|
-
await this.generateApiFromSchema(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1791
|
-
}
|
|
1792
|
-
if (!hasModelsFile) {
|
|
1793
|
-
await this.generateModelFromSchema(
|
|
1794
|
-
path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1795
|
-
this.schema
|
|
1796
|
-
);
|
|
1797
|
-
}
|
|
1798
|
-
const hashes = await this.getHashes(this.schema);
|
|
1799
|
-
if (hashes.schemaHash !== hashes.apiCreatedSchemaHash) {
|
|
1800
|
-
await this.generateApiFromSchema(path2.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1801
|
-
}
|
|
1802
|
-
if (hashes.schemaHash !== hashes.modelCreatedSchemaHash) {
|
|
1803
|
-
await this.generateModelFromSchema(
|
|
1804
|
-
path2.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1805
|
-
this.schema
|
|
1806
|
-
);
|
|
2802
|
+
if (!fs4.existsSync(this.resturaConfig.generatedTypesPath)) {
|
|
2803
|
+
fs4.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
|
|
1807
2804
|
}
|
|
2805
|
+
this.updateTypes();
|
|
1808
2806
|
}
|
|
1809
2807
|
resturaAuthentication(req, res, next) {
|
|
1810
2808
|
if (req.headers["x-auth-token"] !== this.resturaConfig.authToken) res.status(401).send("Unauthorized");
|
|
@@ -1812,7 +2810,7 @@ var ResturaEngine = class {
|
|
|
1812
2810
|
}
|
|
1813
2811
|
async previewCreateSchema(req, res) {
|
|
1814
2812
|
try {
|
|
1815
|
-
const schemaDiff =
|
|
2813
|
+
const schemaDiff = await compareSchema_default.diffSchema(req.data, this.schema, this.psqlEngine);
|
|
1816
2814
|
res.send({ data: schemaDiff });
|
|
1817
2815
|
} catch (err) {
|
|
1818
2816
|
res.status(400).send(err);
|
|
@@ -1820,7 +2818,7 @@ var ResturaEngine = class {
|
|
|
1820
2818
|
}
|
|
1821
2819
|
async updateSchema(req, res) {
|
|
1822
2820
|
try {
|
|
1823
|
-
this.schema = req.data;
|
|
2821
|
+
this.schema = sortObjectKeysAlphabetically(req.data);
|
|
1824
2822
|
await this.storeFileSystemSchema();
|
|
1825
2823
|
await this.reloadEndpoints();
|
|
1826
2824
|
await this.updateTypes();
|
|
@@ -1831,11 +2829,12 @@ var ResturaEngine = class {
|
|
|
1831
2829
|
}
|
|
1832
2830
|
}
|
|
1833
2831
|
async updateTypes() {
|
|
1834
|
-
await this.generateApiFromSchema(
|
|
2832
|
+
await this.generateApiFromSchema(path4.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1835
2833
|
await this.generateModelFromSchema(
|
|
1836
|
-
|
|
2834
|
+
path4.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1837
2835
|
this.schema
|
|
1838
2836
|
);
|
|
2837
|
+
this.generateResturaGlobalTypes(path4.join(this.resturaConfig.generatedTypesPath, "restura.d.ts"));
|
|
1839
2838
|
}
|
|
1840
2839
|
async getSchema(req, res) {
|
|
1841
2840
|
res.send({ data: this.schema });
|
|
@@ -1843,24 +2842,48 @@ var ResturaEngine = class {
|
|
|
1843
2842
|
async getSchemaAndTypes(req, res) {
|
|
1844
2843
|
try {
|
|
1845
2844
|
const schema = await this.getLatestFileSystemSchema();
|
|
1846
|
-
const
|
|
1847
|
-
const
|
|
1848
|
-
const modelsText = await modelGenerator(schema, schemaHash);
|
|
2845
|
+
const apiText = await apiGenerator(schema);
|
|
2846
|
+
const modelsText = await modelGenerator(schema);
|
|
1849
2847
|
res.send({ schema, api: apiText, models: modelsText });
|
|
1850
2848
|
} catch (err) {
|
|
1851
2849
|
res.status(400).send({ error: err });
|
|
1852
2850
|
}
|
|
1853
2851
|
}
|
|
2852
|
+
async getMulterFilesIfAny(req, res, routeData) {
|
|
2853
|
+
var _a2;
|
|
2854
|
+
if (!((_a2 = req.header("content-type")) == null ? void 0 : _a2.includes("multipart/form-data"))) return;
|
|
2855
|
+
if (!this.isCustomRoute(routeData)) return;
|
|
2856
|
+
if (!routeData.fileUploadType) {
|
|
2857
|
+
throw new RsError("BAD_REQUEST", "File upload type not defined for route");
|
|
2858
|
+
}
|
|
2859
|
+
const multerFileUploadFunction = routeData.fileUploadType === "MULTIPLE" ? this.multerCommonUpload.array("files") : this.multerCommonUpload.single("file");
|
|
2860
|
+
return new Promise((resolve2, reject) => {
|
|
2861
|
+
multerFileUploadFunction(req, res, (err) => {
|
|
2862
|
+
if (err) {
|
|
2863
|
+
logger.warn("Multer error: " + err);
|
|
2864
|
+
reject(err);
|
|
2865
|
+
}
|
|
2866
|
+
if (req.body["data"]) req.body = JSON.parse(req.body["data"]);
|
|
2867
|
+
resolve2();
|
|
2868
|
+
});
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
1854
2871
|
async executeRouteLogic(req, res, next) {
|
|
1855
2872
|
try {
|
|
1856
2873
|
const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
|
|
1857
2874
|
this.validateAuthorization(req, routeData);
|
|
1858
|
-
|
|
2875
|
+
await this.getMulterFilesIfAny(req, res, routeData);
|
|
2876
|
+
requestValidator(req, routeData, this.customTypeValidation);
|
|
2877
|
+
if (this.isCustomRoute(routeData)) {
|
|
2878
|
+
await this.runCustomRouteLogic(req, res, routeData);
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
1859
2881
|
const data = await this.psqlEngine.runQueryForRoute(
|
|
1860
2882
|
req,
|
|
1861
2883
|
routeData,
|
|
1862
2884
|
this.schema
|
|
1863
2885
|
);
|
|
2886
|
+
this.responseValidator.validateResponseParams(data, req.baseUrl, routeData);
|
|
1864
2887
|
if (routeData.type === "PAGED") res.sendNoWrap(data);
|
|
1865
2888
|
else res.sendData(data);
|
|
1866
2889
|
} catch (e) {
|
|
@@ -1870,46 +2893,25 @@ var ResturaEngine = class {
|
|
|
1870
2893
|
isCustomRoute(route) {
|
|
1871
2894
|
return route.type === "CUSTOM_ONE" || route.type === "CUSTOM_ARRAY" || route.type === "CUSTOM_PAGED";
|
|
1872
2895
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
// // @ts-expect-error - Here we are dynamically calling the function from a custom class, not sure how to typescript this
|
|
1893
|
-
// const customFunction = customApi[functionName] as (
|
|
1894
|
-
// req: RsRequest<T>,
|
|
1895
|
-
// res: RsResponse<T>,
|
|
1896
|
-
// routeData: RouteData
|
|
1897
|
-
// ) => Promise<void>;
|
|
1898
|
-
// if (!customFunction) throw new RsError('NOT_FOUND', `API path ${routeData.path} not implemented`);
|
|
1899
|
-
// await customFunction(req, res, routeData);
|
|
1900
|
-
// }
|
|
1901
|
-
async generateHashForSchema(providedSchema) {
|
|
1902
|
-
const schemaPrettyStr = await prettier3.format(JSON.stringify(providedSchema), __spreadValues({
|
|
1903
|
-
parser: "json"
|
|
1904
|
-
}, {
|
|
1905
|
-
trailingComma: "none",
|
|
1906
|
-
tabWidth: 4,
|
|
1907
|
-
useTabs: true,
|
|
1908
|
-
endOfLine: "lf",
|
|
1909
|
-
printWidth: 120,
|
|
1910
|
-
singleQuote: true
|
|
1911
|
-
}));
|
|
1912
|
-
return createHash("sha256").update(schemaPrettyStr).digest("hex");
|
|
2896
|
+
async runCustomRouteLogic(req, res, routeData) {
|
|
2897
|
+
const version = req.baseUrl.split("/")[2];
|
|
2898
|
+
let domain = routeData.path.split("/")[1];
|
|
2899
|
+
domain = domain.split("-").reduce((acc, value, index) => {
|
|
2900
|
+
if (index === 0) acc = value;
|
|
2901
|
+
else acc += StringUtils3.capitalizeFirst(value);
|
|
2902
|
+
return acc;
|
|
2903
|
+
}, "");
|
|
2904
|
+
const customApiName = `${StringUtils3.capitalizeFirst(domain)}Api${StringUtils3.capitalizeFirst(version)}`;
|
|
2905
|
+
const customApi = customApiFactory_default.getCustomApi(customApiName);
|
|
2906
|
+
if (!customApi) throw new RsError("NOT_FOUND", `API domain ${domain}-${version} not found`);
|
|
2907
|
+
const functionName = `${routeData.method.toLowerCase()}${routeData.path.replace(new RegExp("-", "g"), "/").split("/").reduce((acc, cur) => {
|
|
2908
|
+
if (cur === "") return acc;
|
|
2909
|
+
return acc + StringUtils3.capitalizeFirst(cur);
|
|
2910
|
+
}, "")}`;
|
|
2911
|
+
const customFunction = customApi[functionName];
|
|
2912
|
+
if (!customFunction)
|
|
2913
|
+
throw new RsError("NOT_FOUND", `API path ${routeData.path} not implemented ${functionName}`);
|
|
2914
|
+
await customFunction(req, res, routeData);
|
|
1913
2915
|
}
|
|
1914
2916
|
async storeFileSystemSchema() {
|
|
1915
2917
|
const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema), __spreadValues({
|
|
@@ -1922,7 +2924,7 @@ var ResturaEngine = class {
|
|
|
1922
2924
|
printWidth: 120,
|
|
1923
2925
|
singleQuote: true
|
|
1924
2926
|
}));
|
|
1925
|
-
|
|
2927
|
+
fs4.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
|
|
1926
2928
|
}
|
|
1927
2929
|
resetPublicEndpoints() {
|
|
1928
2930
|
this.publicEndpoints = {
|
|
@@ -1939,13 +2941,13 @@ var ResturaEngine = class {
|
|
|
1939
2941
|
if (!routeData.roles.includes(role))
|
|
1940
2942
|
throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
|
|
1941
2943
|
}
|
|
1942
|
-
getRouteData(method, baseUrl,
|
|
2944
|
+
getRouteData(method, baseUrl, path5) {
|
|
1943
2945
|
const endpoint = this.schema.endpoints.find((item) => {
|
|
1944
2946
|
return item.baseUrl === baseUrl;
|
|
1945
2947
|
});
|
|
1946
2948
|
if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
|
|
1947
2949
|
const route = endpoint.routes.find((item) => {
|
|
1948
|
-
return item.method === method && item.path ===
|
|
2950
|
+
return item.method === method && item.path === path5;
|
|
1949
2951
|
});
|
|
1950
2952
|
if (!route) throw new RsError("NOT_FOUND", "Route not found");
|
|
1951
2953
|
return route;
|
|
@@ -1966,27 +2968,71 @@ __decorateClass([
|
|
|
1966
2968
|
__decorateClass([
|
|
1967
2969
|
boundMethod
|
|
1968
2970
|
], ResturaEngine.prototype, "getSchemaAndTypes", 1);
|
|
2971
|
+
__decorateClass([
|
|
2972
|
+
boundMethod
|
|
2973
|
+
], ResturaEngine.prototype, "getMulterFilesIfAny", 1);
|
|
1969
2974
|
__decorateClass([
|
|
1970
2975
|
boundMethod
|
|
1971
2976
|
], ResturaEngine.prototype, "executeRouteLogic", 1);
|
|
1972
2977
|
__decorateClass([
|
|
1973
2978
|
boundMethod
|
|
1974
2979
|
], ResturaEngine.prototype, "isCustomRoute", 1);
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
return val === null ? null : new Date(val).toISOString();
|
|
1979
|
-
});
|
|
1980
|
-
const BIGINT_OID = 20;
|
|
1981
|
-
types.setTypeParser(BIGINT_OID, (val) => {
|
|
1982
|
-
return val === null ? null : Number(val);
|
|
1983
|
-
});
|
|
1984
|
-
};
|
|
1985
|
-
setupPgReturnTypes();
|
|
2980
|
+
__decorateClass([
|
|
2981
|
+
boundMethod
|
|
2982
|
+
], ResturaEngine.prototype, "runCustomRouteLogic", 1);
|
|
1986
2983
|
var restura = new ResturaEngine();
|
|
2984
|
+
|
|
2985
|
+
// src/restura/sql/PsqlTransaction.ts
|
|
2986
|
+
import pg3 from "pg";
|
|
2987
|
+
var { Client: Client2 } = pg3;
|
|
2988
|
+
var PsqlTransaction = class extends PsqlConnection {
|
|
2989
|
+
constructor(clientConfig, instanceId) {
|
|
2990
|
+
super(instanceId);
|
|
2991
|
+
this.clientConfig = clientConfig;
|
|
2992
|
+
this.client = new Client2(clientConfig);
|
|
2993
|
+
this.connectPromise = this.client.connect();
|
|
2994
|
+
this.beginTransactionPromise = this.beginTransaction();
|
|
2995
|
+
}
|
|
2996
|
+
async close() {
|
|
2997
|
+
if (this.client) {
|
|
2998
|
+
await this.client.end();
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
async beginTransaction() {
|
|
3002
|
+
await this.connectPromise;
|
|
3003
|
+
return this.client.query("BEGIN");
|
|
3004
|
+
}
|
|
3005
|
+
async rollback() {
|
|
3006
|
+
return this.query("ROLLBACK");
|
|
3007
|
+
}
|
|
3008
|
+
async commit() {
|
|
3009
|
+
return this.query("COMMIT");
|
|
3010
|
+
}
|
|
3011
|
+
async release() {
|
|
3012
|
+
return this.client.end();
|
|
3013
|
+
}
|
|
3014
|
+
async query(query, values) {
|
|
3015
|
+
await this.connectPromise;
|
|
3016
|
+
await this.beginTransactionPromise;
|
|
3017
|
+
return this.client.query(query, values);
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
1987
3020
|
export {
|
|
3021
|
+
HtmlStatusCodes,
|
|
3022
|
+
PsqlConnection,
|
|
3023
|
+
PsqlEngine,
|
|
1988
3024
|
PsqlPool,
|
|
3025
|
+
PsqlTransaction,
|
|
3026
|
+
RsError,
|
|
3027
|
+
SQL,
|
|
3028
|
+
escapeColumnName,
|
|
3029
|
+
eventManager_default as eventManager,
|
|
3030
|
+
filterPsqlParser_default as filterPsqlParser,
|
|
3031
|
+
insertObjectQuery,
|
|
3032
|
+
isValueNumber2 as isValueNumber,
|
|
1989
3033
|
logger,
|
|
1990
|
-
|
|
3034
|
+
questionMarksToOrderedParams,
|
|
3035
|
+
restura,
|
|
3036
|
+
updateObjectQuery
|
|
1991
3037
|
};
|
|
1992
3038
|
//# sourceMappingURL=index.mjs.map
|