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