@restura/core 0.1.0-alpha.6 → 0.1.0-alpha.7
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/acorn-SW5GI5G7.mjs +3016 -0
- package/dist/acorn-SW5GI5G7.mjs.map +1 -0
- package/dist/angular-FYEH6QOL.mjs +1547 -0
- package/dist/angular-FYEH6QOL.mjs.map +1 -0
- package/dist/babel-V6GZHMYX.mjs +6911 -0
- package/dist/babel-V6GZHMYX.mjs.map +1 -0
- package/dist/chunk-TL4KRYOF.mjs +58 -0
- package/dist/chunk-TL4KRYOF.mjs.map +1 -0
- package/dist/estree-67ZCSSSI.mjs +4396 -0
- package/dist/estree-67ZCSSSI.mjs.map +1 -0
- package/dist/flow-SJW7PRXX.mjs +26365 -0
- package/dist/flow-SJW7PRXX.mjs.map +1 -0
- package/dist/glimmer-PF2X22V2.mjs +2995 -0
- package/dist/glimmer-PF2X22V2.mjs.map +1 -0
- package/dist/graphql-NOJ5HX7K.mjs +1253 -0
- package/dist/graphql-NOJ5HX7K.mjs.map +1 -0
- package/dist/html-ROPIWVPQ.mjs +2780 -0
- package/dist/html-ROPIWVPQ.mjs.map +1 -0
- package/dist/index.d.mts +1322 -2
- package/dist/index.d.ts +1322 -2
- package/dist/index.js +1229 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1219 -3
- package/dist/index.mjs.map +1 -1
- package/dist/markdown-PFYT4MSP.mjs +3423 -0
- package/dist/markdown-PFYT4MSP.mjs.map +1 -0
- package/dist/meriyah-5NLZXLMA.mjs +2356 -0
- package/dist/meriyah-5NLZXLMA.mjs.map +1 -0
- package/dist/postcss-ITO6IEN5.mjs +5027 -0
- package/dist/postcss-ITO6IEN5.mjs.map +1 -0
- package/dist/typescript-6IE7K56Q.mjs +13392 -0
- package/dist/typescript-6IE7K56Q.mjs.map +1 -0
- package/dist/yaml-DB2OVPLH.mjs +4230 -0
- package/dist/yaml-DB2OVPLH.mjs.map +1 -0
- package/package.json +18 -6
package/dist/index.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
6
22
|
var __export = (target, all) => {
|
|
7
23
|
for (var name in all)
|
|
8
24
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -15,6 +31,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
31
|
}
|
|
16
32
|
return to;
|
|
17
33
|
};
|
|
34
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
35
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
36
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
37
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
38
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
39
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
40
|
+
mod
|
|
41
|
+
));
|
|
18
42
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
43
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
20
44
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
@@ -28,10 +52,67 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
28
52
|
// src/index.ts
|
|
29
53
|
var src_exports = {};
|
|
30
54
|
__export(src_exports, {
|
|
55
|
+
logger: () => logger,
|
|
31
56
|
restura: () => restura
|
|
32
57
|
});
|
|
33
58
|
module.exports = __toCommonJS(src_exports);
|
|
34
59
|
|
|
60
|
+
// src/logger/logger.ts
|
|
61
|
+
var import_internal = require("@restura/internal");
|
|
62
|
+
var import_winston = __toESM(require("winston"));
|
|
63
|
+
var import_logform = require("logform");
|
|
64
|
+
|
|
65
|
+
// src/config.schema.ts
|
|
66
|
+
var import_zod = require("zod");
|
|
67
|
+
var loggerConfigSchema = import_zod.z.object({
|
|
68
|
+
level: import_zod.z.enum(["info", "warn", "error", "debug"]).default("info")
|
|
69
|
+
});
|
|
70
|
+
var resturaConfigSchema = import_zod.z.object({
|
|
71
|
+
authToken: import_zod.z.string().min(1, "Missing Restura Auth Token"),
|
|
72
|
+
sendErrorStackTrace: import_zod.z.boolean().default(false),
|
|
73
|
+
schemaFilePath: import_zod.z.string().default(process.cwd() + "/restura.schema.json"),
|
|
74
|
+
generatedTypesPath: import_zod.z.string().default(process.cwd() + "/src/@types")
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// src/logger/logger.ts
|
|
78
|
+
var loggerConfig = import_internal.config.validate("logger", loggerConfigSchema);
|
|
79
|
+
var consoleFormat = import_logform.format.combine(
|
|
80
|
+
import_logform.format.timestamp({
|
|
81
|
+
format: "YYYY-MM-DD HH:mm:ss.sss"
|
|
82
|
+
}),
|
|
83
|
+
import_logform.format.errors({ stack: true }),
|
|
84
|
+
import_logform.format.padLevels(),
|
|
85
|
+
import_logform.format.colorize({ all: true }),
|
|
86
|
+
import_logform.format.printf((info) => {
|
|
87
|
+
return `[${info.timestamp}] ${info.level} ${info.message}`;
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
var logger = import_winston.default.createLogger({
|
|
91
|
+
level: loggerConfig.level,
|
|
92
|
+
format: import_logform.format.combine(
|
|
93
|
+
import_logform.format.timestamp({
|
|
94
|
+
format: "YYYY-MM-DD HH:mm:ss.sss"
|
|
95
|
+
}),
|
|
96
|
+
import_logform.format.errors({ stack: true }),
|
|
97
|
+
import_logform.format.json()
|
|
98
|
+
),
|
|
99
|
+
//defaultMeta: { service: 'user-service' },
|
|
100
|
+
transports: [
|
|
101
|
+
//
|
|
102
|
+
// - Write to all logs with level `info` and below to `combined.log`
|
|
103
|
+
// - Write all logs error (and below) to `error.log`.
|
|
104
|
+
// - Write all logs to standard out.
|
|
105
|
+
//
|
|
106
|
+
// new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
|
107
|
+
// new winston.transports.File({ filename: 'combined.log' }),
|
|
108
|
+
new import_winston.default.transports.Console({ format: consoleFormat })
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// src/restura/restura.ts
|
|
113
|
+
var import_core_utils4 = require("@redskytech/core-utils");
|
|
114
|
+
var import_internal2 = require("@restura/internal");
|
|
115
|
+
|
|
35
116
|
// ../../node_modules/.pnpm/autobind-decorator@2.4.0/node_modules/autobind-decorator/lib/esm/index.js
|
|
36
117
|
function _typeof(obj) {
|
|
37
118
|
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
@@ -78,22 +159,1167 @@ function boundMethod(target, key, descriptor) {
|
|
|
78
159
|
};
|
|
79
160
|
}
|
|
80
161
|
|
|
162
|
+
// src/restura/restura.ts
|
|
163
|
+
var import_body_parser = __toESM(require("body-parser"));
|
|
164
|
+
var import_compression = __toESM(require("compression"));
|
|
165
|
+
var import_cookie_parser = __toESM(require("cookie-parser"));
|
|
166
|
+
var import_crypto = require("crypto");
|
|
167
|
+
var express = __toESM(require("express"));
|
|
168
|
+
var import_fs = __toESM(require("fs"));
|
|
169
|
+
var import_path = __toESM(require("path"));
|
|
170
|
+
var prettier3 = __toESM(require("prettier"));
|
|
171
|
+
|
|
172
|
+
// src/restura/errors.ts
|
|
173
|
+
var RsError = class _RsError {
|
|
174
|
+
constructor(errCode, message) {
|
|
175
|
+
this.err = errCode;
|
|
176
|
+
this.msg = message || "";
|
|
177
|
+
this.status = _RsError.htmlStatus(errCode);
|
|
178
|
+
this.stack = new Error().stack || "";
|
|
179
|
+
}
|
|
180
|
+
static htmlStatus(code) {
|
|
181
|
+
return htmlStatusMap[code];
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var htmlStatusMap = {
|
|
185
|
+
UNKNOWN_ERROR: 500 /* SERVER_ERROR */,
|
|
186
|
+
NOT_FOUND: 404 /* NOT_FOUND */,
|
|
187
|
+
EMAIL_TAKEN: 409 /* CONFLICT */,
|
|
188
|
+
FORBIDDEN: 403 /* FORBIDDEN */,
|
|
189
|
+
CONFLICT: 409 /* CONFLICT */,
|
|
190
|
+
UNAUTHORIZED: 401 /* UNAUTHORIZED */,
|
|
191
|
+
UPDATE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
192
|
+
CREATE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
193
|
+
DELETE_FORBIDDEN: 403 /* FORBIDDEN */,
|
|
194
|
+
DELETE_FAILURE: 500 /* SERVER_ERROR */,
|
|
195
|
+
BAD_REQUEST: 400 /* BAD_REQUEST */,
|
|
196
|
+
INVALID_TOKEN: 401 /* UNAUTHORIZED */,
|
|
197
|
+
INCORRECT_EMAIL_OR_PASSWORD: 401 /* UNAUTHORIZED */,
|
|
198
|
+
DUPLICATE_TOKEN: 409 /* CONFLICT */,
|
|
199
|
+
DUPLICATE_USERNAME: 409 /* CONFLICT */,
|
|
200
|
+
DUPLICATE_EMAIL: 409 /* CONFLICT */,
|
|
201
|
+
DUPLICATE: 409 /* CONFLICT */,
|
|
202
|
+
EMAIL_NOT_VERIFIED: 400 /* BAD_REQUEST */,
|
|
203
|
+
UPDATE_WITHOUT_ID: 400 /* BAD_REQUEST */,
|
|
204
|
+
CONNECTION_ERROR: 599 /* NETWORK_CONNECT_TIMEOUT */,
|
|
205
|
+
INVALID_PAYMENT: 403 /* FORBIDDEN */,
|
|
206
|
+
DECLINED_PAYMENT: 403 /* FORBIDDEN */,
|
|
207
|
+
INTEGRATION_ERROR: 500 /* SERVER_ERROR */,
|
|
208
|
+
CANNOT_RESERVE: 403 /* FORBIDDEN */,
|
|
209
|
+
REFUND_FAILURE: 403 /* FORBIDDEN */,
|
|
210
|
+
INVALID_INVOICE: 403 /* FORBIDDEN */,
|
|
211
|
+
INVALID_COUPON: 403 /* FORBIDDEN */,
|
|
212
|
+
SERVICE_UNAVAILABLE: 503 /* SERVICE_UNAVAILABLE */,
|
|
213
|
+
METHOD_UNALLOWED: 405 /* METHOD_NOT_ALLOWED */,
|
|
214
|
+
LOGIN_EXPIRED: 401 /* UNAUTHORIZED */,
|
|
215
|
+
THIRD_PARTY_ERROR: 400 /* BAD_REQUEST */,
|
|
216
|
+
ACCESS_DENIED: 403 /* FORBIDDEN */,
|
|
217
|
+
DATABASE_ERROR: 500 /* SERVER_ERROR */,
|
|
218
|
+
SCHEMA_ERROR: 500 /* SERVER_ERROR */
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/restura/SqlUtils.ts
|
|
222
|
+
var SqlUtils = class _SqlUtils {
|
|
223
|
+
static convertDatabaseTypeToTypescript(type, value) {
|
|
224
|
+
type = type.toLocaleLowerCase();
|
|
225
|
+
if (type.startsWith("tinyint") || type.startsWith("boolean")) return "boolean";
|
|
226
|
+
if (type.indexOf("int") > -1 || type.startsWith("decimal") || type.startsWith("double") || type.startsWith("float"))
|
|
227
|
+
return "number";
|
|
228
|
+
if (type === "json") {
|
|
229
|
+
if (!value) return "object";
|
|
230
|
+
return value.split(",").map((val) => {
|
|
231
|
+
return val.replace(/['"]/g, "");
|
|
232
|
+
}).join(" | ");
|
|
233
|
+
}
|
|
234
|
+
if (type.startsWith("varchar") || type.indexOf("text") > -1 || type.startsWith("char") || type.indexOf("blob") > -1 || type.startsWith("binary"))
|
|
235
|
+
return "string";
|
|
236
|
+
if (type.startsWith("date") || type.startsWith("time")) return "string";
|
|
237
|
+
if (type.startsWith("enum")) return _SqlUtils.convertDatabaseEnumToStringUnion(value || type);
|
|
238
|
+
return "any";
|
|
239
|
+
}
|
|
240
|
+
static convertDatabaseEnumToStringUnion(type) {
|
|
241
|
+
return type.replace(/^enum\(|\)/g, "").split(",").map((value) => {
|
|
242
|
+
return `'${value.replace(/'/g, "")}'`;
|
|
243
|
+
}).join(" | ");
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/restura/ResponseValidator.ts
|
|
248
|
+
var ResponseValidator = class _ResponseValidator {
|
|
249
|
+
constructor(schema) {
|
|
250
|
+
this.database = schema.database;
|
|
251
|
+
this.rootMap = {};
|
|
252
|
+
for (const endpoint of schema.endpoints) {
|
|
253
|
+
const endpointMap = {};
|
|
254
|
+
for (const route of endpoint.routes) {
|
|
255
|
+
if (_ResponseValidator.isCustomRoute(route)) {
|
|
256
|
+
endpointMap[`${route.method}:${route.path}`] = { validator: "any" };
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
endpointMap[`${route.method}:${route.path}`] = this.getRouteResponseType(route);
|
|
260
|
+
}
|
|
261
|
+
const endpointUrl = endpoint.baseUrl.endsWith("/") ? endpoint.baseUrl.slice(0, -1) : endpoint.baseUrl;
|
|
262
|
+
this.rootMap[endpointUrl] = { validator: endpointMap };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
validateResponseParams(data, endpointUrl, routeData) {
|
|
266
|
+
if (!this.rootMap) {
|
|
267
|
+
throw new RsError("BAD_REQUEST", "Cannot validate response without type maps");
|
|
268
|
+
}
|
|
269
|
+
const routeMap = this.rootMap[endpointUrl].validator[`${routeData.method}:${routeData.path}`];
|
|
270
|
+
data = this.validateAndCoerceMap("_base", data, routeMap);
|
|
271
|
+
}
|
|
272
|
+
getRouteResponseType(route) {
|
|
273
|
+
const map = {};
|
|
274
|
+
for (const field of route.response) {
|
|
275
|
+
map[field.name] = this.getFieldResponseType(field, route.table);
|
|
276
|
+
}
|
|
277
|
+
if (route.type === "PAGED") {
|
|
278
|
+
return {
|
|
279
|
+
validator: {
|
|
280
|
+
data: { validator: map, isArray: true },
|
|
281
|
+
total: { validator: "number" }
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
if (route.method === "DELETE") {
|
|
286
|
+
return {
|
|
287
|
+
validator: "boolean"
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return { validator: map, isArray: route.type === "ARRAY" };
|
|
291
|
+
}
|
|
292
|
+
getFieldResponseType(field, tableName) {
|
|
293
|
+
if (field.selector) {
|
|
294
|
+
return this.getTypeFromTable(field.selector, tableName);
|
|
295
|
+
} else if (field.subquery) {
|
|
296
|
+
const table = this.database.find((t) => t.name == tableName);
|
|
297
|
+
if (!table) return { isArray: true, validator: "any" };
|
|
298
|
+
const isOptional = table.roles.length > 0;
|
|
299
|
+
const validator = {};
|
|
300
|
+
for (const prop of field.subquery.properties) {
|
|
301
|
+
validator[prop.name] = this.getFieldResponseType(prop, field.subquery.table);
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
isArray: true,
|
|
305
|
+
isOptional,
|
|
306
|
+
validator
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return { validator: "any" };
|
|
310
|
+
}
|
|
311
|
+
getTypeFromTable(selector, name) {
|
|
312
|
+
const path2 = selector.split(".");
|
|
313
|
+
if (path2.length === 0 || path2.length > 2 || path2[0] === "") return { validator: "any", isOptional: false };
|
|
314
|
+
const tableName = path2.length == 2 ? path2[0] : name, columnName = path2.length == 2 ? path2[1] : path2[0];
|
|
315
|
+
const table = this.database.find((t) => t.name == tableName);
|
|
316
|
+
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
317
|
+
if (!table || !column) return { validator: "any", isOptional: false };
|
|
318
|
+
let validator = SqlUtils.convertDatabaseTypeToTypescript(
|
|
319
|
+
column.type,
|
|
320
|
+
column.value
|
|
321
|
+
);
|
|
322
|
+
if (!_ResponseValidator.validatorIsValidString(validator)) validator = this.parseValidationEnum(validator);
|
|
323
|
+
return {
|
|
324
|
+
validator,
|
|
325
|
+
isOptional: column.roles.length > 0 || column.isNullable
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
parseValidationEnum(validator) {
|
|
329
|
+
let terms = validator.split("|");
|
|
330
|
+
terms = terms.map((v) => v.replace(/'/g, "").trim());
|
|
331
|
+
return terms;
|
|
332
|
+
}
|
|
333
|
+
validateAndCoerceMap(name, value, { isOptional, isArray, validator }) {
|
|
334
|
+
if (validator === "any") return value;
|
|
335
|
+
const valueType = typeof value;
|
|
336
|
+
if (value == null) {
|
|
337
|
+
if (isOptional) return value;
|
|
338
|
+
throw new RsError("DATABASE_ERROR", `Response param (${name}) is required`);
|
|
339
|
+
}
|
|
340
|
+
if (isArray) {
|
|
341
|
+
if (!Array.isArray(value)) {
|
|
342
|
+
throw new RsError(
|
|
343
|
+
"DATABASE_ERROR",
|
|
344
|
+
`Response param (${name}) is a/an ${valueType} instead of an array`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
value.forEach((v, i) => this.validateAndCoerceMap(`${name}[${i}]`, v, { validator }));
|
|
348
|
+
return value;
|
|
349
|
+
}
|
|
350
|
+
if (typeof validator === "string") {
|
|
351
|
+
if (validator === "boolean" && valueType === "number") {
|
|
352
|
+
if (value !== 0 && value !== 1)
|
|
353
|
+
throw new RsError("DATABASE_ERROR", `Response param (${name}) is of the wrong type (${valueType})`);
|
|
354
|
+
return value === 1;
|
|
355
|
+
} else if (validator === "string" && valueType === "string") {
|
|
356
|
+
if (typeof value === "string" && value.match(
|
|
357
|
+
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.?\d*$|\d{2}:\d{2}:\d{2}.?\d*$|^\d{4}-\d{2}-\d{2}$/
|
|
358
|
+
)) {
|
|
359
|
+
const date = new Date(value);
|
|
360
|
+
if (date.toISOString() === "1970-01-01T00:00:00.000Z") return null;
|
|
361
|
+
const timezoneOffset = date.getTimezoneOffset() * 6e4;
|
|
362
|
+
return new Date(date.getTime() - timezoneOffset * 2).toISOString();
|
|
363
|
+
}
|
|
364
|
+
return value;
|
|
365
|
+
} else if (valueType === validator) {
|
|
366
|
+
return value;
|
|
367
|
+
} else if (valueType === "object") {
|
|
368
|
+
return value;
|
|
369
|
+
}
|
|
370
|
+
throw new RsError("DATABASE_ERROR", `Response param (${name}) is of the wrong type (${valueType})`);
|
|
371
|
+
}
|
|
372
|
+
if (Array.isArray(validator) && typeof value === "string") {
|
|
373
|
+
if (validator.includes(value)) return value;
|
|
374
|
+
throw new RsError("DATABASE_ERROR", `Response param (${name}) is not one of the enum options (${value})`);
|
|
375
|
+
}
|
|
376
|
+
if (valueType !== "object") {
|
|
377
|
+
throw new RsError("DATABASE_ERROR", `Response param (${name}) is of the wrong type (${valueType})`);
|
|
378
|
+
}
|
|
379
|
+
for (const prop in value) {
|
|
380
|
+
if (!validator[prop])
|
|
381
|
+
throw new RsError("DATABASE_ERROR", `Response param (${name}.${prop}) is not allowed`);
|
|
382
|
+
}
|
|
383
|
+
for (const prop in validator) {
|
|
384
|
+
value[prop] = this.validateAndCoerceMap(`${name}.${prop}`, value[prop], validator[prop]);
|
|
385
|
+
}
|
|
386
|
+
return value;
|
|
387
|
+
}
|
|
388
|
+
static isCustomRoute(route) {
|
|
389
|
+
return route.type === "CUSTOM_ONE" || route.type === "CUSTOM_ARRAY" || route.type === "CUSTOM_PAGED";
|
|
390
|
+
}
|
|
391
|
+
static validatorIsValidString(validator) {
|
|
392
|
+
return !validator.includes("|");
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/restura/apiGenerator.ts
|
|
397
|
+
var import_core_utils = require("@redskytech/core-utils");
|
|
398
|
+
var import_prettier = __toESM(require("prettier"));
|
|
399
|
+
var ApiTree = class _ApiTree {
|
|
400
|
+
constructor(namespace, database) {
|
|
401
|
+
this.database = database;
|
|
402
|
+
this.data = [];
|
|
403
|
+
this.namespace = namespace;
|
|
404
|
+
this.children = /* @__PURE__ */ new Map();
|
|
405
|
+
}
|
|
406
|
+
static createRootNode(database) {
|
|
407
|
+
return new _ApiTree(null, database);
|
|
408
|
+
}
|
|
409
|
+
static isRouteData(data) {
|
|
410
|
+
return data.method !== void 0;
|
|
411
|
+
}
|
|
412
|
+
static isEndpointData(data) {
|
|
413
|
+
return data.routes !== void 0;
|
|
414
|
+
}
|
|
415
|
+
addData(namespaces, route) {
|
|
416
|
+
if (import_core_utils.ObjectUtils.isEmpty(namespaces)) {
|
|
417
|
+
this.data.push(route);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const childName = namespaces[0];
|
|
421
|
+
this.children.set(childName, this.children.get(childName) || new _ApiTree(childName, this.database));
|
|
422
|
+
this.children.get(childName).addData(namespaces.slice(1), route);
|
|
423
|
+
}
|
|
424
|
+
createApiModels() {
|
|
425
|
+
let result = "";
|
|
426
|
+
for (const child of this.children.values()) {
|
|
427
|
+
result += child.createApiModelImpl(true);
|
|
428
|
+
}
|
|
429
|
+
return result;
|
|
430
|
+
}
|
|
431
|
+
createApiModelImpl(isBase) {
|
|
432
|
+
let result = ``;
|
|
433
|
+
for (const data of this.data) {
|
|
434
|
+
if (_ApiTree.isEndpointData(data)) {
|
|
435
|
+
result += _ApiTree.generateEndpointComments(data);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
result += isBase ? `
|
|
439
|
+
declare namespace ${this.namespace} {` : `
|
|
440
|
+
export namespace ${this.namespace} {`;
|
|
441
|
+
for (const data of this.data) {
|
|
442
|
+
if (_ApiTree.isRouteData(data)) {
|
|
443
|
+
result += this.generateRouteModels(data);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
for (const child of this.children.values()) {
|
|
447
|
+
result += child.createApiModelImpl(false);
|
|
448
|
+
}
|
|
449
|
+
result += "}";
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
static generateEndpointComments(endpoint) {
|
|
453
|
+
return `
|
|
454
|
+
// ${endpoint.name}
|
|
455
|
+
// ${endpoint.description}`;
|
|
456
|
+
}
|
|
457
|
+
generateRouteModels(route) {
|
|
458
|
+
let modelString = ``;
|
|
459
|
+
modelString += `
|
|
460
|
+
// ${route.name}
|
|
461
|
+
// ${route.description}
|
|
462
|
+
export namespace ${import_core_utils.StringUtils.capitalizeFirst(route.method.toLowerCase())} {
|
|
463
|
+
${this.generateRequestParameters(route)}
|
|
464
|
+
${this.generateResponseParameters(route)}
|
|
465
|
+
}`;
|
|
466
|
+
return modelString;
|
|
467
|
+
}
|
|
468
|
+
generateRequestParameters(route) {
|
|
469
|
+
let modelString = ``;
|
|
470
|
+
if (ResponseValidator.isCustomRoute(route) && route.requestType) {
|
|
471
|
+
modelString += `
|
|
472
|
+
export type Req = CustomTypes.${route.requestType}`;
|
|
473
|
+
return modelString;
|
|
474
|
+
}
|
|
475
|
+
if (!route.request) return modelString;
|
|
476
|
+
modelString += `
|
|
477
|
+
export interface Req{
|
|
478
|
+
${route.request.map((p) => {
|
|
479
|
+
let requestType = "any";
|
|
480
|
+
const oneOfValidator = p.validator.find((v) => v.type === "ONE_OF");
|
|
481
|
+
const typeCheckValidator = p.validator.find((v) => v.type === "TYPE_CHECK");
|
|
482
|
+
if (oneOfValidator && import_core_utils.ObjectUtils.isArrayWithData(oneOfValidator.value)) {
|
|
483
|
+
requestType = oneOfValidator.value.map((v) => `'${v}'`).join(" | ");
|
|
484
|
+
} else if (typeCheckValidator) {
|
|
485
|
+
switch (typeCheckValidator.value) {
|
|
486
|
+
case "string":
|
|
487
|
+
case "number":
|
|
488
|
+
case "boolean":
|
|
489
|
+
case "string[]":
|
|
490
|
+
case "number[]":
|
|
491
|
+
case "any[]":
|
|
492
|
+
requestType = typeCheckValidator.value;
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return `'${p.name}'${p.required ? "" : "?"}:${requestType}`;
|
|
497
|
+
}).join(";\n")}${import_core_utils.ObjectUtils.isArrayWithData(route.request) ? ";" : ""}
|
|
498
|
+
`;
|
|
499
|
+
modelString += `}`;
|
|
500
|
+
return modelString;
|
|
501
|
+
}
|
|
502
|
+
generateResponseParameters(route) {
|
|
503
|
+
if (ResponseValidator.isCustomRoute(route)) {
|
|
504
|
+
if (["number", "string", "boolean"].includes(route.responseType))
|
|
505
|
+
return `export type Res = ${route.responseType}`;
|
|
506
|
+
else if (["CUSTOM_ARRAY", "CUSTOM_PAGED"].includes(route.type))
|
|
507
|
+
return `export type Res = CustomTypes.${route.responseType}[]`;
|
|
508
|
+
else return `export type Res = CustomTypes.${route.responseType}`;
|
|
509
|
+
}
|
|
510
|
+
return `export interface Res ${this.getFields(route.response)}`;
|
|
511
|
+
}
|
|
512
|
+
getFields(fields) {
|
|
513
|
+
const nameFields = fields.map((f) => this.getNameAndType(f));
|
|
514
|
+
const nested = `{
|
|
515
|
+
${nameFields.join(";\n ")}${import_core_utils.ObjectUtils.isArrayWithData(nameFields) ? ";" : ""}
|
|
516
|
+
}`;
|
|
517
|
+
return nested;
|
|
518
|
+
}
|
|
519
|
+
getNameAndType(p) {
|
|
520
|
+
let responseType = "any", optional = false, array = false;
|
|
521
|
+
if (p.selector) {
|
|
522
|
+
({ responseType, optional } = this.getTypeFromTable(p.selector, p.name));
|
|
523
|
+
} else if (p.subquery) {
|
|
524
|
+
responseType = this.getFields(p.subquery.properties);
|
|
525
|
+
array = true;
|
|
526
|
+
}
|
|
527
|
+
return `${p.name}${optional ? "?" : ""}:${responseType}${array ? "[]" : ""}`;
|
|
528
|
+
}
|
|
529
|
+
getTypeFromTable(selector, name) {
|
|
530
|
+
const path2 = selector.split(".");
|
|
531
|
+
if (path2.length === 0 || path2.length > 2 || path2[0] === "") return { responseType: "any", optional: false };
|
|
532
|
+
let tableName = path2.length == 2 ? path2[0] : name;
|
|
533
|
+
const columnName = path2.length == 2 ? path2[1] : path2[0];
|
|
534
|
+
let table = this.database.find((t) => t.name == tableName);
|
|
535
|
+
if (!table && tableName.includes("_")) {
|
|
536
|
+
const tableAliasSplit = tableName.split("_");
|
|
537
|
+
tableName = tableAliasSplit[1];
|
|
538
|
+
table = this.database.find((t) => t.name == tableName);
|
|
539
|
+
}
|
|
540
|
+
const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
|
|
541
|
+
if (!table || !column) return { responseType: "any", optional: false };
|
|
542
|
+
return {
|
|
543
|
+
responseType: SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value),
|
|
544
|
+
optional: column.roles.length > 0 || column.isNullable
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
function pathToNamespaces(path2) {
|
|
549
|
+
return path2.split("/").map((e) => import_core_utils.StringUtils.toPascalCasing(e)).filter((e) => e);
|
|
550
|
+
}
|
|
551
|
+
function apiGenerator(schema, schemaHash) {
|
|
552
|
+
let apiString = `/** Auto generated file from Schema Hash (${schemaHash}). DO NOT MODIFY **/`;
|
|
553
|
+
const rootNamespace = ApiTree.createRootNode(schema.database);
|
|
554
|
+
for (const endpoint of schema.endpoints) {
|
|
555
|
+
const endpointNamespaces = pathToNamespaces(endpoint.baseUrl);
|
|
556
|
+
rootNamespace.addData(endpointNamespaces, endpoint);
|
|
557
|
+
for (const route of endpoint.routes) {
|
|
558
|
+
const fullNamespace = [...endpointNamespaces, ...pathToNamespaces(route.path)];
|
|
559
|
+
rootNamespace.addData(fullNamespace, route);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
apiString += rootNamespace.createApiModels();
|
|
563
|
+
if (schema.customTypes.length > 0) {
|
|
564
|
+
apiString += `
|
|
565
|
+
|
|
566
|
+
declare namespace CustomTypes {
|
|
567
|
+
${schema.customTypes}
|
|
568
|
+
}`;
|
|
569
|
+
}
|
|
570
|
+
return import_prettier.default.format(apiString, __spreadValues({
|
|
571
|
+
parser: "typescript"
|
|
572
|
+
}, {
|
|
573
|
+
trailingComma: "none",
|
|
574
|
+
tabWidth: 4,
|
|
575
|
+
useTabs: true,
|
|
576
|
+
endOfLine: "lf",
|
|
577
|
+
printWidth: 120,
|
|
578
|
+
singleQuote: true
|
|
579
|
+
}));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/restura/middleware/addApiResponseFunctions.ts
|
|
583
|
+
function addApiResponseFunctions(req, res, next) {
|
|
584
|
+
res.sendData = function(data, statusCode = 200) {
|
|
585
|
+
res.status(statusCode).send({ data });
|
|
586
|
+
};
|
|
587
|
+
res.sendNoWrap = function(dataNoWrap, statusCode = 200) {
|
|
588
|
+
res.status(statusCode).send(dataNoWrap);
|
|
589
|
+
};
|
|
590
|
+
res.sendPaginated = function(pagedData, statusCode = 200) {
|
|
591
|
+
res.status(statusCode).send({ data: pagedData.data, total: pagedData.total });
|
|
592
|
+
};
|
|
593
|
+
res.sendError = function(shortError, msg, htmlStatusCode, stack) {
|
|
594
|
+
if (htmlStatusCode === void 0) {
|
|
595
|
+
if (RsError.htmlStatus(shortError) !== void 0) {
|
|
596
|
+
htmlStatusCode = RsError.htmlStatus(shortError);
|
|
597
|
+
} else {
|
|
598
|
+
htmlStatusCode = 500;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const errorData = __spreadValues({
|
|
602
|
+
err: shortError,
|
|
603
|
+
msg
|
|
604
|
+
}, restura.resturaConfig.sendErrorStackTrace && stack ? { stack } : {});
|
|
605
|
+
res.status(htmlStatusCode).send(errorData);
|
|
606
|
+
};
|
|
607
|
+
next();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/restura/validateRequestParams.ts
|
|
611
|
+
var import_core_utils2 = require("@redskytech/core-utils");
|
|
612
|
+
var import_jsonschema = __toESM(require("jsonschema"));
|
|
613
|
+
function getRequestData(req) {
|
|
614
|
+
let body = "";
|
|
615
|
+
if (req.method === "GET" || req.method === "DELETE") {
|
|
616
|
+
body = "query";
|
|
617
|
+
} else {
|
|
618
|
+
body = "body";
|
|
619
|
+
}
|
|
620
|
+
const bodyData = req[body];
|
|
621
|
+
if (bodyData) {
|
|
622
|
+
for (const attr in bodyData) {
|
|
623
|
+
if (attr === "token") {
|
|
624
|
+
delete bodyData[attr];
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (bodyData[attr] instanceof Array) {
|
|
628
|
+
const attrList = [];
|
|
629
|
+
for (const value of bodyData[attr]) {
|
|
630
|
+
if (isNaN(Number(value))) continue;
|
|
631
|
+
attrList.push(Number(value));
|
|
632
|
+
}
|
|
633
|
+
if (import_core_utils2.ObjectUtils.isArrayWithData(attrList)) {
|
|
634
|
+
bodyData[attr] = attrList;
|
|
635
|
+
}
|
|
636
|
+
} else {
|
|
637
|
+
bodyData[attr] = import_core_utils2.ObjectUtils.safeParse(bodyData[attr]);
|
|
638
|
+
if (isNaN(Number(bodyData[attr]))) continue;
|
|
639
|
+
bodyData[attr] = Number(bodyData[attr]);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return bodyData;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/restura/restura.schema.ts
|
|
647
|
+
var import_zod2 = require("zod");
|
|
648
|
+
var orderBySchema = import_zod2.z.object({
|
|
649
|
+
columnName: import_zod2.z.string(),
|
|
650
|
+
order: import_zod2.z.enum(["ASC", "DESC"]),
|
|
651
|
+
tableName: import_zod2.z.string()
|
|
652
|
+
}).strict();
|
|
653
|
+
var groupBySchema = import_zod2.z.object({
|
|
654
|
+
columnName: import_zod2.z.string(),
|
|
655
|
+
tableName: import_zod2.z.string()
|
|
656
|
+
}).strict();
|
|
657
|
+
var whereDataSchema = import_zod2.z.object({
|
|
658
|
+
tableName: import_zod2.z.string().optional(),
|
|
659
|
+
columnName: import_zod2.z.string().optional(),
|
|
660
|
+
operator: import_zod2.z.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH"]).optional(),
|
|
661
|
+
value: import_zod2.z.string().optional(),
|
|
662
|
+
custom: import_zod2.z.string().optional(),
|
|
663
|
+
conjunction: import_zod2.z.enum(["AND", "OR"]).optional()
|
|
664
|
+
}).strict();
|
|
665
|
+
var assignmentDataSchema = import_zod2.z.object({
|
|
666
|
+
name: import_zod2.z.string(),
|
|
667
|
+
value: import_zod2.z.string()
|
|
668
|
+
}).strict();
|
|
669
|
+
var joinDataSchema = import_zod2.z.object({
|
|
670
|
+
table: import_zod2.z.string(),
|
|
671
|
+
localColumnName: import_zod2.z.string().optional(),
|
|
672
|
+
foreignColumnName: import_zod2.z.string().optional(),
|
|
673
|
+
custom: import_zod2.z.string().optional(),
|
|
674
|
+
type: import_zod2.z.enum(["LEFT", "INNER"]),
|
|
675
|
+
alias: import_zod2.z.string().optional()
|
|
676
|
+
}).strict();
|
|
677
|
+
var validatorDataSchema = import_zod2.z.object({
|
|
678
|
+
type: import_zod2.z.enum(["TYPE_CHECK", "MIN", "MAX", "ONE_OF"]),
|
|
679
|
+
value: 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())])
|
|
680
|
+
}).strict();
|
|
681
|
+
var requestDataSchema = import_zod2.z.object({
|
|
682
|
+
name: import_zod2.z.string(),
|
|
683
|
+
required: import_zod2.z.boolean(),
|
|
684
|
+
validator: import_zod2.z.array(validatorDataSchema)
|
|
685
|
+
}).strict();
|
|
686
|
+
var responseDataSchema = import_zod2.z.object({
|
|
687
|
+
name: import_zod2.z.string(),
|
|
688
|
+
selector: import_zod2.z.string().optional(),
|
|
689
|
+
subquery: import_zod2.z.object({
|
|
690
|
+
table: import_zod2.z.string(),
|
|
691
|
+
joins: import_zod2.z.array(joinDataSchema),
|
|
692
|
+
where: import_zod2.z.array(whereDataSchema),
|
|
693
|
+
properties: import_zod2.z.array(import_zod2.z.lazy(() => responseDataSchema)),
|
|
694
|
+
// Explicit type for the lazy schema
|
|
695
|
+
groupBy: groupBySchema.optional(),
|
|
696
|
+
orderBy: orderBySchema.optional()
|
|
697
|
+
}).optional()
|
|
698
|
+
}).strict();
|
|
699
|
+
var routeDataBaseSchema = import_zod2.z.object({
|
|
700
|
+
method: import_zod2.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
701
|
+
name: import_zod2.z.string(),
|
|
702
|
+
description: import_zod2.z.string(),
|
|
703
|
+
path: import_zod2.z.string(),
|
|
704
|
+
roles: import_zod2.z.array(import_zod2.z.string())
|
|
705
|
+
}).strict();
|
|
706
|
+
var standardRouteSchema = routeDataBaseSchema.extend({
|
|
707
|
+
type: import_zod2.z.enum(["ONE", "ARRAY", "PAGED"]),
|
|
708
|
+
table: import_zod2.z.string(),
|
|
709
|
+
joins: import_zod2.z.array(joinDataSchema),
|
|
710
|
+
assignments: import_zod2.z.array(assignmentDataSchema),
|
|
711
|
+
where: import_zod2.z.array(whereDataSchema),
|
|
712
|
+
request: import_zod2.z.array(requestDataSchema),
|
|
713
|
+
response: import_zod2.z.array(responseDataSchema),
|
|
714
|
+
groupBy: groupBySchema.optional(),
|
|
715
|
+
orderBy: orderBySchema.optional()
|
|
716
|
+
}).strict();
|
|
717
|
+
var customRouteSchema = routeDataBaseSchema.extend({
|
|
718
|
+
type: import_zod2.z.enum(["CUSTOM_ONE", "CUSTOM_ARRAY", "CUSTOM_PAGED"]),
|
|
719
|
+
responseType: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.enum(["string", "number", "boolean"])]),
|
|
720
|
+
requestType: import_zod2.z.string().optional(),
|
|
721
|
+
request: import_zod2.z.array(requestDataSchema).optional(),
|
|
722
|
+
fileUploadType: import_zod2.z.enum(["SINGLE", "MULTIPLE"]).optional()
|
|
723
|
+
}).strict();
|
|
724
|
+
var postgresColumnNumericTypesSchema = import_zod2.z.enum([
|
|
725
|
+
"SMALLINT",
|
|
726
|
+
// 2 bytes, -32,768 to 32,767
|
|
727
|
+
"INTEGER",
|
|
728
|
+
// 4 bytes, -2,147,483,648 to 2,147,483,647
|
|
729
|
+
"BIGINT",
|
|
730
|
+
// 8 bytes, -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
|
|
731
|
+
"DECIMAL",
|
|
732
|
+
// user-specified precision, exact numeric
|
|
733
|
+
"NUMERIC",
|
|
734
|
+
// same as DECIMAL
|
|
735
|
+
"REAL",
|
|
736
|
+
// 4 bytes, 6 decimal digits precision (single precision)
|
|
737
|
+
"DOUBLE PRECISION",
|
|
738
|
+
// 8 bytes, 15 decimal digits precision (double precision)
|
|
739
|
+
"SERIAL",
|
|
740
|
+
// auto-incrementing integer
|
|
741
|
+
"BIGSERIAL"
|
|
742
|
+
// auto-incrementing big integer
|
|
743
|
+
]);
|
|
744
|
+
var postgresColumnStringTypesSchema = import_zod2.z.enum([
|
|
745
|
+
"CHAR",
|
|
746
|
+
// fixed-length, blank-padded
|
|
747
|
+
"VARCHAR",
|
|
748
|
+
// variable-length with limit
|
|
749
|
+
"TEXT",
|
|
750
|
+
// variable-length without limit
|
|
751
|
+
"BYTEA"
|
|
752
|
+
// binary data
|
|
753
|
+
]);
|
|
754
|
+
var postgresColumnDateTypesSchema = import_zod2.z.enum([
|
|
755
|
+
"DATE",
|
|
756
|
+
// calendar date (year, month, day)
|
|
757
|
+
"TIMESTAMP",
|
|
758
|
+
// both date and time (without time zone)
|
|
759
|
+
"TIMESTAMPTZ",
|
|
760
|
+
// both date and time (with time zone)
|
|
761
|
+
"TIME",
|
|
762
|
+
// time of day (without time zone)
|
|
763
|
+
"INTERVAL"
|
|
764
|
+
// time span
|
|
765
|
+
]);
|
|
766
|
+
var mariaDbColumnNumericTypesSchema = import_zod2.z.enum([
|
|
767
|
+
"BOOLEAN",
|
|
768
|
+
// 1-byte A synonym for "TINYINT(1)". Supported from version 1.2.0 onwards.
|
|
769
|
+
"TINYINT",
|
|
770
|
+
// 1-byte A very small integer. Numeric value with scale 0. Signed: -126 to +127. Unsigned: 0 to 253.
|
|
771
|
+
"SMALLINT",
|
|
772
|
+
// 2-bytes A small integer. Signed: -32,766 to 32,767. Unsigned: 0 to 65,533.
|
|
773
|
+
"MEDIUMINT",
|
|
774
|
+
// 3-bytes A medium integer. Signed: -8388608 to 8388607. Unsigned: 0 to 16777215. Supported starting with MariaDB ColumnStore 1.4.2.
|
|
775
|
+
"INTEGER",
|
|
776
|
+
// 4-bytes A normal-size integer. Numeric value with scale 0. Signed: -2,147,483,646 to 2,147,483,647. Unsigned: 0 to 4,294,967,293
|
|
777
|
+
"BIGINT",
|
|
778
|
+
// 8-bytes A large integer. Numeric value with scale 0. Signed: -9,223,372,036,854,775,806 to +9,223,372,036,854,775,807 Unsigned: 0 to +18,446,744,073,709,551,613
|
|
779
|
+
"DECIMAL",
|
|
780
|
+
// 2, 4, or 8 bytes A packed fixed-point number that can have a specific total number of digits and with a set number of digits after a decimal. The maximum precision (total number of digits) that can be specified is 18.
|
|
781
|
+
"FLOAT",
|
|
782
|
+
// 4 bytes Stored in 32-bit IEEE-754 floating point format. As such, the number of significant digits is about 6, and the range of values is approximately +/- 1e38.
|
|
783
|
+
"DOUBLE"
|
|
784
|
+
// 8 bytes Stored in 64-bit IEEE-754 floating point format. As such, the number of significant digits is about 15 and the range of values is approximately +/-1e308.
|
|
785
|
+
]);
|
|
786
|
+
var mariaDbColumnStringTypesSchema = import_zod2.z.enum([
|
|
787
|
+
"CHAR",
|
|
788
|
+
// 1, 2, 4, or 8 bytes Holds letters and special characters of fixed length. Max length is 255. Default and minimum size is 1 byte.
|
|
789
|
+
"VARCHAR",
|
|
790
|
+
// 1, 2, 4, or 8 bytes or 8-byte token Holds letters, numbers, and special characters of variable length. Max length = 8000 bytes or characters and minimum length = 1 byte or character.
|
|
791
|
+
"TINYTEXT",
|
|
792
|
+
// 255 bytes Holds a small amount of letters, numbers, and special characters of variable length. Supported from version 1.1.0 onwards.
|
|
793
|
+
"TINYBLOB",
|
|
794
|
+
// 255 bytes Holds a small amount of binary data of variable length. Supported from version 1.1.0 onwards.
|
|
795
|
+
"TEXT",
|
|
796
|
+
// 64 KB Holds letters, numbers, and special characters of variable length. Supported from version 1.1.0 onwards.
|
|
797
|
+
"BLOB",
|
|
798
|
+
// 64 KB Holds binary data of variable length. Supported from version 1.1.0 onwards.
|
|
799
|
+
"MEDIUMTEXT",
|
|
800
|
+
// 16 MB Holds a medium amount of letters, numbers, and special characters of variable length. Supported from version 1.1.0 onwards.
|
|
801
|
+
"MEDIUMBLOB",
|
|
802
|
+
// 16 MB Holds a medium amount of binary data of variable length. Supported from version 1.1.0 onwards.
|
|
803
|
+
"LONGTEXT",
|
|
804
|
+
// 1.96 GB Holds a large amount of letters, numbers, and special characters of variable length. Supported from version 1.1.0 onwards.
|
|
805
|
+
"JSON",
|
|
806
|
+
// Alias for LONGTEXT, creates a CONSTRAINT for JSON_VALID, holds a JSON-formatted string of plain text.
|
|
807
|
+
"LONGBLOB",
|
|
808
|
+
// 1.96 GB Holds a large amount of binary data of variable length. Supported from version 1.1.0 onwards.
|
|
809
|
+
"ENUM"
|
|
810
|
+
// Enum type
|
|
811
|
+
]);
|
|
812
|
+
var mariaDbColumnDateTypesSchema = import_zod2.z.enum([
|
|
813
|
+
"DATE",
|
|
814
|
+
// 4-bytes Date has year, month, and day.
|
|
815
|
+
"DATETIME",
|
|
816
|
+
// 8-bytes A date and time combination. Supported range is 1000-01-01 00:00:00 to 9999-12-31 23:59:59. From version 1.2.0 microseconds are also supported.
|
|
817
|
+
"TIME",
|
|
818
|
+
// 8-bytes Holds hour, minute, second and optionally microseconds for time.
|
|
819
|
+
"TIMESTAMP"
|
|
820
|
+
// 4-bytes Values are stored as the number of seconds since 1970-01-01 00:00:00 UTC, and optionally microseconds.
|
|
821
|
+
]);
|
|
822
|
+
var columnDataSchema = import_zod2.z.object({
|
|
823
|
+
name: import_zod2.z.string(),
|
|
824
|
+
type: import_zod2.z.union([
|
|
825
|
+
postgresColumnNumericTypesSchema,
|
|
826
|
+
postgresColumnStringTypesSchema,
|
|
827
|
+
postgresColumnDateTypesSchema,
|
|
828
|
+
mariaDbColumnNumericTypesSchema,
|
|
829
|
+
mariaDbColumnStringTypesSchema,
|
|
830
|
+
mariaDbColumnDateTypesSchema
|
|
831
|
+
]),
|
|
832
|
+
isNullable: import_zod2.z.boolean(),
|
|
833
|
+
roles: import_zod2.z.array(import_zod2.z.string()),
|
|
834
|
+
comment: import_zod2.z.string().optional(),
|
|
835
|
+
default: import_zod2.z.string().optional(),
|
|
836
|
+
value: import_zod2.z.string().optional(),
|
|
837
|
+
isPrimary: import_zod2.z.boolean().optional(),
|
|
838
|
+
isUnique: import_zod2.z.boolean().optional(),
|
|
839
|
+
hasAutoIncrement: import_zod2.z.boolean().optional(),
|
|
840
|
+
length: import_zod2.z.number().optional()
|
|
841
|
+
}).strict();
|
|
842
|
+
var indexDataSchema = import_zod2.z.object({
|
|
843
|
+
name: import_zod2.z.string(),
|
|
844
|
+
columns: import_zod2.z.array(import_zod2.z.string()),
|
|
845
|
+
isUnique: import_zod2.z.boolean(),
|
|
846
|
+
isPrimaryKey: import_zod2.z.boolean(),
|
|
847
|
+
order: import_zod2.z.enum(["ASC", "DESC"])
|
|
848
|
+
}).strict();
|
|
849
|
+
var foreignKeyActionsSchema = import_zod2.z.enum([
|
|
850
|
+
"CASCADE",
|
|
851
|
+
// CASCADE action for foreign keys
|
|
852
|
+
"SET NULL",
|
|
853
|
+
// SET NULL action for foreign keys
|
|
854
|
+
"RESTRICT",
|
|
855
|
+
// RESTRICT action for foreign keys
|
|
856
|
+
"NO ACTION",
|
|
857
|
+
// NO ACTION for foreign keys
|
|
858
|
+
"SET DEFAULT"
|
|
859
|
+
// SET DEFAULT action for foreign keys
|
|
860
|
+
]);
|
|
861
|
+
var foreignKeyDataSchema = import_zod2.z.object({
|
|
862
|
+
name: import_zod2.z.string(),
|
|
863
|
+
column: import_zod2.z.string(),
|
|
864
|
+
refTable: import_zod2.z.string(),
|
|
865
|
+
refColumn: import_zod2.z.string(),
|
|
866
|
+
onDelete: foreignKeyActionsSchema,
|
|
867
|
+
onUpdate: foreignKeyActionsSchema
|
|
868
|
+
}).strict();
|
|
869
|
+
var checkConstraintDataSchema = import_zod2.z.object({
|
|
870
|
+
name: import_zod2.z.string(),
|
|
871
|
+
check: import_zod2.z.string()
|
|
872
|
+
}).strict();
|
|
873
|
+
var tableDataSchema = import_zod2.z.object({
|
|
874
|
+
name: import_zod2.z.string(),
|
|
875
|
+
columns: import_zod2.z.array(columnDataSchema),
|
|
876
|
+
indexes: import_zod2.z.array(indexDataSchema),
|
|
877
|
+
foreignKeys: import_zod2.z.array(foreignKeyDataSchema),
|
|
878
|
+
checkConstraints: import_zod2.z.array(checkConstraintDataSchema),
|
|
879
|
+
roles: import_zod2.z.array(import_zod2.z.string())
|
|
880
|
+
}).strict();
|
|
881
|
+
var endpointDataSchema = import_zod2.z.object({
|
|
882
|
+
name: import_zod2.z.string(),
|
|
883
|
+
description: import_zod2.z.string(),
|
|
884
|
+
baseUrl: import_zod2.z.string(),
|
|
885
|
+
routes: import_zod2.z.array(import_zod2.z.union([standardRouteSchema, customRouteSchema]))
|
|
886
|
+
}).strict();
|
|
887
|
+
var resturaZodSchema = import_zod2.z.object({
|
|
888
|
+
database: import_zod2.z.array(tableDataSchema),
|
|
889
|
+
endpoints: import_zod2.z.array(endpointDataSchema),
|
|
890
|
+
globalParams: import_zod2.z.array(import_zod2.z.string()),
|
|
891
|
+
roles: import_zod2.z.array(import_zod2.z.string()),
|
|
892
|
+
customTypes: import_zod2.z.string()
|
|
893
|
+
}).strict();
|
|
894
|
+
async function isSchemaValid(schemaToCheck) {
|
|
895
|
+
try {
|
|
896
|
+
resturaZodSchema.parse(schemaToCheck);
|
|
897
|
+
return true;
|
|
898
|
+
} catch (error) {
|
|
899
|
+
logger.error(error);
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/restura/middleware/schemaValidation.ts
|
|
905
|
+
async function schemaValidation(req, res, next) {
|
|
906
|
+
req.data = getRequestData(req);
|
|
907
|
+
try {
|
|
908
|
+
resturaZodSchema.parse(req.data);
|
|
909
|
+
next();
|
|
910
|
+
} catch (error) {
|
|
911
|
+
logger.error(error);
|
|
912
|
+
res.sendError("BAD_REQUEST", error, 400 /* BAD_REQUEST */);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/restura/modelGenerator.ts
|
|
917
|
+
var import_core_utils3 = require("@redskytech/core-utils");
|
|
918
|
+
var import_prettier2 = __toESM(require("prettier"));
|
|
919
|
+
function modelGenerator(schema, schemaHash) {
|
|
920
|
+
let modelString = `/** Auto generated file from Schema Hash (${schemaHash}). DO NOT MODIFY **/
|
|
921
|
+
`;
|
|
922
|
+
modelString += `declare namespace Model {
|
|
923
|
+
`;
|
|
924
|
+
for (const table of schema.database) {
|
|
925
|
+
modelString += convertTable(table);
|
|
926
|
+
}
|
|
927
|
+
modelString += `}`;
|
|
928
|
+
return import_prettier2.default.format(modelString, __spreadValues({
|
|
929
|
+
parser: "typescript"
|
|
930
|
+
}, {
|
|
931
|
+
trailingComma: "none",
|
|
932
|
+
tabWidth: 4,
|
|
933
|
+
useTabs: true,
|
|
934
|
+
endOfLine: "lf",
|
|
935
|
+
printWidth: 120,
|
|
936
|
+
singleQuote: true
|
|
937
|
+
}));
|
|
938
|
+
}
|
|
939
|
+
function convertTable(table) {
|
|
940
|
+
let modelString = ` export interface ${import_core_utils3.StringUtils.capitalizeFirst(table.name)} {
|
|
941
|
+
`;
|
|
942
|
+
for (const column of table.columns) {
|
|
943
|
+
modelString += ` ${column.name}${column.isNullable ? "?" : ""}: ${SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value)};
|
|
944
|
+
`;
|
|
945
|
+
}
|
|
946
|
+
modelString += ` }
|
|
947
|
+
`;
|
|
948
|
+
return modelString;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/restura/middleware/authenticateUser.ts
|
|
952
|
+
async function authenticateUser(applicationAuthenticateHandler) {
|
|
953
|
+
return (req, res, next) => {
|
|
954
|
+
applicationAuthenticateHandler(
|
|
955
|
+
req,
|
|
956
|
+
(userDetails) => {
|
|
957
|
+
req.requesterDetails = __spreadValues(__spreadValues({}, req.requesterDetails), userDetails);
|
|
958
|
+
next();
|
|
959
|
+
},
|
|
960
|
+
(errorMessage) => {
|
|
961
|
+
res.sendError("UNAUTHORIZED", errorMessage);
|
|
962
|
+
}
|
|
963
|
+
);
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
81
967
|
// src/restura/restura.ts
|
|
82
968
|
var ResturaEngine = class {
|
|
83
|
-
|
|
969
|
+
constructor() {
|
|
970
|
+
this.publicEndpoints = {
|
|
971
|
+
GET: [],
|
|
972
|
+
POST: [],
|
|
973
|
+
PUT: [],
|
|
974
|
+
PATCH: [],
|
|
975
|
+
DELETE: []
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
// private customTypeValidation!: ValidationDictionary;
|
|
979
|
+
/**
|
|
980
|
+
* Initializes the Restura engine with the provided Express application.
|
|
981
|
+
*
|
|
982
|
+
* @param app - The Express application instance to initialize with Restura.
|
|
983
|
+
* @returns A promise that resolves when the initialization is complete.
|
|
984
|
+
*/
|
|
985
|
+
async init(app, authenticationHandler) {
|
|
986
|
+
this.resturaConfig = import_internal2.config.validate("restura", resturaConfigSchema);
|
|
987
|
+
this.authenticationHandler = authenticationHandler;
|
|
988
|
+
app.use((0, import_compression.default)());
|
|
989
|
+
app.use(import_body_parser.default.json({ limit: "32mb" }));
|
|
990
|
+
app.use(import_body_parser.default.urlencoded({ limit: "32mb", extended: false }));
|
|
991
|
+
app.use((0, import_cookie_parser.default)());
|
|
992
|
+
app.disable("x-powered-by");
|
|
993
|
+
app.use("/", addApiResponseFunctions);
|
|
994
|
+
app.use("/api/", authenticateUser);
|
|
84
995
|
app.use("/restura", this.resturaAuthentication);
|
|
85
|
-
|
|
996
|
+
app.put(
|
|
997
|
+
"/restura/v1/schema",
|
|
998
|
+
schemaValidation,
|
|
999
|
+
this.updateSchema
|
|
1000
|
+
);
|
|
1001
|
+
app.post(
|
|
1002
|
+
"/restura/v1/schema/preview",
|
|
1003
|
+
schemaValidation,
|
|
1004
|
+
this.previewCreateSchema
|
|
1005
|
+
);
|
|
1006
|
+
app.get("/restura/v1/schema", this.getSchema);
|
|
1007
|
+
app.get("/restura/v1/schema/types", this.getSchemaAndTypes);
|
|
1008
|
+
this.expressApp = app;
|
|
1009
|
+
await this.reloadEndpoints();
|
|
1010
|
+
this.validateGeneratedTypesFolder();
|
|
1011
|
+
logger.info("Restura Engine Initialized");
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Determines if a given endpoint is public based on the HTTP method and full URL. This
|
|
1015
|
+
* is determined on whether the endpoint in the schema has no roles assigned to it.
|
|
1016
|
+
*
|
|
1017
|
+
* @param method - The HTTP method (e.g., 'GET', 'POST', 'PUT', 'PATCH', 'DELETE').
|
|
1018
|
+
* @param fullUrl - The full URL of the endpoint.
|
|
1019
|
+
* @returns A boolean indicating whether the endpoint is public.
|
|
1020
|
+
*/
|
|
1021
|
+
isEndpointPublic(method, fullUrl) {
|
|
1022
|
+
if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method)) return false;
|
|
1023
|
+
return this.publicEndpoints[method].includes(fullUrl);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Checks if an endpoint exists for a given HTTP method and full URL.
|
|
1027
|
+
*
|
|
1028
|
+
* @param method - The HTTP method to check (e.g., 'GET', 'POST', 'PUT', 'PATCH', 'DELETE').
|
|
1029
|
+
* @param fullUrl - The full URL of the endpoint to check.
|
|
1030
|
+
* @returns `true` if the endpoint exists, otherwise `false`.
|
|
1031
|
+
*/
|
|
1032
|
+
doesEndpointExist(method, fullUrl) {
|
|
1033
|
+
return this.schema.endpoints.some((endpoint) => {
|
|
1034
|
+
if (!fullUrl.startsWith(endpoint.baseUrl)) return false;
|
|
1035
|
+
const pathWithoutBaseUrl = fullUrl.replace(endpoint.baseUrl, "");
|
|
1036
|
+
return endpoint.routes.some((route) => {
|
|
1037
|
+
return route.method === method && route.path === pathWithoutBaseUrl;
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Generates an API from the provided schema and writes it to the specified output file.
|
|
1043
|
+
*
|
|
1044
|
+
* @param outputFile - The path to the file where the generated API will be written.
|
|
1045
|
+
* @param providedSchema - The schema from which the API will be generated.
|
|
1046
|
+
* @returns A promise that resolves when the API has been successfully generated and written to the output file.
|
|
1047
|
+
*/
|
|
1048
|
+
async generateApiFromSchema(outputFile, providedSchema) {
|
|
1049
|
+
import_fs.default.writeFileSync(
|
|
1050
|
+
outputFile,
|
|
1051
|
+
await apiGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Generates a model from the provided schema and writes it to the specified output file.
|
|
1056
|
+
*
|
|
1057
|
+
* @param outputFile - The path to the file where the generated model will be written.
|
|
1058
|
+
* @param providedSchema - The schema from which the model will be generated.
|
|
1059
|
+
* @returns A promise that resolves when the model has been successfully written to the output file.
|
|
1060
|
+
*/
|
|
1061
|
+
async generateModelFromSchema(outputFile, providedSchema) {
|
|
1062
|
+
import_fs.default.writeFileSync(
|
|
1063
|
+
outputFile,
|
|
1064
|
+
await modelGenerator(providedSchema, await this.generateHashForSchema(providedSchema))
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Retrieves the latest file system schema for Restura.
|
|
1069
|
+
*
|
|
1070
|
+
* @returns {Promise<ResturaSchema>} A promise that resolves to the latest Restura schema.
|
|
1071
|
+
* @throws {Error} If the schema file is missing or the schema is not valid.
|
|
1072
|
+
*/
|
|
1073
|
+
async getLatestFileSystemSchema() {
|
|
1074
|
+
if (!import_fs.default.existsSync(this.resturaConfig.schemaFilePath)) {
|
|
1075
|
+
logger.error(`Missing restura schema file, expected path: ${this.resturaConfig.schemaFilePath}`);
|
|
1076
|
+
throw new Error("Missing restura schema file");
|
|
1077
|
+
}
|
|
1078
|
+
const schemaFileData = import_fs.default.readFileSync(this.resturaConfig.schemaFilePath, { encoding: "utf8" });
|
|
1079
|
+
const schema = import_core_utils4.ObjectUtils.safeParse(schemaFileData);
|
|
1080
|
+
const isValid = await isSchemaValid(schema);
|
|
1081
|
+
if (!isValid) {
|
|
1082
|
+
logger.error("Schema is not valid");
|
|
1083
|
+
throw new Error("Schema is not valid");
|
|
1084
|
+
}
|
|
1085
|
+
return schema;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Asynchronously generates and retrieves hashes for the provided schema and related generated files.
|
|
1089
|
+
*
|
|
1090
|
+
* @param providedSchema - The schema for which hashes need to be generated.
|
|
1091
|
+
* @returns A promise that resolves to an object containing:
|
|
1092
|
+
* - `schemaHash`: The hash of the provided schema.
|
|
1093
|
+
* - `apiCreatedSchemaHash`: The hash extracted from the generated `api.d.ts` file.
|
|
1094
|
+
* - `modelCreatedSchemaHash`: The hash extracted from the generated `models.d.ts` file.
|
|
1095
|
+
*/
|
|
1096
|
+
async getHashes(providedSchema) {
|
|
1097
|
+
var _a, _b, _c, _d;
|
|
1098
|
+
const schemaHash = await this.generateHashForSchema(providedSchema);
|
|
1099
|
+
const apiFile = import_fs.default.readFileSync(import_path.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1100
|
+
const apiCreatedSchemaHash = (_b = (_a = apiFile.toString().match(/\((.*)\)/)) == null ? void 0 : _a[1]) != null ? _b : "";
|
|
1101
|
+
const modelFile = import_fs.default.readFileSync(import_path.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1102
|
+
const modelCreatedSchemaHash = (_d = (_c = modelFile.toString().match(/\((.*)\)/)) == null ? void 0 : _c[1]) != null ? _d : "";
|
|
1103
|
+
return {
|
|
1104
|
+
schemaHash,
|
|
1105
|
+
apiCreatedSchemaHash,
|
|
1106
|
+
modelCreatedSchemaHash
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
async reloadEndpoints() {
|
|
1110
|
+
this.schema = await this.getLatestFileSystemSchema();
|
|
1111
|
+
this.resturaRouter = express.Router();
|
|
1112
|
+
this.resetPublicEndpoints();
|
|
1113
|
+
let routeCount = 0;
|
|
1114
|
+
for (const endpoint of this.schema.endpoints) {
|
|
1115
|
+
const baseUrl = endpoint.baseUrl.endsWith("/") ? endpoint.baseUrl.slice(0, -1) : endpoint.baseUrl;
|
|
1116
|
+
this.expressApp.use(baseUrl, (req, res, next) => {
|
|
1117
|
+
this.resturaRouter(req, res, next);
|
|
1118
|
+
});
|
|
1119
|
+
for (const route of endpoint.routes) {
|
|
1120
|
+
route.path = route.path.startsWith("/") ? route.path : `/${route.path}`;
|
|
1121
|
+
route.path = route.path.endsWith("/") ? route.path.slice(0, -1) : route.path;
|
|
1122
|
+
const fullUrl = `${baseUrl}${route.path}`;
|
|
1123
|
+
if (route.roles.length === 0) this.publicEndpoints[route.method].push(fullUrl);
|
|
1124
|
+
this.resturaRouter[route.method.toLowerCase()](
|
|
1125
|
+
route.path,
|
|
1126
|
+
// <-- Notice we only use path here since the baseUrl is already added to the router.
|
|
1127
|
+
this.executeRouteLogic
|
|
1128
|
+
);
|
|
1129
|
+
routeCount++;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
this.responseValidator = new ResponseValidator(this.schema);
|
|
1133
|
+
logger.info(`Restura loaded (${routeCount}) endpoint${routeCount > 1 ? "s" : ""}`);
|
|
1134
|
+
}
|
|
1135
|
+
async validateGeneratedTypesFolder() {
|
|
1136
|
+
if (!import_fs.default.existsSync(this.resturaConfig.generatedTypesPath)) {
|
|
1137
|
+
import_fs.default.mkdirSync(this.resturaConfig.generatedTypesPath, { recursive: true });
|
|
1138
|
+
}
|
|
1139
|
+
const hasApiFile = import_fs.default.existsSync(import_path.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"));
|
|
1140
|
+
const hasModelsFile = import_fs.default.existsSync(import_path.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"));
|
|
1141
|
+
if (!hasApiFile) {
|
|
1142
|
+
await this.generateApiFromSchema(import_path.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1143
|
+
}
|
|
1144
|
+
if (!hasModelsFile) {
|
|
1145
|
+
await this.generateModelFromSchema(
|
|
1146
|
+
import_path.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1147
|
+
this.schema
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
const hashes = await this.getHashes(this.schema);
|
|
1151
|
+
if (hashes.schemaHash !== hashes.apiCreatedSchemaHash) {
|
|
1152
|
+
await this.generateApiFromSchema(import_path.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1153
|
+
}
|
|
1154
|
+
if (hashes.schemaHash !== hashes.modelCreatedSchemaHash) {
|
|
1155
|
+
await this.generateModelFromSchema(
|
|
1156
|
+
import_path.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1157
|
+
this.schema
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
86
1160
|
}
|
|
87
1161
|
resturaAuthentication(req, res, next) {
|
|
88
|
-
|
|
1162
|
+
if (req.headers["x-auth-token"] !== this.resturaConfig.authToken) res.status(401).send("Unauthorized");
|
|
1163
|
+
else next();
|
|
1164
|
+
}
|
|
1165
|
+
async previewCreateSchema(req, res) {
|
|
1166
|
+
try {
|
|
1167
|
+
const schemaDiff = { commands: "", endPoints: [], globalParams: [], roles: [], customTypes: false };
|
|
1168
|
+
res.send({ data: schemaDiff });
|
|
1169
|
+
} catch (err) {
|
|
1170
|
+
res.status(400).send(err);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
async updateSchema(req, res) {
|
|
1174
|
+
try {
|
|
1175
|
+
this.schema = req.data;
|
|
1176
|
+
await this.storeFileSystemSchema();
|
|
1177
|
+
await this.reloadEndpoints();
|
|
1178
|
+
await this.updateTypes();
|
|
1179
|
+
res.send({ data: "success" });
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
if (err instanceof Error) res.status(400).send(err.message);
|
|
1182
|
+
else res.status(400).send("Unknown error");
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
async updateTypes() {
|
|
1186
|
+
await this.generateApiFromSchema(import_path.default.join(this.resturaConfig.generatedTypesPath, "api.d.ts"), this.schema);
|
|
1187
|
+
await this.generateModelFromSchema(
|
|
1188
|
+
import_path.default.join(this.resturaConfig.generatedTypesPath, "models.d.ts"),
|
|
1189
|
+
this.schema
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
async getSchema(req, res) {
|
|
1193
|
+
res.send({ data: this.schema });
|
|
1194
|
+
}
|
|
1195
|
+
async getSchemaAndTypes(req, res) {
|
|
1196
|
+
try {
|
|
1197
|
+
const schema = await this.getLatestFileSystemSchema();
|
|
1198
|
+
const schemaHash = await this.generateHashForSchema(schema);
|
|
1199
|
+
const apiText = await apiGenerator(schema, schemaHash);
|
|
1200
|
+
const modelsText = await modelGenerator(schema, schemaHash);
|
|
1201
|
+
res.send({ schema, api: apiText, models: modelsText });
|
|
1202
|
+
} catch (err) {
|
|
1203
|
+
res.status(400).send({ error: err });
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
async executeRouteLogic(req, res, next) {
|
|
1207
|
+
try {
|
|
1208
|
+
const routeData = this.getRouteData(req.method, req.baseUrl, req.path);
|
|
1209
|
+
this.validateAuthorization(req, routeData);
|
|
1210
|
+
} catch (e) {
|
|
1211
|
+
next(e);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
isCustomRoute(route) {
|
|
1215
|
+
return route.type === "CUSTOM_ONE" || route.type === "CUSTOM_ARRAY" || route.type === "CUSTOM_PAGED";
|
|
1216
|
+
}
|
|
1217
|
+
// @boundMethod
|
|
1218
|
+
// private async runCustomRouteLogic<T>(req: RsRequest<T>, res: RsResponse<T>, routeData: RouteData) {
|
|
1219
|
+
// const version = req.baseUrl.split('/')[2];
|
|
1220
|
+
// let domain = routeData.path.split('/')[1];
|
|
1221
|
+
// domain = domain.split('-').reduce((acc, value, index) => {
|
|
1222
|
+
// if (index === 0) acc = value;
|
|
1223
|
+
// else acc += StringUtils.capitalizeFirst(value);
|
|
1224
|
+
// return acc;
|
|
1225
|
+
// }, '');
|
|
1226
|
+
// const customApiName = `${StringUtils.capitalizeFirst(domain)}Api${StringUtils.capitalizeFirst(version)}`;
|
|
1227
|
+
// const customApi = apiFactory.getCustomApi(customApiName);
|
|
1228
|
+
// if (!customApi) throw new RsError('NOT_FOUND', `API domain ${domain}-${version} not found`);
|
|
1229
|
+
// const functionName = `${routeData.method.toLowerCase()}${routeData.path
|
|
1230
|
+
// .replace(new RegExp('-', 'g'), '/')
|
|
1231
|
+
// .split('/')
|
|
1232
|
+
// .reduce((acc, cur) => {
|
|
1233
|
+
// if (cur === '') return acc;
|
|
1234
|
+
// return acc + StringUtils.capitalizeFirst(cur);
|
|
1235
|
+
// }, '')}`;
|
|
1236
|
+
// // @ts-expect-error - Here we are dynamically calling the function from a custom class, not sure how to typescript this
|
|
1237
|
+
// const customFunction = customApi[functionName] as (
|
|
1238
|
+
// req: RsRequest<T>,
|
|
1239
|
+
// res: RsResponse<T>,
|
|
1240
|
+
// routeData: RouteData
|
|
1241
|
+
// ) => Promise<void>;
|
|
1242
|
+
// if (!customFunction) throw new RsError('NOT_FOUND', `API path ${routeData.path} not implemented`);
|
|
1243
|
+
// await customFunction(req, res, routeData);
|
|
1244
|
+
// }
|
|
1245
|
+
async generateHashForSchema(providedSchema) {
|
|
1246
|
+
const schemaPrettyStr = await prettier3.format(JSON.stringify(providedSchema), __spreadValues({
|
|
1247
|
+
parser: "json"
|
|
1248
|
+
}, {
|
|
1249
|
+
trailingComma: "none",
|
|
1250
|
+
tabWidth: 4,
|
|
1251
|
+
useTabs: true,
|
|
1252
|
+
endOfLine: "lf",
|
|
1253
|
+
printWidth: 120,
|
|
1254
|
+
singleQuote: true
|
|
1255
|
+
}));
|
|
1256
|
+
return (0, import_crypto.createHash)("sha256").update(schemaPrettyStr).digest("hex");
|
|
1257
|
+
}
|
|
1258
|
+
async storeFileSystemSchema() {
|
|
1259
|
+
const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema), __spreadValues({
|
|
1260
|
+
parser: "json"
|
|
1261
|
+
}, {
|
|
1262
|
+
trailingComma: "none",
|
|
1263
|
+
tabWidth: 4,
|
|
1264
|
+
useTabs: true,
|
|
1265
|
+
endOfLine: "lf",
|
|
1266
|
+
printWidth: 120,
|
|
1267
|
+
singleQuote: true
|
|
1268
|
+
}));
|
|
1269
|
+
import_fs.default.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
|
|
1270
|
+
}
|
|
1271
|
+
resetPublicEndpoints() {
|
|
1272
|
+
this.publicEndpoints = {
|
|
1273
|
+
GET: [],
|
|
1274
|
+
POST: [],
|
|
1275
|
+
PUT: [],
|
|
1276
|
+
PATCH: [],
|
|
1277
|
+
DELETE: []
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
validateAuthorization(req, routeData) {
|
|
1281
|
+
const role = req.requesterDetails.role;
|
|
1282
|
+
if (routeData.roles.length === 0 || !role) return;
|
|
1283
|
+
if (!routeData.roles.includes(role))
|
|
1284
|
+
throw new RsError("UNAUTHORIZED", "Not authorized to access this endpoint");
|
|
1285
|
+
}
|
|
1286
|
+
getRouteData(method, baseUrl, path2) {
|
|
1287
|
+
const endpoint = this.schema.endpoints.find((item) => {
|
|
1288
|
+
return item.baseUrl === baseUrl;
|
|
1289
|
+
});
|
|
1290
|
+
if (!endpoint) throw new RsError("NOT_FOUND", "Route not found");
|
|
1291
|
+
const route = endpoint.routes.find((item) => {
|
|
1292
|
+
return item.method === method && item.path === path2;
|
|
1293
|
+
});
|
|
1294
|
+
if (!route) throw new RsError("NOT_FOUND", "Route not found");
|
|
1295
|
+
return route;
|
|
89
1296
|
}
|
|
90
1297
|
};
|
|
91
1298
|
__decorateClass([
|
|
92
1299
|
boundMethod
|
|
93
1300
|
], ResturaEngine.prototype, "resturaAuthentication", 1);
|
|
1301
|
+
__decorateClass([
|
|
1302
|
+
boundMethod
|
|
1303
|
+
], ResturaEngine.prototype, "previewCreateSchema", 1);
|
|
1304
|
+
__decorateClass([
|
|
1305
|
+
boundMethod
|
|
1306
|
+
], ResturaEngine.prototype, "updateSchema", 1);
|
|
1307
|
+
__decorateClass([
|
|
1308
|
+
boundMethod
|
|
1309
|
+
], ResturaEngine.prototype, "getSchema", 1);
|
|
1310
|
+
__decorateClass([
|
|
1311
|
+
boundMethod
|
|
1312
|
+
], ResturaEngine.prototype, "getSchemaAndTypes", 1);
|
|
1313
|
+
__decorateClass([
|
|
1314
|
+
boundMethod
|
|
1315
|
+
], ResturaEngine.prototype, "executeRouteLogic", 1);
|
|
1316
|
+
__decorateClass([
|
|
1317
|
+
boundMethod
|
|
1318
|
+
], ResturaEngine.prototype, "isCustomRoute", 1);
|
|
94
1319
|
var restura = new ResturaEngine();
|
|
95
1320
|
// Annotate the CommonJS export names for ESM import in node:
|
|
96
1321
|
0 && (module.exports = {
|
|
1322
|
+
logger,
|
|
97
1323
|
restura
|
|
98
1324
|
});
|
|
99
1325
|
//# sourceMappingURL=index.js.map
|