@ruiapp/rapid-core 0.8.20 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -11
- package/dist/core/request.d.ts +1 -0
- package/dist/core/server.d.ts +2 -1
- package/dist/index.js +746 -741
- package/dist/server.d.ts +2 -1
- package/package.json +1 -1
- package/rollup.config.js +16 -16
- package/src/bootstrapApplicationConfig.ts +782 -782
- package/src/core/actionHandler.ts +23 -23
- package/src/core/eventManager.ts +20 -20
- package/src/core/facility.ts +7 -7
- package/src/core/http/formDataParser.ts +89 -89
- package/src/core/http-types.ts +4 -4
- package/src/core/pluginManager.ts +184 -184
- package/src/core/providers/runtimeProvider.ts +5 -5
- package/src/core/request.ts +96 -95
- package/src/core/response.ts +79 -79
- package/src/core/routeContext.ts +127 -127
- package/src/core/routesBuilder.ts +90 -90
- package/src/core/server.ts +152 -151
- package/src/dataAccess/columnTypeMapper.ts +22 -22
- package/src/dataAccess/dataAccessTypes.ts +165 -165
- package/src/dataAccess/dataAccessor.ts +135 -135
- package/src/dataAccess/entityManager.ts +1932 -1932
- package/src/dataAccess/entityMapper.ts +111 -111
- package/src/dataAccess/entityValidator.ts +33 -26
- package/src/dataAccess/propertyMapper.ts +28 -28
- package/src/deno-std/assert/assert.ts +9 -9
- package/src/deno-std/assert/assertion_error.ts +7 -7
- package/src/deno-std/datetime/to_imf.ts +32 -32
- package/src/deno-std/encoding/base64.ts +141 -141
- package/src/deno-std/http/cookie.ts +372 -372
- package/src/facilities/cache/CacheFacilityTypes.ts +29 -29
- package/src/facilities/cache/CacheFactory.ts +31 -31
- package/src/facilities/cache/MemoryCache.ts +58 -58
- package/src/facilities/cache/MemoryCacheProvider.ts +15 -15
- package/src/facilities/log/LogFacility.ts +35 -35
- package/src/helpers/entityHelpers.ts +76 -76
- package/src/helpers/filterHelper.ts +148 -148
- package/src/helpers/inputHelper.ts +11 -11
- package/src/helpers/licenseHelper.ts +29 -29
- package/src/helpers/metaHelper.ts +111 -111
- package/src/helpers/runCollectionEntityActionHandler.ts +58 -58
- package/src/index.ts +76 -76
- package/src/plugins/auth/AuthPlugin.ts +93 -93
- package/src/plugins/auth/actionHandlers/changePassword.ts +61 -61
- package/src/plugins/auth/actionHandlers/createSession.ts +67 -67
- package/src/plugins/auth/actionHandlers/deleteSession.ts +18 -18
- package/src/plugins/auth/actionHandlers/getMyProfile.ts +35 -35
- package/src/plugins/auth/actionHandlers/index.ts +8 -8
- package/src/plugins/auth/actionHandlers/resetPassword.ts +45 -45
- package/src/plugins/auth/models/AccessToken.ts +56 -56
- package/src/plugins/auth/models/index.ts +3 -3
- package/src/plugins/auth/routes/changePassword.ts +15 -15
- package/src/plugins/auth/routes/getMyProfile.ts +15 -15
- package/src/plugins/auth/routes/index.ts +7 -7
- package/src/plugins/auth/routes/resetPassword.ts +15 -15
- package/src/plugins/auth/routes/signin.ts +15 -15
- package/src/plugins/auth/routes/signout.ts +15 -15
- package/src/plugins/auth/services/AuthService.ts +39 -39
- package/src/plugins/cronJob/CronJobPlugin.ts +104 -104
- package/src/plugins/cronJob/CronJobPluginTypes.ts +44 -44
- package/src/plugins/cronJob/actionHandlers/index.ts +4 -4
- package/src/plugins/cronJob/actionHandlers/runCronJob.ts +32 -32
- package/src/plugins/cronJob/entityWatchers/cronJobEntityWatchers.ts +24 -24
- package/src/plugins/cronJob/entityWatchers/index.ts +4 -4
- package/src/plugins/cronJob/models/CronJob.ts +129 -129
- package/src/plugins/cronJob/models/index.ts +3 -3
- package/src/plugins/cronJob/routes/index.ts +3 -3
- package/src/plugins/cronJob/routes/runCronJob.ts +15 -15
- package/src/plugins/cronJob/services/CronJobService.ts +252 -252
- package/src/plugins/dataManage/DataManagePlugin.ts +163 -163
- package/src/plugins/dataManage/actionHandlers/addEntityRelations.ts +15 -15
- package/src/plugins/dataManage/actionHandlers/countCollectionEntities.ts +17 -17
- package/src/plugins/dataManage/actionHandlers/createCollectionEntitiesBatch.ts +81 -81
- package/src/plugins/dataManage/actionHandlers/createCollectionEntity.ts +20 -20
- package/src/plugins/dataManage/actionHandlers/deleteCollectionEntities.ts +45 -45
- package/src/plugins/dataManage/actionHandlers/deleteCollectionEntityById.ts +20 -20
- package/src/plugins/dataManage/actionHandlers/findCollectionEntities.ts +27 -27
- package/src/plugins/dataManage/actionHandlers/findCollectionEntityById.ts +30 -30
- package/src/plugins/dataManage/actionHandlers/queryDatabase.ts +22 -22
- package/src/plugins/dataManage/actionHandlers/removeEntityRelations.ts +15 -15
- package/src/plugins/dataManage/actionHandlers/updateCollectionEntityById.ts +38 -38
- package/src/plugins/entityAccessControl/EntityAccessControlPlugin.ts +146 -146
- package/src/plugins/fileManage/FileManagePlugin.ts +52 -52
- package/src/plugins/fileManage/actionHandlers/downloadDocument.ts +65 -65
- package/src/plugins/fileManage/actionHandlers/downloadFile.ts +44 -44
- package/src/plugins/fileManage/actionHandlers/uploadFile.ts +33 -33
- package/src/plugins/fileManage/routes/downloadDocument.ts +15 -15
- package/src/plugins/fileManage/routes/downloadFile.ts +15 -15
- package/src/plugins/fileManage/routes/index.ts +5 -5
- package/src/plugins/fileManage/routes/uploadFile.ts +15 -15
- package/src/plugins/license/LicensePlugin.ts +79 -79
- package/src/plugins/license/LicensePluginTypes.ts +95 -95
- package/src/plugins/license/LicenseService.ts +118 -118
- package/src/plugins/license/actionHandlers/getLicense.ts +18 -18
- package/src/plugins/license/actionHandlers/index.ts +4 -4
- package/src/plugins/license/helpers/certHelper.ts +21 -21
- package/src/plugins/license/helpers/cryptoHelper.ts +47 -47
- package/src/plugins/license/models/index.ts +1 -1
- package/src/plugins/license/routes/getLicense.ts +15 -15
- package/src/plugins/license/routes/index.ts +3 -3
- package/src/plugins/mail/MailPlugin.ts +74 -74
- package/src/plugins/mail/MailPluginTypes.ts +27 -27
- package/src/plugins/mail/MailService.ts +38 -38
- package/src/plugins/mail/actionHandlers/index.ts +3 -3
- package/src/plugins/mail/models/index.ts +1 -1
- package/src/plugins/mail/routes/index.ts +1 -1
- package/src/plugins/metaManage/MetaManagePlugin.ts +198 -198
- package/src/plugins/metaManage/actionHandlers/getMetaModelDetail.ts +10 -10
- package/src/plugins/metaManage/actionHandlers/listMetaModels.ts +9 -9
- package/src/plugins/metaManage/actionHandlers/listMetaRoutes.ts +9 -9
- package/src/plugins/metaManage/services/MetaService.ts +376 -376
- package/src/plugins/notification/NotificationPlugin.ts +68 -68
- package/src/plugins/notification/NotificationPluginTypes.ts +13 -13
- package/src/plugins/notification/NotificationService.ts +25 -25
- package/src/plugins/notification/actionHandlers/index.ts +3 -3
- package/src/plugins/notification/models/Notification.ts +60 -60
- package/src/plugins/notification/models/index.ts +3 -3
- package/src/plugins/notification/routes/index.ts +1 -1
- package/src/plugins/routeManage/RouteManagePlugin.ts +62 -62
- package/src/plugins/routeManage/actionHandlers/httpProxy.ts +13 -13
- package/src/plugins/sequence/SequencePlugin.ts +146 -146
- package/src/plugins/sequence/SequencePluginTypes.ts +69 -69
- package/src/plugins/sequence/SequenceService.ts +92 -92
- package/src/plugins/sequence/actionHandlers/generateSn.ts +32 -32
- package/src/plugins/sequence/actionHandlers/index.ts +4 -4
- package/src/plugins/sequence/models/SequenceAutoIncrementRecord.ts +49 -49
- package/src/plugins/sequence/models/SequenceRule.ts +42 -42
- package/src/plugins/sequence/models/index.ts +4 -4
- package/src/plugins/sequence/routes/generateSn.ts +15 -15
- package/src/plugins/sequence/routes/index.ts +3 -3
- package/src/plugins/sequence/segment-utility.ts +11 -11
- package/src/plugins/sequence/segments/autoIncrement.ts +90 -90
- package/src/plugins/sequence/segments/dayOfMonth.ts +19 -19
- package/src/plugins/sequence/segments/index.ts +9 -9
- package/src/plugins/sequence/segments/literal.ts +16 -16
- package/src/plugins/sequence/segments/month.ts +19 -19
- package/src/plugins/sequence/segments/parameter.ts +20 -20
- package/src/plugins/sequence/segments/year.ts +19 -19
- package/src/plugins/serverOperation/ServerOperationPlugin.ts +91 -91
- package/src/plugins/serverOperation/ServerOperationPluginTypes.ts +15 -15
- package/src/plugins/serverOperation/actionHandlers/index.ts +4 -4
- package/src/plugins/serverOperation/actionHandlers/runServerOperation.ts +15 -15
- package/src/plugins/setting/SettingPlugin.ts +68 -68
- package/src/plugins/setting/SettingPluginTypes.ts +37 -37
- package/src/plugins/setting/SettingService.ts +213 -213
- package/src/plugins/setting/actionHandlers/getSystemSettingValues.ts +30 -30
- package/src/plugins/setting/actionHandlers/getUserSettingValues.ts +38 -38
- package/src/plugins/setting/actionHandlers/index.ts +6 -6
- package/src/plugins/setting/actionHandlers/setSystemSettingValues.ts +30 -30
- package/src/plugins/setting/models/SystemSettingGroupSetting.ts +57 -57
- package/src/plugins/setting/models/SystemSettingItem.ts +48 -48
- package/src/plugins/setting/models/SystemSettingItemSetting.ts +73 -73
- package/src/plugins/setting/models/UserSettingGroupSetting.ts +57 -57
- package/src/plugins/setting/models/UserSettingItem.ts +55 -55
- package/src/plugins/setting/models/UserSettingItemSetting.ts +73 -73
- package/src/plugins/setting/models/index.ts +8 -8
- package/src/plugins/setting/routes/getSystemSettingValues.ts +15 -15
- package/src/plugins/setting/routes/getUserSettingValues.ts +15 -15
- package/src/plugins/setting/routes/index.ts +5 -5
- package/src/plugins/setting/routes/setSystemSettingValues.ts +15 -15
- package/src/plugins/stateMachine/StateMachinePlugin.ts +196 -196
- package/src/plugins/stateMachine/StateMachinePluginTypes.ts +48 -48
- package/src/plugins/stateMachine/actionHandlers/index.ts +4 -4
- package/src/plugins/stateMachine/actionHandlers/sendStateMachineEvent.ts +54 -54
- package/src/plugins/stateMachine/models/StateMachine.ts +42 -42
- package/src/plugins/stateMachine/models/index.ts +3 -3
- package/src/plugins/stateMachine/routes/index.ts +3 -3
- package/src/plugins/stateMachine/routes/sendStateMachineEvent.ts +15 -15
- package/src/plugins/stateMachine/stateMachineHelper.ts +36 -36
- package/src/plugins/webhooks/WebhooksPlugin.ts +148 -148
- package/src/plugins/webhooks/pluginConfig.ts +75 -75
- package/src/polyfill.ts +5 -5
- package/src/proxy/mod.ts +38 -38
- package/src/proxy/types.ts +21 -21
- package/src/queryBuilder/index.ts +1 -1
- package/src/queryBuilder/queryBuilder.ts +755 -755
- package/src/server.ts +523 -524
- package/src/types/cron-job-types.ts +66 -66
- package/src/types.ts +832 -832
- package/src/utilities/accessControlUtility.ts +33 -33
- package/src/utilities/entityUtility.ts +18 -18
- package/src/utilities/errorUtility.ts +15 -15
- package/src/utilities/fsUtility.ts +61 -61
- package/src/utilities/httpUtility.ts +19 -19
- package/src/utilities/jwtUtility.ts +26 -26
- package/src/utilities/pathUtility.ts +14 -14
- package/src/utilities/timeUtility.ts +17 -17
- package/src/utilities/typeUtility.ts +15 -15
- package/tsconfig.json +19 -19
package/dist/index.js
CHANGED
|
@@ -1203,485 +1203,6 @@ class RouteContext {
|
|
|
1203
1203
|
}
|
|
1204
1204
|
}
|
|
1205
1205
|
|
|
1206
|
-
const parseFormDataBody = async (request, options = { all: false }) => {
|
|
1207
|
-
const contentType = request.headers.get("Content-Type");
|
|
1208
|
-
if (isFormDataContent(contentType)) {
|
|
1209
|
-
return parseFormData(request, options);
|
|
1210
|
-
}
|
|
1211
|
-
return {};
|
|
1212
|
-
};
|
|
1213
|
-
function isFormDataContent(contentType) {
|
|
1214
|
-
if (contentType === null) {
|
|
1215
|
-
return false;
|
|
1216
|
-
}
|
|
1217
|
-
return contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded");
|
|
1218
|
-
}
|
|
1219
|
-
async function parseFormData(request, options) {
|
|
1220
|
-
const formData = await request.formData();
|
|
1221
|
-
if (formData) {
|
|
1222
|
-
return convertFormDataToBodyData(formData, options);
|
|
1223
|
-
}
|
|
1224
|
-
return {};
|
|
1225
|
-
}
|
|
1226
|
-
function convertFormDataToBodyData(formData, options) {
|
|
1227
|
-
const form = {};
|
|
1228
|
-
formData.forEach((value, key) => {
|
|
1229
|
-
const shouldParseAllValues = options.all || key.endsWith("[]");
|
|
1230
|
-
if (!shouldParseAllValues) {
|
|
1231
|
-
form[key] = value;
|
|
1232
|
-
}
|
|
1233
|
-
else {
|
|
1234
|
-
handleParsingAllValues(form, key, value);
|
|
1235
|
-
}
|
|
1236
|
-
});
|
|
1237
|
-
return form;
|
|
1238
|
-
}
|
|
1239
|
-
const handleParsingAllValues = (form, key, value) => {
|
|
1240
|
-
if (form[key] && isArrayField(form[key])) {
|
|
1241
|
-
appendToExistingArray(form[key], value);
|
|
1242
|
-
}
|
|
1243
|
-
else if (form[key]) {
|
|
1244
|
-
convertToNewArray(form, key, value);
|
|
1245
|
-
}
|
|
1246
|
-
else {
|
|
1247
|
-
form[key] = value;
|
|
1248
|
-
}
|
|
1249
|
-
};
|
|
1250
|
-
function isArrayField(field) {
|
|
1251
|
-
return Array.isArray(field);
|
|
1252
|
-
}
|
|
1253
|
-
const appendToExistingArray = (arr, value) => {
|
|
1254
|
-
arr.push(value);
|
|
1255
|
-
};
|
|
1256
|
-
const convertToNewArray = (form, key, value) => {
|
|
1257
|
-
form[key] = [form[key], value];
|
|
1258
|
-
};
|
|
1259
|
-
|
|
1260
|
-
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
1261
|
-
class AssertionError extends Error {
|
|
1262
|
-
name = "AssertionError";
|
|
1263
|
-
constructor(message) {
|
|
1264
|
-
super(message);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
1269
|
-
/** Make an assertion, error will be thrown if `expr` does not have truthy value. */
|
|
1270
|
-
function assert(expr, msg = "") {
|
|
1271
|
-
if (!expr) {
|
|
1272
|
-
throw new AssertionError(msg);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
1277
|
-
// This module is browser compatible.
|
|
1278
|
-
/**
|
|
1279
|
-
* Formats the given date to IMF date time format. (Reference:
|
|
1280
|
-
* https://tools.ietf.org/html/rfc7231#section-7.1.1.1).
|
|
1281
|
-
* IMF is the time format to use when generating times in HTTP
|
|
1282
|
-
* headers. The time being formatted must be in UTC for Format to
|
|
1283
|
-
* generate the correct format.
|
|
1284
|
-
*
|
|
1285
|
-
* @example
|
|
1286
|
-
* ```ts
|
|
1287
|
-
* import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/to_imf.ts";
|
|
1288
|
-
*
|
|
1289
|
-
* toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT"
|
|
1290
|
-
* ```
|
|
1291
|
-
* @param date Date to parse
|
|
1292
|
-
* @return IMF date formatted string
|
|
1293
|
-
*/
|
|
1294
|
-
function toIMF(date) {
|
|
1295
|
-
function dtPad(v, lPad = 2) {
|
|
1296
|
-
return v.padStart(lPad, "0");
|
|
1297
|
-
}
|
|
1298
|
-
const d = dtPad(date.getUTCDate().toString());
|
|
1299
|
-
const h = dtPad(date.getUTCHours().toString());
|
|
1300
|
-
const min = dtPad(date.getUTCMinutes().toString());
|
|
1301
|
-
const s = dtPad(date.getUTCSeconds().toString());
|
|
1302
|
-
const y = date.getUTCFullYear();
|
|
1303
|
-
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
1304
|
-
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1305
|
-
return `${days[date.getUTCDay()]}, ${d} ${months[date.getUTCMonth()]} ${y} ${h}:${min}:${s} GMT`;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
1309
|
-
const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
|
|
1310
|
-
function toString(cookie) {
|
|
1311
|
-
if (!cookie.name) {
|
|
1312
|
-
return "";
|
|
1313
|
-
}
|
|
1314
|
-
const out = [];
|
|
1315
|
-
validateName(cookie.name);
|
|
1316
|
-
validateValue(cookie.name, cookie.value);
|
|
1317
|
-
out.push(`${cookie.name}=${cookie.value}`);
|
|
1318
|
-
// Fallback for invalid Set-Cookie
|
|
1319
|
-
// ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
|
|
1320
|
-
if (cookie.name.startsWith("__Secure")) {
|
|
1321
|
-
cookie.secure = true;
|
|
1322
|
-
}
|
|
1323
|
-
if (cookie.name.startsWith("__Host")) {
|
|
1324
|
-
cookie.path = "/";
|
|
1325
|
-
cookie.secure = true;
|
|
1326
|
-
delete cookie.domain;
|
|
1327
|
-
}
|
|
1328
|
-
if (cookie.secure) {
|
|
1329
|
-
out.push("Secure");
|
|
1330
|
-
}
|
|
1331
|
-
if (cookie.httpOnly) {
|
|
1332
|
-
out.push("HttpOnly");
|
|
1333
|
-
}
|
|
1334
|
-
if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
|
|
1335
|
-
assert(cookie.maxAge >= 0, "Max-Age must be an integer superior or equal to 0");
|
|
1336
|
-
out.push(`Max-Age=${cookie.maxAge}`);
|
|
1337
|
-
}
|
|
1338
|
-
if (cookie.domain) {
|
|
1339
|
-
validateDomain(cookie.domain);
|
|
1340
|
-
out.push(`Domain=${cookie.domain}`);
|
|
1341
|
-
}
|
|
1342
|
-
if (cookie.sameSite) {
|
|
1343
|
-
out.push(`SameSite=${cookie.sameSite}`);
|
|
1344
|
-
}
|
|
1345
|
-
if (cookie.path) {
|
|
1346
|
-
validatePath(cookie.path);
|
|
1347
|
-
out.push(`Path=${cookie.path}`);
|
|
1348
|
-
}
|
|
1349
|
-
if (cookie.expires) {
|
|
1350
|
-
const { expires } = cookie;
|
|
1351
|
-
const dateString = toIMF(typeof expires === "number" ? new Date(expires) : expires);
|
|
1352
|
-
out.push(`Expires=${dateString}`);
|
|
1353
|
-
}
|
|
1354
|
-
if (cookie.unparsed) {
|
|
1355
|
-
out.push(cookie.unparsed.join("; "));
|
|
1356
|
-
}
|
|
1357
|
-
return out.join("; ");
|
|
1358
|
-
}
|
|
1359
|
-
/**
|
|
1360
|
-
* Validate Cookie Name.
|
|
1361
|
-
* @param name Cookie name.
|
|
1362
|
-
*/
|
|
1363
|
-
function validateName(name) {
|
|
1364
|
-
if (name && !FIELD_CONTENT_REGEXP.test(name)) {
|
|
1365
|
-
throw new TypeError(`Invalid cookie name: "${name}".`);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
/**
|
|
1369
|
-
* Validate Path Value.
|
|
1370
|
-
* See {@link https://tools.ietf.org/html/rfc6265#section-4.1.2.4}.
|
|
1371
|
-
* @param path Path value.
|
|
1372
|
-
*/
|
|
1373
|
-
function validatePath(path) {
|
|
1374
|
-
if (path == null) {
|
|
1375
|
-
return;
|
|
1376
|
-
}
|
|
1377
|
-
for (let i = 0; i < path.length; i++) {
|
|
1378
|
-
const c = path.charAt(i);
|
|
1379
|
-
if (c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7e) || c == ";") {
|
|
1380
|
-
throw new Error(path + ": Invalid cookie path char '" + c + "'");
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
/**
|
|
1385
|
-
* Validate Cookie Value.
|
|
1386
|
-
* See {@link https://tools.ietf.org/html/rfc6265#section-4.1}.
|
|
1387
|
-
* @param value Cookie value.
|
|
1388
|
-
*/
|
|
1389
|
-
function validateValue(name, value) {
|
|
1390
|
-
if (value == null || name == null)
|
|
1391
|
-
return;
|
|
1392
|
-
for (let i = 0; i < value.length; i++) {
|
|
1393
|
-
const c = value.charAt(i);
|
|
1394
|
-
if (c < String.fromCharCode(0x21) ||
|
|
1395
|
-
c == String.fromCharCode(0x22) ||
|
|
1396
|
-
c == String.fromCharCode(0x2c) ||
|
|
1397
|
-
c == String.fromCharCode(0x3b) ||
|
|
1398
|
-
c == String.fromCharCode(0x5c) ||
|
|
1399
|
-
c == String.fromCharCode(0x7f)) {
|
|
1400
|
-
throw new Error("RFC2616 cookie '" + name + "' cannot contain character '" + c + "'");
|
|
1401
|
-
}
|
|
1402
|
-
if (c > String.fromCharCode(0x80)) {
|
|
1403
|
-
throw new Error("RFC2616 cookie '" + name + "' can only have US-ASCII chars as value" + c.charCodeAt(0).toString(16));
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Validate Cookie Domain.
|
|
1409
|
-
* See {@link https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3}.
|
|
1410
|
-
* @param domain Cookie domain.
|
|
1411
|
-
*/
|
|
1412
|
-
function validateDomain(domain) {
|
|
1413
|
-
if (domain == null) {
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
const char1 = domain.charAt(0);
|
|
1417
|
-
const charN = domain.charAt(domain.length - 1);
|
|
1418
|
-
if (char1 == "-" || charN == "." || charN == "-") {
|
|
1419
|
-
throw new Error("Invalid first/last char in cookie domain: " + domain);
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
/**
|
|
1423
|
-
* Parse cookies of a header
|
|
1424
|
-
*
|
|
1425
|
-
* @example
|
|
1426
|
-
* ```ts
|
|
1427
|
-
* import { getCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
1428
|
-
*
|
|
1429
|
-
* const headers = new Headers();
|
|
1430
|
-
* headers.set("Cookie", "full=of; tasty=chocolate");
|
|
1431
|
-
*
|
|
1432
|
-
* const cookies = getCookies(headers);
|
|
1433
|
-
* console.log(cookies); // { full: "of", tasty: "chocolate" }
|
|
1434
|
-
* ```
|
|
1435
|
-
*
|
|
1436
|
-
* @param headers The headers instance to get cookies from
|
|
1437
|
-
* @return Object with cookie names as keys
|
|
1438
|
-
*/
|
|
1439
|
-
function getCookies(headers) {
|
|
1440
|
-
const cookie = headers.get("Cookie");
|
|
1441
|
-
if (cookie != null) {
|
|
1442
|
-
const out = {};
|
|
1443
|
-
const c = cookie.split(";");
|
|
1444
|
-
for (const kv of c) {
|
|
1445
|
-
const [cookieKey, ...cookieVal] = kv.split("=");
|
|
1446
|
-
assert(cookieKey != null);
|
|
1447
|
-
const key = cookieKey.trim();
|
|
1448
|
-
out[key] = cookieVal.join("=");
|
|
1449
|
-
}
|
|
1450
|
-
return out;
|
|
1451
|
-
}
|
|
1452
|
-
return {};
|
|
1453
|
-
}
|
|
1454
|
-
/**
|
|
1455
|
-
* Set the cookie header properly in the headers
|
|
1456
|
-
*
|
|
1457
|
-
* @example
|
|
1458
|
-
* ```ts
|
|
1459
|
-
* import {
|
|
1460
|
-
* Cookie,
|
|
1461
|
-
* setCookie,
|
|
1462
|
-
* } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
1463
|
-
*
|
|
1464
|
-
* const headers = new Headers();
|
|
1465
|
-
* const cookie: Cookie = { name: "Space", value: "Cat" };
|
|
1466
|
-
* setCookie(headers, cookie);
|
|
1467
|
-
*
|
|
1468
|
-
* const cookieHeader = headers.get("set-cookie");
|
|
1469
|
-
* console.log(cookieHeader); // Space=Cat
|
|
1470
|
-
* ```
|
|
1471
|
-
*
|
|
1472
|
-
* @param headers The headers instance to set the cookie to
|
|
1473
|
-
* @param cookie Cookie to set
|
|
1474
|
-
*/
|
|
1475
|
-
function setCookie(headers, cookie) {
|
|
1476
|
-
// Parsing cookie headers to make consistent set-cookie header
|
|
1477
|
-
// ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
|
|
1478
|
-
const v = toString(cookie);
|
|
1479
|
-
if (v) {
|
|
1480
|
-
headers.append("Set-Cookie", v);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Set the cookie header with empty value in the headers to delete it
|
|
1485
|
-
*
|
|
1486
|
-
* > Note: Deleting a `Cookie` will set its expiration date before now. Forcing
|
|
1487
|
-
* > the browser to delete it.
|
|
1488
|
-
*
|
|
1489
|
-
* @example
|
|
1490
|
-
* ```ts
|
|
1491
|
-
* import { deleteCookie } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
1492
|
-
*
|
|
1493
|
-
* const headers = new Headers();
|
|
1494
|
-
* deleteCookie(headers, "deno");
|
|
1495
|
-
*
|
|
1496
|
-
* const cookieHeader = headers.get("set-cookie");
|
|
1497
|
-
* console.log(cookieHeader); // deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT
|
|
1498
|
-
* ```
|
|
1499
|
-
*
|
|
1500
|
-
* @param headers The headers instance to delete the cookie from
|
|
1501
|
-
* @param name Name of cookie
|
|
1502
|
-
* @param attributes Additional cookie attributes
|
|
1503
|
-
*/
|
|
1504
|
-
function deleteCookie(headers, name, attributes) {
|
|
1505
|
-
setCookie(headers, {
|
|
1506
|
-
name: name,
|
|
1507
|
-
value: "",
|
|
1508
|
-
expires: new Date(0),
|
|
1509
|
-
...attributes,
|
|
1510
|
-
});
|
|
1511
|
-
}
|
|
1512
|
-
function parseSetCookie(value) {
|
|
1513
|
-
const attrs = value.split(";").map((attr) => {
|
|
1514
|
-
const [key, ...values] = attr.trim().split("=");
|
|
1515
|
-
return [key, values.join("=")];
|
|
1516
|
-
});
|
|
1517
|
-
const cookie = {
|
|
1518
|
-
name: attrs[0][0],
|
|
1519
|
-
value: attrs[0][1],
|
|
1520
|
-
};
|
|
1521
|
-
for (const [key, value] of attrs.slice(1)) {
|
|
1522
|
-
switch (key.toLocaleLowerCase()) {
|
|
1523
|
-
case "expires":
|
|
1524
|
-
cookie.expires = new Date(value);
|
|
1525
|
-
break;
|
|
1526
|
-
case "max-age":
|
|
1527
|
-
cookie.maxAge = Number(value);
|
|
1528
|
-
if (cookie.maxAge < 0) {
|
|
1529
|
-
console.warn("Max-Age must be an integer superior or equal to 0. Cookie ignored.");
|
|
1530
|
-
return null;
|
|
1531
|
-
}
|
|
1532
|
-
break;
|
|
1533
|
-
case "domain":
|
|
1534
|
-
cookie.domain = value;
|
|
1535
|
-
break;
|
|
1536
|
-
case "path":
|
|
1537
|
-
cookie.path = value;
|
|
1538
|
-
break;
|
|
1539
|
-
case "secure":
|
|
1540
|
-
cookie.secure = true;
|
|
1541
|
-
break;
|
|
1542
|
-
case "httponly":
|
|
1543
|
-
cookie.httpOnly = true;
|
|
1544
|
-
break;
|
|
1545
|
-
case "samesite":
|
|
1546
|
-
cookie.sameSite = value;
|
|
1547
|
-
break;
|
|
1548
|
-
default:
|
|
1549
|
-
if (!Array.isArray(cookie.unparsed)) {
|
|
1550
|
-
cookie.unparsed = [];
|
|
1551
|
-
}
|
|
1552
|
-
cookie.unparsed.push([key, value].join("="));
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
if (cookie.name.startsWith("__Secure-")) {
|
|
1556
|
-
/** This requirement is mentioned in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie but not the RFC. */
|
|
1557
|
-
if (!cookie.secure) {
|
|
1558
|
-
console.warn("Cookies with names starting with `__Secure-` must be set with the secure flag. Cookie ignored.");
|
|
1559
|
-
return null;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
if (cookie.name.startsWith("__Host-")) {
|
|
1563
|
-
if (!cookie.secure) {
|
|
1564
|
-
console.warn("Cookies with names starting with `__Host-` must be set with the secure flag. Cookie ignored.");
|
|
1565
|
-
return null;
|
|
1566
|
-
}
|
|
1567
|
-
if (cookie.domain !== undefined) {
|
|
1568
|
-
console.warn("Cookies with names starting with `__Host-` must not have a domain specified. Cookie ignored.");
|
|
1569
|
-
return null;
|
|
1570
|
-
}
|
|
1571
|
-
if (cookie.path !== "/") {
|
|
1572
|
-
console.warn("Cookies with names starting with `__Host-` must have path be `/`. Cookie has been ignored.");
|
|
1573
|
-
return null;
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
return cookie;
|
|
1577
|
-
}
|
|
1578
|
-
/**
|
|
1579
|
-
* Parse set-cookies of a header
|
|
1580
|
-
*
|
|
1581
|
-
* @example
|
|
1582
|
-
* ```ts
|
|
1583
|
-
* import { getSetCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
1584
|
-
*
|
|
1585
|
-
* const headers = new Headers([
|
|
1586
|
-
* ["Set-Cookie", "lulu=meow; Secure; Max-Age=3600"],
|
|
1587
|
-
* ["Set-Cookie", "booya=kasha; HttpOnly; Path=/"],
|
|
1588
|
-
* ]);
|
|
1589
|
-
*
|
|
1590
|
-
* const cookies = getSetCookies(headers);
|
|
1591
|
-
* console.log(cookies); // [{ name: "lulu", value: "meow", secure: true, maxAge: 3600 }, { name: "booya", value: "kahsa", httpOnly: true, path: "/ }]
|
|
1592
|
-
* ```
|
|
1593
|
-
*
|
|
1594
|
-
* @param headers The headers instance to get set-cookies from
|
|
1595
|
-
* @return List of cookies
|
|
1596
|
-
*/
|
|
1597
|
-
function getSetCookies(headers) {
|
|
1598
|
-
// TODO(lino-levan): remove this ts-ignore when Typescript 5.2 lands in Deno
|
|
1599
|
-
// @ts-ignore Typescript's TS Dom types will be out of date until 5.2
|
|
1600
|
-
return headers
|
|
1601
|
-
.getSetCookie()
|
|
1602
|
-
/** Parse each `set-cookie` header separately */
|
|
1603
|
-
.map(parseSetCookie)
|
|
1604
|
-
/** Skip empty cookies */
|
|
1605
|
-
.filter(Boolean);
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
const GlobalRequest = global.Request;
|
|
1609
|
-
class RapidRequest {
|
|
1610
|
-
#logger;
|
|
1611
|
-
#raw;
|
|
1612
|
-
#bodyParsed;
|
|
1613
|
-
#body;
|
|
1614
|
-
#headers;
|
|
1615
|
-
#parsedCookies;
|
|
1616
|
-
method;
|
|
1617
|
-
url;
|
|
1618
|
-
constructor(server, req) {
|
|
1619
|
-
this.#logger = server.getLogger();
|
|
1620
|
-
this.#raw = req;
|
|
1621
|
-
this.method = req.method;
|
|
1622
|
-
this.url = new URL(req.url);
|
|
1623
|
-
this.#headers = req.headers;
|
|
1624
|
-
}
|
|
1625
|
-
async parseBody() {
|
|
1626
|
-
if (this.#bodyParsed) {
|
|
1627
|
-
this.#logger.warn("Request body has been parsed. 'parseBody()' method should not be called more than once.");
|
|
1628
|
-
return;
|
|
1629
|
-
}
|
|
1630
|
-
const requestMethod = this.method;
|
|
1631
|
-
if (requestMethod !== "POST" && requestMethod !== "PUT" && requestMethod !== "PATCH") {
|
|
1632
|
-
this.#body = null;
|
|
1633
|
-
this.#bodyParsed = true;
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1636
|
-
const contentLength = parseInt(this.#headers.get("Content-Length") || "0", 10);
|
|
1637
|
-
if (!contentLength) {
|
|
1638
|
-
this.#body = null;
|
|
1639
|
-
this.#bodyParsed = true;
|
|
1640
|
-
return;
|
|
1641
|
-
}
|
|
1642
|
-
const req = this.#raw;
|
|
1643
|
-
const contentType = this.#headers.get("Content-Type") || "application/json";
|
|
1644
|
-
if (contentType.includes("json")) {
|
|
1645
|
-
this.#body = {
|
|
1646
|
-
type: "json",
|
|
1647
|
-
value: await req.json(),
|
|
1648
|
-
};
|
|
1649
|
-
}
|
|
1650
|
-
else if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
|
1651
|
-
const bodyText = await req.text();
|
|
1652
|
-
this.#body = {
|
|
1653
|
-
type: "form",
|
|
1654
|
-
value: qs__default["default"].parse(bodyText),
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
else if (contentType.startsWith("multipart/form-data")) {
|
|
1658
|
-
this.#body = {
|
|
1659
|
-
type: "form-data",
|
|
1660
|
-
value: await parseFormDataBody(req),
|
|
1661
|
-
};
|
|
1662
|
-
}
|
|
1663
|
-
this.#bodyParsed = true;
|
|
1664
|
-
}
|
|
1665
|
-
get rawRequest() {
|
|
1666
|
-
return this.#raw;
|
|
1667
|
-
}
|
|
1668
|
-
get headers() {
|
|
1669
|
-
return this.#headers;
|
|
1670
|
-
}
|
|
1671
|
-
get cookies() {
|
|
1672
|
-
if (!this.#parsedCookies) {
|
|
1673
|
-
this.#parsedCookies = getCookies(this.#headers);
|
|
1674
|
-
}
|
|
1675
|
-
return this.#parsedCookies;
|
|
1676
|
-
}
|
|
1677
|
-
get body() {
|
|
1678
|
-
if (!this.#bodyParsed) {
|
|
1679
|
-
throw new Error("Request body not parsed, you should call 'parseBody()' method before getting the body.");
|
|
1680
|
-
}
|
|
1681
|
-
return this.#body;
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
1206
|
var bootstrapApplicationConfig = {
|
|
1686
1207
|
code: "default",
|
|
1687
1208
|
name: "default",
|
|
@@ -2762,11 +2283,16 @@ async function validateEntity(server, model, entity) {
|
|
|
2762
2283
|
for (const propCode in entity) {
|
|
2763
2284
|
let prop = getEntityPropertyByCode(server, model, propCode);
|
|
2764
2285
|
if (!prop) {
|
|
2765
|
-
getEntityPropertyByFieldName(server, model, propCode);
|
|
2286
|
+
prop = getEntityPropertyByFieldName(server, model, propCode);
|
|
2766
2287
|
}
|
|
2767
2288
|
if (!prop) {
|
|
2768
2289
|
continue;
|
|
2769
2290
|
}
|
|
2291
|
+
if (propCode === "id") {
|
|
2292
|
+
if (lodash.isNil(entity[propCode])) {
|
|
2293
|
+
delete entity[propCode];
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2770
2296
|
if (prop.type === "date") {
|
|
2771
2297
|
const originValue = entity[propCode];
|
|
2772
2298
|
if (originValue) {
|
|
@@ -3289,8 +2815,8 @@ async function findManyRelationLinksViaLinkTable(options) {
|
|
|
3289
2815
|
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
3290
2816
|
schema: relationProperty.linkSchema,
|
|
3291
2817
|
tableName: relationProperty.linkTableName,
|
|
3292
|
-
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])
|
|
3293
|
-
ORDER BY id
|
|
2818
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])
|
|
2819
|
+
ORDER BY id
|
|
3294
2820
|
`;
|
|
3295
2821
|
const params = [mainEntityIds];
|
|
3296
2822
|
const relationLinks = await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
@@ -3921,7 +3447,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3921
3447
|
await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
3922
3448
|
schema: property.linkSchema,
|
|
3923
3449
|
tableName: property.linkTableName,
|
|
3924
|
-
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1
|
|
3450
|
+
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1
|
|
3925
3451
|
AND ${server.queryBuilder.quoteObject(property.targetIdColumnName)} <> ALL($2::int[])`, [id, targetIdsToKeep], routeContext?.getDbTransactionClient());
|
|
3926
3452
|
}
|
|
3927
3453
|
else {
|
|
@@ -3950,8 +3476,8 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3950
3476
|
await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
|
|
3951
3477
|
schema: relationModel.schema,
|
|
3952
3478
|
tableName: relationModel.tableName,
|
|
3953
|
-
})}
|
|
3954
|
-
SET ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = null
|
|
3479
|
+
})}
|
|
3480
|
+
SET ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = null
|
|
3955
3481
|
WHERE id = ANY($1::int[])`, [targetIdsToRemove], routeContext?.getDbTransactionClient());
|
|
3956
3482
|
}
|
|
3957
3483
|
else {
|
|
@@ -4236,7 +3762,7 @@ async function deleteEntityById(server, dataAccessor, options, plugin) {
|
|
|
4236
3762
|
await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
4237
3763
|
schema: relationProperty.linkSchema,
|
|
4238
3764
|
tableName: relationProperty.linkTableName,
|
|
4239
|
-
})}
|
|
3765
|
+
})}
|
|
4240
3766
|
WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
|
|
4241
3767
|
}
|
|
4242
3768
|
else {
|
|
@@ -4246,8 +3772,8 @@ async function deleteEntityById(server, dataAccessor, options, plugin) {
|
|
|
4246
3772
|
await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
|
|
4247
3773
|
schema: relationModel.schema,
|
|
4248
3774
|
tableName: relationModel.tableName,
|
|
4249
|
-
})}
|
|
4250
|
-
SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = null
|
|
3775
|
+
})}
|
|
3776
|
+
SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = null
|
|
4251
3777
|
WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
|
|
4252
3778
|
}
|
|
4253
3779
|
}
|
|
@@ -4344,11 +3870,11 @@ class EntityManager {
|
|
|
4344
3870
|
const command = `INSERT INTO ${queryBuilder.quoteTable({
|
|
4345
3871
|
schema: relationProperty.linkSchema,
|
|
4346
3872
|
tableName: relationProperty.linkTableName,
|
|
4347
|
-
})} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
|
|
4348
|
-
SELECT $1, $2 WHERE NOT EXISTS (
|
|
4349
|
-
SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}
|
|
4350
|
-
FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
|
|
4351
|
-
WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2
|
|
3873
|
+
})} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
|
|
3874
|
+
SELECT $1, $2 WHERE NOT EXISTS (
|
|
3875
|
+
SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}
|
|
3876
|
+
FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
|
|
3877
|
+
WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2
|
|
4352
3878
|
)`;
|
|
4353
3879
|
const params = [id, relation.id];
|
|
4354
3880
|
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
@@ -4388,7 +3914,7 @@ class EntityManager {
|
|
|
4388
3914
|
const { queryBuilder } = server;
|
|
4389
3915
|
if (relationProperty.linkTableName) {
|
|
4390
3916
|
for (const relation of relations) {
|
|
4391
|
-
const command = `DELETE FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
|
|
3917
|
+
const command = `DELETE FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
|
|
4392
3918
|
WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2;`;
|
|
4393
3919
|
const params = [id, relation.id];
|
|
4394
3920
|
await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
|
|
@@ -4535,295 +4061,774 @@ class RapidServer {
|
|
|
4535
4061
|
}
|
|
4536
4062
|
}
|
|
4537
4063
|
}
|
|
4538
|
-
if (routes) {
|
|
4539
|
-
for (const route of routes) {
|
|
4540
|
-
const originalRoute = lodash.find(this.#applicationConfig.routes, (item) => item.code == route.code);
|
|
4541
|
-
if (originalRoute) {
|
|
4542
|
-
originalRoute.name = route.name;
|
|
4543
|
-
originalRoute.actions = route.actions;
|
|
4544
|
-
}
|
|
4545
|
-
else {
|
|
4546
|
-
this.#applicationConfig.routes.push(route);
|
|
4547
|
-
}
|
|
4064
|
+
if (routes) {
|
|
4065
|
+
for (const route of routes) {
|
|
4066
|
+
const originalRoute = lodash.find(this.#applicationConfig.routes, (item) => item.code == route.code);
|
|
4067
|
+
if (originalRoute) {
|
|
4068
|
+
originalRoute.name = route.name;
|
|
4069
|
+
originalRoute.actions = route.actions;
|
|
4070
|
+
}
|
|
4071
|
+
else {
|
|
4072
|
+
this.#applicationConfig.routes.push(route);
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
appendModelProperties(modelSingularCode, properties) {
|
|
4078
|
+
const originalModel = lodash.find(this.#applicationConfig.models, (item) => item.singularCode == modelSingularCode);
|
|
4079
|
+
if (!originalModel) {
|
|
4080
|
+
throw new Error(`Cannot append model properties. Model '${modelSingularCode}' was not found.`);
|
|
4081
|
+
}
|
|
4082
|
+
const originalProperties = originalModel.properties;
|
|
4083
|
+
for (const property of properties) {
|
|
4084
|
+
const originalProperty = lodash.find(originalProperties, (item) => item.code == property.code);
|
|
4085
|
+
if (originalProperty) {
|
|
4086
|
+
originalProperty.name = property.name;
|
|
4087
|
+
}
|
|
4088
|
+
else {
|
|
4089
|
+
originalProperties.push(property);
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
registerActionHandler(plugin, options) {
|
|
4094
|
+
const handler = lodash.bind(options.handler, null, plugin);
|
|
4095
|
+
this.#actionHandlersMapByCode.set(options.code, handler);
|
|
4096
|
+
}
|
|
4097
|
+
getActionHandlerByCode(code) {
|
|
4098
|
+
return this.#actionHandlersMapByCode.get(code);
|
|
4099
|
+
}
|
|
4100
|
+
registerMiddleware(middleware) {
|
|
4101
|
+
this.#middlewares.push(middleware);
|
|
4102
|
+
}
|
|
4103
|
+
getDataAccessor(options) {
|
|
4104
|
+
const { namespace, singularCode } = options;
|
|
4105
|
+
let dataAccessor = this.#cachedDataAccessors.get(singularCode);
|
|
4106
|
+
if (dataAccessor) {
|
|
4107
|
+
return dataAccessor;
|
|
4108
|
+
}
|
|
4109
|
+
const model = this.getModel(options);
|
|
4110
|
+
if (!model) {
|
|
4111
|
+
throw new Error(`Data model ${namespace}.${singularCode} not found.`);
|
|
4112
|
+
}
|
|
4113
|
+
dataAccessor = new DataAccessor(this, this.#databaseAccessor, {
|
|
4114
|
+
model,
|
|
4115
|
+
queryBuilder: this.queryBuilder,
|
|
4116
|
+
});
|
|
4117
|
+
this.#cachedDataAccessors.set(singularCode, dataAccessor);
|
|
4118
|
+
return dataAccessor;
|
|
4119
|
+
}
|
|
4120
|
+
getModel(options) {
|
|
4121
|
+
if (options.namespace) {
|
|
4122
|
+
return this.#applicationConfig?.models.find((e) => e.namespace === options.namespace && e.singularCode === options.singularCode);
|
|
4123
|
+
}
|
|
4124
|
+
return this.#applicationConfig?.models.find((e) => e.singularCode === options.singularCode);
|
|
4125
|
+
}
|
|
4126
|
+
getEntityManager(singularCode) {
|
|
4127
|
+
let entityManager = this.#cachedEntityManager.get(singularCode);
|
|
4128
|
+
if (entityManager) {
|
|
4129
|
+
return entityManager;
|
|
4130
|
+
}
|
|
4131
|
+
const dataAccessor = this.getDataAccessor({ singularCode });
|
|
4132
|
+
entityManager = new EntityManager(this, dataAccessor);
|
|
4133
|
+
this.#cachedEntityManager.set(singularCode, entityManager);
|
|
4134
|
+
return entityManager;
|
|
4135
|
+
}
|
|
4136
|
+
registerEventHandler(eventName, listener) {
|
|
4137
|
+
this.#eventManager.on(eventName, listener);
|
|
4138
|
+
}
|
|
4139
|
+
registerEntityWatcher(entityWatcher) {
|
|
4140
|
+
this.#entityWatchers.push(entityWatcher);
|
|
4141
|
+
}
|
|
4142
|
+
async emitEvent(event) {
|
|
4143
|
+
const { eventName, payload, sender, routeContext: routerContext } = event;
|
|
4144
|
+
this.#logger.debug(`Emitting '${eventName}' event.`, { eventName });
|
|
4145
|
+
this.#logger.verbose(`Event payload: `, { payload });
|
|
4146
|
+
await this.#eventManager.emit(eventName, sender, payload, routerContext);
|
|
4147
|
+
// TODO: should move this logic into metaManager
|
|
4148
|
+
// if (
|
|
4149
|
+
// (eventName === "entity.create" || eventName === "entity.update" ||
|
|
4150
|
+
// eventName === "entity.delete") &&
|
|
4151
|
+
// payload.namespace === "meta"
|
|
4152
|
+
// ) {
|
|
4153
|
+
// await this.configureApplication();
|
|
4154
|
+
// }
|
|
4155
|
+
}
|
|
4156
|
+
registerService(name, service) {
|
|
4157
|
+
this.#services.set(name, service);
|
|
4158
|
+
}
|
|
4159
|
+
getService(name) {
|
|
4160
|
+
return this.#services.get(name);
|
|
4161
|
+
}
|
|
4162
|
+
registerCronJob(job) {
|
|
4163
|
+
const jobDuplicate = lodash.find(this.#cronJobs, (item) => item.code === job.code);
|
|
4164
|
+
if (jobDuplicate) {
|
|
4165
|
+
this.#logger.warn(`Duplicated cron job with code "${job.code}"`);
|
|
4166
|
+
}
|
|
4167
|
+
this.#cronJobs.push(job);
|
|
4168
|
+
}
|
|
4169
|
+
listCronJobs() {
|
|
4170
|
+
return [...this.#cronJobs, ...this.#appCronJobs];
|
|
4171
|
+
}
|
|
4172
|
+
async start() {
|
|
4173
|
+
this.#logger.info("Starting rapid server...");
|
|
4174
|
+
const pluginManager = this.#pluginManager;
|
|
4175
|
+
await pluginManager.loadPlugins(this.#plugins);
|
|
4176
|
+
await pluginManager.initPlugins();
|
|
4177
|
+
await pluginManager.registerMiddlewares();
|
|
4178
|
+
await pluginManager.registerActionHandlers();
|
|
4179
|
+
await pluginManager.registerEventHandlers();
|
|
4180
|
+
await pluginManager.registerMessageHandlers();
|
|
4181
|
+
await pluginManager.registerTaskProcessors();
|
|
4182
|
+
this.#entityWatchers = this.#entityWatchers.concat(this.#appEntityWatchers);
|
|
4183
|
+
for (const entityWatcher of this.#entityWatchers) {
|
|
4184
|
+
if (entityWatcher.eventName === "entity.beforeCreate") {
|
|
4185
|
+
this.#entityBeforeCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4186
|
+
}
|
|
4187
|
+
else if (entityWatcher.eventName === "entity.create") {
|
|
4188
|
+
this.#entityCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4189
|
+
}
|
|
4190
|
+
else if (entityWatcher.eventName === "entity.beforeUpdate") {
|
|
4191
|
+
this.#entityBeforeUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4192
|
+
}
|
|
4193
|
+
else if (entityWatcher.eventName === "entity.update") {
|
|
4194
|
+
this.#entityUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4195
|
+
}
|
|
4196
|
+
else if (entityWatcher.eventName === "entity.beforeDelete") {
|
|
4197
|
+
this.#entityBeforeDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4198
|
+
}
|
|
4199
|
+
else if (entityWatcher.eventName === "entity.delete") {
|
|
4200
|
+
this.#entityDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4201
|
+
}
|
|
4202
|
+
else if (entityWatcher.eventName === "entity.addRelations") {
|
|
4203
|
+
this.#entityAddRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4204
|
+
}
|
|
4205
|
+
else if (entityWatcher.eventName === "entity.removeRelations") {
|
|
4206
|
+
this.#entityRemoveRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4207
|
+
}
|
|
4208
|
+
else if (entityWatcher.eventName === "entity.beforeResponse") {
|
|
4209
|
+
this.#entityBeforeResponseEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
await this.configureApplication();
|
|
4213
|
+
if (!this.#disableCronJobs) {
|
|
4214
|
+
await pluginManager.registerCronJobs();
|
|
4215
|
+
}
|
|
4216
|
+
this.#logger.info(`Rapid server ready.`);
|
|
4217
|
+
await pluginManager.onApplicationReady(this.#applicationConfig);
|
|
4218
|
+
}
|
|
4219
|
+
async configureApplication() {
|
|
4220
|
+
this.#applicationConfig = lodash.cloneDeep(this.#bootstrapApplicationConfig);
|
|
4221
|
+
const pluginManager = this.#pluginManager;
|
|
4222
|
+
await pluginManager.onLoadingApplication(this.#applicationConfig);
|
|
4223
|
+
await pluginManager.configureModels(this.#applicationConfig);
|
|
4224
|
+
await pluginManager.configureModelProperties(this.#applicationConfig);
|
|
4225
|
+
await pluginManager.configureServices(this.#applicationConfig);
|
|
4226
|
+
await pluginManager.configureRoutes(this.#applicationConfig);
|
|
4227
|
+
// TODO: check application configuration.
|
|
4228
|
+
await pluginManager.onApplicationLoaded(this.#applicationConfig);
|
|
4229
|
+
this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
|
|
4230
|
+
}
|
|
4231
|
+
registerFacilityFactory(factory) {
|
|
4232
|
+
this.#facilityFactories.set(factory.name, factory);
|
|
4233
|
+
}
|
|
4234
|
+
async getFacility(name, options, nullIfUnknownFacility) {
|
|
4235
|
+
const factory = this.#facilityFactories.get(name);
|
|
4236
|
+
if (!factory) {
|
|
4237
|
+
if (nullIfUnknownFacility) {
|
|
4238
|
+
return null;
|
|
4239
|
+
}
|
|
4240
|
+
else {
|
|
4241
|
+
throw new Error(`Failed to get facility. Unknown facility name: ${name}`);
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
return await factory.createFacility(this, options);
|
|
4245
|
+
}
|
|
4246
|
+
async queryDatabaseObject(sql, params, client, dropErrorLog) {
|
|
4247
|
+
try {
|
|
4248
|
+
return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
|
|
4249
|
+
}
|
|
4250
|
+
catch (err) {
|
|
4251
|
+
if (!dropErrorLog) {
|
|
4252
|
+
this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
|
|
4253
|
+
}
|
|
4254
|
+
throw err;
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
async tryQueryDatabaseObject(sql, params, client, dropErrorLog) {
|
|
4258
|
+
try {
|
|
4259
|
+
return await this.queryDatabaseObject(sql, params, client);
|
|
4260
|
+
}
|
|
4261
|
+
catch (err) {
|
|
4262
|
+
if (!dropErrorLog) {
|
|
4263
|
+
this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
|
|
4548
4264
|
}
|
|
4549
4265
|
}
|
|
4266
|
+
return [];
|
|
4550
4267
|
}
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4268
|
+
get middlewares() {
|
|
4269
|
+
return this.#middlewares;
|
|
4270
|
+
}
|
|
4271
|
+
async handleRequest(rapidRequest, next) {
|
|
4272
|
+
await rapidRequest.parseBody();
|
|
4273
|
+
const routeContext = new RouteContext(this, rapidRequest);
|
|
4274
|
+
const { response } = routeContext;
|
|
4275
|
+
try {
|
|
4276
|
+
await this.#pluginManager.onPrepareRouteContext(routeContext);
|
|
4277
|
+
await this.#buildedRoutes(routeContext, next);
|
|
4555
4278
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4279
|
+
catch (ex) {
|
|
4280
|
+
let error;
|
|
4281
|
+
if (lodash.isString(ex)) {
|
|
4282
|
+
error = {
|
|
4283
|
+
message: ex,
|
|
4284
|
+
};
|
|
4561
4285
|
}
|
|
4562
4286
|
else {
|
|
4563
|
-
|
|
4287
|
+
error = { name: ex.name, message: ex.message, stack: ex.stack };
|
|
4564
4288
|
}
|
|
4289
|
+
this.#logger.error("handle request error.", { error });
|
|
4290
|
+
response.json({ error }, 500);
|
|
4291
|
+
}
|
|
4292
|
+
if (!response.status && !response.body) {
|
|
4293
|
+
response.json({
|
|
4294
|
+
error: {
|
|
4295
|
+
message: "No route handler was found to handle this request.",
|
|
4296
|
+
},
|
|
4297
|
+
}, 404);
|
|
4565
4298
|
}
|
|
4299
|
+
return response.getResponse();
|
|
4566
4300
|
}
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
this.#actionHandlersMapByCode.set(options.code, handler);
|
|
4301
|
+
async beforeRunRouteActions(handlerContext) {
|
|
4302
|
+
await this.#pluginManager.beforeRunRouteActions(handlerContext);
|
|
4570
4303
|
}
|
|
4571
|
-
|
|
4572
|
-
|
|
4304
|
+
async beforeCreateEntity(model, options) {
|
|
4305
|
+
await this.#pluginManager.beforeCreateEntity(model, options);
|
|
4573
4306
|
}
|
|
4574
|
-
|
|
4575
|
-
this.#
|
|
4307
|
+
async beforeUpdateEntity(model, options, currentEntity) {
|
|
4308
|
+
await this.#pluginManager.beforeUpdateEntity(model, options, currentEntity);
|
|
4576
4309
|
}
|
|
4577
|
-
|
|
4578
|
-
const {
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
return dataAccessor;
|
|
4310
|
+
async #handleEntityEvent(eventName, sender, payload, routerContext) {
|
|
4311
|
+
const { modelSingularCode, baseModelSingularCode } = payload;
|
|
4312
|
+
if (!routerContext) {
|
|
4313
|
+
routerContext = new RouteContext(this);
|
|
4582
4314
|
}
|
|
4583
|
-
const
|
|
4584
|
-
|
|
4585
|
-
|
|
4315
|
+
const entityWatchHandlerContext = {
|
|
4316
|
+
server: this,
|
|
4317
|
+
payload,
|
|
4318
|
+
routerContext,
|
|
4319
|
+
};
|
|
4320
|
+
let emitter;
|
|
4321
|
+
if (eventName === "entity.beforeCreate") {
|
|
4322
|
+
emitter = this.#entityBeforeCreateEventEmitters;
|
|
4586
4323
|
}
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4324
|
+
else if (eventName === "entity.create") {
|
|
4325
|
+
emitter = this.#entityCreateEventEmitters;
|
|
4326
|
+
}
|
|
4327
|
+
else if (eventName === "entity.beforeUpdate") {
|
|
4328
|
+
emitter = this.#entityBeforeUpdateEventEmitters;
|
|
4329
|
+
}
|
|
4330
|
+
else if (eventName === "entity.update") {
|
|
4331
|
+
emitter = this.#entityUpdateEventEmitters;
|
|
4332
|
+
}
|
|
4333
|
+
else if (eventName === "entity.beforeDelete") {
|
|
4334
|
+
emitter = this.#entityBeforeDeleteEventEmitters;
|
|
4335
|
+
}
|
|
4336
|
+
else if (eventName === "entity.delete") {
|
|
4337
|
+
emitter = this.#entityDeleteEventEmitters;
|
|
4338
|
+
}
|
|
4339
|
+
else if (eventName === "entity.addRelations") {
|
|
4340
|
+
emitter = this.#entityAddRelationsEventEmitters;
|
|
4341
|
+
}
|
|
4342
|
+
else if (eventName === "entity.removeRelations") {
|
|
4343
|
+
emitter = this.#entityRemoveRelationsEventEmitters;
|
|
4344
|
+
}
|
|
4345
|
+
else if (eventName === "entity.beforeResponse") {
|
|
4346
|
+
emitter = this.#entityBeforeResponseEventEmitters;
|
|
4347
|
+
}
|
|
4348
|
+
await emitter.emit(modelSingularCode, entityWatchHandlerContext);
|
|
4349
|
+
if (baseModelSingularCode) {
|
|
4350
|
+
await emitter.emit(baseModelSingularCode, entityWatchHandlerContext);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
|
|
4355
|
+
const parseFormDataBody = async (request, options = { all: false }) => {
|
|
4356
|
+
const contentType = request.headers.get("Content-Type");
|
|
4357
|
+
if (isFormDataContent(contentType)) {
|
|
4358
|
+
return parseFormData(request, options);
|
|
4359
|
+
}
|
|
4360
|
+
return {};
|
|
4361
|
+
};
|
|
4362
|
+
function isFormDataContent(contentType) {
|
|
4363
|
+
if (contentType === null) {
|
|
4364
|
+
return false;
|
|
4365
|
+
}
|
|
4366
|
+
return contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded");
|
|
4367
|
+
}
|
|
4368
|
+
async function parseFormData(request, options) {
|
|
4369
|
+
const formData = await request.formData();
|
|
4370
|
+
if (formData) {
|
|
4371
|
+
return convertFormDataToBodyData(formData, options);
|
|
4372
|
+
}
|
|
4373
|
+
return {};
|
|
4374
|
+
}
|
|
4375
|
+
function convertFormDataToBodyData(formData, options) {
|
|
4376
|
+
const form = {};
|
|
4377
|
+
formData.forEach((value, key) => {
|
|
4378
|
+
const shouldParseAllValues = options.all || key.endsWith("[]");
|
|
4379
|
+
if (!shouldParseAllValues) {
|
|
4380
|
+
form[key] = value;
|
|
4381
|
+
}
|
|
4382
|
+
else {
|
|
4383
|
+
handleParsingAllValues(form, key, value);
|
|
4384
|
+
}
|
|
4385
|
+
});
|
|
4386
|
+
return form;
|
|
4387
|
+
}
|
|
4388
|
+
const handleParsingAllValues = (form, key, value) => {
|
|
4389
|
+
if (form[key] && isArrayField(form[key])) {
|
|
4390
|
+
appendToExistingArray(form[key], value);
|
|
4391
|
+
}
|
|
4392
|
+
else if (form[key]) {
|
|
4393
|
+
convertToNewArray(form, key, value);
|
|
4394
|
+
}
|
|
4395
|
+
else {
|
|
4396
|
+
form[key] = value;
|
|
4397
|
+
}
|
|
4398
|
+
};
|
|
4399
|
+
function isArrayField(field) {
|
|
4400
|
+
return Array.isArray(field);
|
|
4401
|
+
}
|
|
4402
|
+
const appendToExistingArray = (arr, value) => {
|
|
4403
|
+
arr.push(value);
|
|
4404
|
+
};
|
|
4405
|
+
const convertToNewArray = (form, key, value) => {
|
|
4406
|
+
form[key] = [form[key], value];
|
|
4407
|
+
};
|
|
4408
|
+
|
|
4409
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4410
|
+
class AssertionError extends Error {
|
|
4411
|
+
name = "AssertionError";
|
|
4412
|
+
constructor(message) {
|
|
4413
|
+
super(message);
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4418
|
+
/** Make an assertion, error will be thrown if `expr` does not have truthy value. */
|
|
4419
|
+
function assert(expr, msg = "") {
|
|
4420
|
+
if (!expr) {
|
|
4421
|
+
throw new AssertionError(msg);
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4426
|
+
// This module is browser compatible.
|
|
4427
|
+
/**
|
|
4428
|
+
* Formats the given date to IMF date time format. (Reference:
|
|
4429
|
+
* https://tools.ietf.org/html/rfc7231#section-7.1.1.1).
|
|
4430
|
+
* IMF is the time format to use when generating times in HTTP
|
|
4431
|
+
* headers. The time being formatted must be in UTC for Format to
|
|
4432
|
+
* generate the correct format.
|
|
4433
|
+
*
|
|
4434
|
+
* @example
|
|
4435
|
+
* ```ts
|
|
4436
|
+
* import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/to_imf.ts";
|
|
4437
|
+
*
|
|
4438
|
+
* toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT"
|
|
4439
|
+
* ```
|
|
4440
|
+
* @param date Date to parse
|
|
4441
|
+
* @return IMF date formatted string
|
|
4442
|
+
*/
|
|
4443
|
+
function toIMF(date) {
|
|
4444
|
+
function dtPad(v, lPad = 2) {
|
|
4445
|
+
return v.padStart(lPad, "0");
|
|
4446
|
+
}
|
|
4447
|
+
const d = dtPad(date.getUTCDate().toString());
|
|
4448
|
+
const h = dtPad(date.getUTCHours().toString());
|
|
4449
|
+
const min = dtPad(date.getUTCMinutes().toString());
|
|
4450
|
+
const s = dtPad(date.getUTCSeconds().toString());
|
|
4451
|
+
const y = date.getUTCFullYear();
|
|
4452
|
+
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
4453
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
4454
|
+
return `${days[date.getUTCDay()]}, ${d} ${months[date.getUTCMonth()]} ${y} ${h}:${min}:${s} GMT`;
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4457
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4458
|
+
const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
|
|
4459
|
+
function toString(cookie) {
|
|
4460
|
+
if (!cookie.name) {
|
|
4461
|
+
return "";
|
|
4462
|
+
}
|
|
4463
|
+
const out = [];
|
|
4464
|
+
validateName(cookie.name);
|
|
4465
|
+
validateValue(cookie.name, cookie.value);
|
|
4466
|
+
out.push(`${cookie.name}=${cookie.value}`);
|
|
4467
|
+
// Fallback for invalid Set-Cookie
|
|
4468
|
+
// ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
|
|
4469
|
+
if (cookie.name.startsWith("__Secure")) {
|
|
4470
|
+
cookie.secure = true;
|
|
4471
|
+
}
|
|
4472
|
+
if (cookie.name.startsWith("__Host")) {
|
|
4473
|
+
cookie.path = "/";
|
|
4474
|
+
cookie.secure = true;
|
|
4475
|
+
delete cookie.domain;
|
|
4476
|
+
}
|
|
4477
|
+
if (cookie.secure) {
|
|
4478
|
+
out.push("Secure");
|
|
4479
|
+
}
|
|
4480
|
+
if (cookie.httpOnly) {
|
|
4481
|
+
out.push("HttpOnly");
|
|
4482
|
+
}
|
|
4483
|
+
if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
|
|
4484
|
+
assert(cookie.maxAge >= 0, "Max-Age must be an integer superior or equal to 0");
|
|
4485
|
+
out.push(`Max-Age=${cookie.maxAge}`);
|
|
4593
4486
|
}
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
}
|
|
4598
|
-
return this.#applicationConfig?.models.find((e) => e.singularCode === options.singularCode);
|
|
4487
|
+
if (cookie.domain) {
|
|
4488
|
+
validateDomain(cookie.domain);
|
|
4489
|
+
out.push(`Domain=${cookie.domain}`);
|
|
4599
4490
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
if (entityManager) {
|
|
4603
|
-
return entityManager;
|
|
4604
|
-
}
|
|
4605
|
-
const dataAccessor = this.getDataAccessor({ singularCode });
|
|
4606
|
-
entityManager = new EntityManager(this, dataAccessor);
|
|
4607
|
-
this.#cachedEntityManager.set(singularCode, entityManager);
|
|
4608
|
-
return entityManager;
|
|
4491
|
+
if (cookie.sameSite) {
|
|
4492
|
+
out.push(`SameSite=${cookie.sameSite}`);
|
|
4609
4493
|
}
|
|
4610
|
-
|
|
4611
|
-
|
|
4494
|
+
if (cookie.path) {
|
|
4495
|
+
validatePath(cookie.path);
|
|
4496
|
+
out.push(`Path=${cookie.path}`);
|
|
4612
4497
|
}
|
|
4613
|
-
|
|
4614
|
-
|
|
4498
|
+
if (cookie.expires) {
|
|
4499
|
+
const { expires } = cookie;
|
|
4500
|
+
const dateString = toIMF(typeof expires === "number" ? new Date(expires) : expires);
|
|
4501
|
+
out.push(`Expires=${dateString}`);
|
|
4615
4502
|
}
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
this.#logger.debug(`Emitting '${eventName}' event.`, { eventName });
|
|
4619
|
-
this.#logger.verbose(`Event payload: `, { payload });
|
|
4620
|
-
await this.#eventManager.emit(eventName, sender, payload, routerContext);
|
|
4621
|
-
// TODO: should move this logic into metaManager
|
|
4622
|
-
// if (
|
|
4623
|
-
// (eventName === "entity.create" || eventName === "entity.update" ||
|
|
4624
|
-
// eventName === "entity.delete") &&
|
|
4625
|
-
// payload.namespace === "meta"
|
|
4626
|
-
// ) {
|
|
4627
|
-
// await this.configureApplication();
|
|
4628
|
-
// }
|
|
4503
|
+
if (cookie.unparsed) {
|
|
4504
|
+
out.push(cookie.unparsed.join("; "));
|
|
4629
4505
|
}
|
|
4630
|
-
|
|
4631
|
-
|
|
4506
|
+
return out.join("; ");
|
|
4507
|
+
}
|
|
4508
|
+
/**
|
|
4509
|
+
* Validate Cookie Name.
|
|
4510
|
+
* @param name Cookie name.
|
|
4511
|
+
*/
|
|
4512
|
+
function validateName(name) {
|
|
4513
|
+
if (name && !FIELD_CONTENT_REGEXP.test(name)) {
|
|
4514
|
+
throw new TypeError(`Invalid cookie name: "${name}".`);
|
|
4632
4515
|
}
|
|
4633
|
-
|
|
4634
|
-
|
|
4516
|
+
}
|
|
4517
|
+
/**
|
|
4518
|
+
* Validate Path Value.
|
|
4519
|
+
* See {@link https://tools.ietf.org/html/rfc6265#section-4.1.2.4}.
|
|
4520
|
+
* @param path Path value.
|
|
4521
|
+
*/
|
|
4522
|
+
function validatePath(path) {
|
|
4523
|
+
if (path == null) {
|
|
4524
|
+
return;
|
|
4635
4525
|
}
|
|
4636
|
-
|
|
4637
|
-
const
|
|
4638
|
-
if (
|
|
4639
|
-
|
|
4526
|
+
for (let i = 0; i < path.length; i++) {
|
|
4527
|
+
const c = path.charAt(i);
|
|
4528
|
+
if (c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7e) || c == ";") {
|
|
4529
|
+
throw new Error(path + ": Invalid cookie path char '" + c + "'");
|
|
4640
4530
|
}
|
|
4641
|
-
this.#cronJobs.push(job);
|
|
4642
|
-
}
|
|
4643
|
-
listCronJobs() {
|
|
4644
|
-
return [...this.#cronJobs, ...this.#appCronJobs];
|
|
4645
4531
|
}
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
else if (entityWatcher.eventName === "entity.beforeUpdate") {
|
|
4665
|
-
this.#entityBeforeUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4666
|
-
}
|
|
4667
|
-
else if (entityWatcher.eventName === "entity.update") {
|
|
4668
|
-
this.#entityUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4669
|
-
}
|
|
4670
|
-
else if (entityWatcher.eventName === "entity.beforeDelete") {
|
|
4671
|
-
this.#entityBeforeDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4672
|
-
}
|
|
4673
|
-
else if (entityWatcher.eventName === "entity.delete") {
|
|
4674
|
-
this.#entityDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4675
|
-
}
|
|
4676
|
-
else if (entityWatcher.eventName === "entity.addRelations") {
|
|
4677
|
-
this.#entityAddRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4678
|
-
}
|
|
4679
|
-
else if (entityWatcher.eventName === "entity.removeRelations") {
|
|
4680
|
-
this.#entityRemoveRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4681
|
-
}
|
|
4682
|
-
else if (entityWatcher.eventName === "entity.beforeResponse") {
|
|
4683
|
-
this.#entityBeforeResponseEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
|
|
4684
|
-
}
|
|
4532
|
+
}
|
|
4533
|
+
/**
|
|
4534
|
+
* Validate Cookie Value.
|
|
4535
|
+
* See {@link https://tools.ietf.org/html/rfc6265#section-4.1}.
|
|
4536
|
+
* @param value Cookie value.
|
|
4537
|
+
*/
|
|
4538
|
+
function validateValue(name, value) {
|
|
4539
|
+
if (value == null || name == null)
|
|
4540
|
+
return;
|
|
4541
|
+
for (let i = 0; i < value.length; i++) {
|
|
4542
|
+
const c = value.charAt(i);
|
|
4543
|
+
if (c < String.fromCharCode(0x21) ||
|
|
4544
|
+
c == String.fromCharCode(0x22) ||
|
|
4545
|
+
c == String.fromCharCode(0x2c) ||
|
|
4546
|
+
c == String.fromCharCode(0x3b) ||
|
|
4547
|
+
c == String.fromCharCode(0x5c) ||
|
|
4548
|
+
c == String.fromCharCode(0x7f)) {
|
|
4549
|
+
throw new Error("RFC2616 cookie '" + name + "' cannot contain character '" + c + "'");
|
|
4685
4550
|
}
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
await pluginManager.registerCronJobs();
|
|
4551
|
+
if (c > String.fromCharCode(0x80)) {
|
|
4552
|
+
throw new Error("RFC2616 cookie '" + name + "' can only have US-ASCII chars as value" + c.charCodeAt(0).toString(16));
|
|
4689
4553
|
}
|
|
4690
|
-
this.#logger.info(`Rapid server ready.`);
|
|
4691
|
-
await pluginManager.onApplicationReady(this.#applicationConfig);
|
|
4692
4554
|
}
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
await pluginManager.onApplicationLoaded(this.#applicationConfig);
|
|
4703
|
-
this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
|
|
4555
|
+
}
|
|
4556
|
+
/**
|
|
4557
|
+
* Validate Cookie Domain.
|
|
4558
|
+
* See {@link https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3}.
|
|
4559
|
+
* @param domain Cookie domain.
|
|
4560
|
+
*/
|
|
4561
|
+
function validateDomain(domain) {
|
|
4562
|
+
if (domain == null) {
|
|
4563
|
+
return;
|
|
4704
4564
|
}
|
|
4705
|
-
|
|
4706
|
-
|
|
4565
|
+
const char1 = domain.charAt(0);
|
|
4566
|
+
const charN = domain.charAt(domain.length - 1);
|
|
4567
|
+
if (char1 == "-" || charN == "." || charN == "-") {
|
|
4568
|
+
throw new Error("Invalid first/last char in cookie domain: " + domain);
|
|
4707
4569
|
}
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4570
|
+
}
|
|
4571
|
+
/**
|
|
4572
|
+
* Parse cookies of a header
|
|
4573
|
+
*
|
|
4574
|
+
* @example
|
|
4575
|
+
* ```ts
|
|
4576
|
+
* import { getCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
4577
|
+
*
|
|
4578
|
+
* const headers = new Headers();
|
|
4579
|
+
* headers.set("Cookie", "full=of; tasty=chocolate");
|
|
4580
|
+
*
|
|
4581
|
+
* const cookies = getCookies(headers);
|
|
4582
|
+
* console.log(cookies); // { full: "of", tasty: "chocolate" }
|
|
4583
|
+
* ```
|
|
4584
|
+
*
|
|
4585
|
+
* @param headers The headers instance to get cookies from
|
|
4586
|
+
* @return Object with cookie names as keys
|
|
4587
|
+
*/
|
|
4588
|
+
function getCookies(headers) {
|
|
4589
|
+
const cookie = headers.get("Cookie");
|
|
4590
|
+
if (cookie != null) {
|
|
4591
|
+
const out = {};
|
|
4592
|
+
const c = cookie.split(";");
|
|
4593
|
+
for (const kv of c) {
|
|
4594
|
+
const [cookieKey, ...cookieVal] = kv.split("=");
|
|
4595
|
+
assert(cookieKey != null);
|
|
4596
|
+
const key = cookieKey.trim();
|
|
4597
|
+
out[key] = cookieVal.join("=");
|
|
4717
4598
|
}
|
|
4718
|
-
return
|
|
4599
|
+
return out;
|
|
4719
4600
|
}
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4601
|
+
return {};
|
|
4602
|
+
}
|
|
4603
|
+
/**
|
|
4604
|
+
* Set the cookie header properly in the headers
|
|
4605
|
+
*
|
|
4606
|
+
* @example
|
|
4607
|
+
* ```ts
|
|
4608
|
+
* import {
|
|
4609
|
+
* Cookie,
|
|
4610
|
+
* setCookie,
|
|
4611
|
+
* } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
4612
|
+
*
|
|
4613
|
+
* const headers = new Headers();
|
|
4614
|
+
* const cookie: Cookie = { name: "Space", value: "Cat" };
|
|
4615
|
+
* setCookie(headers, cookie);
|
|
4616
|
+
*
|
|
4617
|
+
* const cookieHeader = headers.get("set-cookie");
|
|
4618
|
+
* console.log(cookieHeader); // Space=Cat
|
|
4619
|
+
* ```
|
|
4620
|
+
*
|
|
4621
|
+
* @param headers The headers instance to set the cookie to
|
|
4622
|
+
* @param cookie Cookie to set
|
|
4623
|
+
*/
|
|
4624
|
+
function setCookie(headers, cookie) {
|
|
4625
|
+
// Parsing cookie headers to make consistent set-cookie header
|
|
4626
|
+
// ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
|
|
4627
|
+
const v = toString(cookie);
|
|
4628
|
+
if (v) {
|
|
4629
|
+
headers.append("Set-Cookie", v);
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
/**
|
|
4633
|
+
* Set the cookie header with empty value in the headers to delete it
|
|
4634
|
+
*
|
|
4635
|
+
* > Note: Deleting a `Cookie` will set its expiration date before now. Forcing
|
|
4636
|
+
* > the browser to delete it.
|
|
4637
|
+
*
|
|
4638
|
+
* @example
|
|
4639
|
+
* ```ts
|
|
4640
|
+
* import { deleteCookie } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
4641
|
+
*
|
|
4642
|
+
* const headers = new Headers();
|
|
4643
|
+
* deleteCookie(headers, "deno");
|
|
4644
|
+
*
|
|
4645
|
+
* const cookieHeader = headers.get("set-cookie");
|
|
4646
|
+
* console.log(cookieHeader); // deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT
|
|
4647
|
+
* ```
|
|
4648
|
+
*
|
|
4649
|
+
* @param headers The headers instance to delete the cookie from
|
|
4650
|
+
* @param name Name of cookie
|
|
4651
|
+
* @param attributes Additional cookie attributes
|
|
4652
|
+
*/
|
|
4653
|
+
function deleteCookie(headers, name, attributes) {
|
|
4654
|
+
setCookie(headers, {
|
|
4655
|
+
name: name,
|
|
4656
|
+
value: "",
|
|
4657
|
+
expires: new Date(0),
|
|
4658
|
+
...attributes,
|
|
4659
|
+
});
|
|
4660
|
+
}
|
|
4661
|
+
function parseSetCookie(value) {
|
|
4662
|
+
const attrs = value.split(";").map((attr) => {
|
|
4663
|
+
const [key, ...values] = attr.trim().split("=");
|
|
4664
|
+
return [key, values.join("=")];
|
|
4665
|
+
});
|
|
4666
|
+
const cookie = {
|
|
4667
|
+
name: attrs[0][0],
|
|
4668
|
+
value: attrs[0][1],
|
|
4669
|
+
};
|
|
4670
|
+
for (const [key, value] of attrs.slice(1)) {
|
|
4671
|
+
switch (key.toLocaleLowerCase()) {
|
|
4672
|
+
case "expires":
|
|
4673
|
+
cookie.expires = new Date(value);
|
|
4674
|
+
break;
|
|
4675
|
+
case "max-age":
|
|
4676
|
+
cookie.maxAge = Number(value);
|
|
4677
|
+
if (cookie.maxAge < 0) {
|
|
4678
|
+
console.warn("Max-Age must be an integer superior or equal to 0. Cookie ignored.");
|
|
4679
|
+
return null;
|
|
4680
|
+
}
|
|
4681
|
+
break;
|
|
4682
|
+
case "domain":
|
|
4683
|
+
cookie.domain = value;
|
|
4684
|
+
break;
|
|
4685
|
+
case "path":
|
|
4686
|
+
cookie.path = value;
|
|
4687
|
+
break;
|
|
4688
|
+
case "secure":
|
|
4689
|
+
cookie.secure = true;
|
|
4690
|
+
break;
|
|
4691
|
+
case "httponly":
|
|
4692
|
+
cookie.httpOnly = true;
|
|
4693
|
+
break;
|
|
4694
|
+
case "samesite":
|
|
4695
|
+
cookie.sameSite = value;
|
|
4696
|
+
break;
|
|
4697
|
+
default:
|
|
4698
|
+
if (!Array.isArray(cookie.unparsed)) {
|
|
4699
|
+
cookie.unparsed = [];
|
|
4700
|
+
}
|
|
4701
|
+
cookie.unparsed.push([key, value].join("="));
|
|
4729
4702
|
}
|
|
4730
4703
|
}
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
if (!dropErrorLog) {
|
|
4737
|
-
this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
|
|
4738
|
-
}
|
|
4704
|
+
if (cookie.name.startsWith("__Secure-")) {
|
|
4705
|
+
/** This requirement is mentioned in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie but not the RFC. */
|
|
4706
|
+
if (!cookie.secure) {
|
|
4707
|
+
console.warn("Cookies with names starting with `__Secure-` must be set with the secure flag. Cookie ignored.");
|
|
4708
|
+
return null;
|
|
4739
4709
|
}
|
|
4740
|
-
return [];
|
|
4741
|
-
}
|
|
4742
|
-
get middlewares() {
|
|
4743
|
-
return this.#middlewares;
|
|
4744
4710
|
}
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
const { response } = routeContext;
|
|
4750
|
-
try {
|
|
4751
|
-
await this.#pluginManager.onPrepareRouteContext(routeContext);
|
|
4752
|
-
await this.#buildedRoutes(routeContext, next);
|
|
4711
|
+
if (cookie.name.startsWith("__Host-")) {
|
|
4712
|
+
if (!cookie.secure) {
|
|
4713
|
+
console.warn("Cookies with names starting with `__Host-` must be set with the secure flag. Cookie ignored.");
|
|
4714
|
+
return null;
|
|
4753
4715
|
}
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
error = {
|
|
4758
|
-
message: ex,
|
|
4759
|
-
};
|
|
4760
|
-
}
|
|
4761
|
-
else {
|
|
4762
|
-
error = { name: ex.name, message: ex.message, stack: ex.stack };
|
|
4763
|
-
}
|
|
4764
|
-
this.#logger.error("handle request error.", { error });
|
|
4765
|
-
response.json({ error }, 500);
|
|
4716
|
+
if (cookie.domain !== undefined) {
|
|
4717
|
+
console.warn("Cookies with names starting with `__Host-` must not have a domain specified. Cookie ignored.");
|
|
4718
|
+
return null;
|
|
4766
4719
|
}
|
|
4767
|
-
if (
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
message: "No route handler was found to handle this request.",
|
|
4771
|
-
},
|
|
4772
|
-
}, 404);
|
|
4720
|
+
if (cookie.path !== "/") {
|
|
4721
|
+
console.warn("Cookies with names starting with `__Host-` must have path be `/`. Cookie has been ignored.");
|
|
4722
|
+
return null;
|
|
4773
4723
|
}
|
|
4774
|
-
return response.getResponse();
|
|
4775
|
-
}
|
|
4776
|
-
async beforeRunRouteActions(handlerContext) {
|
|
4777
|
-
await this.#pluginManager.beforeRunRouteActions(handlerContext);
|
|
4778
4724
|
}
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4725
|
+
return cookie;
|
|
4726
|
+
}
|
|
4727
|
+
/**
|
|
4728
|
+
* Parse set-cookies of a header
|
|
4729
|
+
*
|
|
4730
|
+
* @example
|
|
4731
|
+
* ```ts
|
|
4732
|
+
* import { getSetCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
4733
|
+
*
|
|
4734
|
+
* const headers = new Headers([
|
|
4735
|
+
* ["Set-Cookie", "lulu=meow; Secure; Max-Age=3600"],
|
|
4736
|
+
* ["Set-Cookie", "booya=kasha; HttpOnly; Path=/"],
|
|
4737
|
+
* ]);
|
|
4738
|
+
*
|
|
4739
|
+
* const cookies = getSetCookies(headers);
|
|
4740
|
+
* console.log(cookies); // [{ name: "lulu", value: "meow", secure: true, maxAge: 3600 }, { name: "booya", value: "kahsa", httpOnly: true, path: "/ }]
|
|
4741
|
+
* ```
|
|
4742
|
+
*
|
|
4743
|
+
* @param headers The headers instance to get set-cookies from
|
|
4744
|
+
* @return List of cookies
|
|
4745
|
+
*/
|
|
4746
|
+
function getSetCookies(headers) {
|
|
4747
|
+
// TODO(lino-levan): remove this ts-ignore when Typescript 5.2 lands in Deno
|
|
4748
|
+
// @ts-ignore Typescript's TS Dom types will be out of date until 5.2
|
|
4749
|
+
return headers
|
|
4750
|
+
.getSetCookie()
|
|
4751
|
+
/** Parse each `set-cookie` header separately */
|
|
4752
|
+
.map(parseSetCookie)
|
|
4753
|
+
/** Skip empty cookies */
|
|
4754
|
+
.filter(Boolean);
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4757
|
+
const GlobalRequest = global.Request;
|
|
4758
|
+
class RapidRequest {
|
|
4759
|
+
#logger;
|
|
4760
|
+
#raw;
|
|
4761
|
+
#bodyParsed;
|
|
4762
|
+
#body;
|
|
4763
|
+
#headers;
|
|
4764
|
+
#parsedCookies;
|
|
4765
|
+
method;
|
|
4766
|
+
url;
|
|
4767
|
+
ip;
|
|
4768
|
+
constructor(server, req) {
|
|
4769
|
+
this.#logger = server.getLogger();
|
|
4770
|
+
this.#raw = req;
|
|
4771
|
+
this.method = req.method;
|
|
4772
|
+
this.url = new URL(req.url);
|
|
4773
|
+
this.#headers = req.headers;
|
|
4784
4774
|
}
|
|
4785
|
-
async
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
}
|
|
4790
|
-
const entityWatchHandlerContext = {
|
|
4791
|
-
server: this,
|
|
4792
|
-
payload,
|
|
4793
|
-
routerContext,
|
|
4794
|
-
};
|
|
4795
|
-
let emitter;
|
|
4796
|
-
if (eventName === "entity.beforeCreate") {
|
|
4797
|
-
emitter = this.#entityBeforeCreateEventEmitters;
|
|
4798
|
-
}
|
|
4799
|
-
else if (eventName === "entity.create") {
|
|
4800
|
-
emitter = this.#entityCreateEventEmitters;
|
|
4801
|
-
}
|
|
4802
|
-
else if (eventName === "entity.beforeUpdate") {
|
|
4803
|
-
emitter = this.#entityBeforeUpdateEventEmitters;
|
|
4775
|
+
async parseBody() {
|
|
4776
|
+
if (this.#bodyParsed) {
|
|
4777
|
+
this.#logger.warn("Request body has been parsed. 'parseBody()' method should not be called more than once.");
|
|
4778
|
+
return;
|
|
4804
4779
|
}
|
|
4805
|
-
|
|
4806
|
-
|
|
4780
|
+
const requestMethod = this.method;
|
|
4781
|
+
if (requestMethod !== "POST" && requestMethod !== "PUT" && requestMethod !== "PATCH") {
|
|
4782
|
+
this.#body = null;
|
|
4783
|
+
this.#bodyParsed = true;
|
|
4784
|
+
return;
|
|
4807
4785
|
}
|
|
4808
|
-
|
|
4809
|
-
|
|
4786
|
+
const contentLength = parseInt(this.#headers.get("Content-Length") || "0", 10);
|
|
4787
|
+
if (!contentLength) {
|
|
4788
|
+
this.#body = null;
|
|
4789
|
+
this.#bodyParsed = true;
|
|
4790
|
+
return;
|
|
4810
4791
|
}
|
|
4811
|
-
|
|
4812
|
-
|
|
4792
|
+
const req = this.#raw;
|
|
4793
|
+
const contentType = this.#headers.get("Content-Type") || "application/json";
|
|
4794
|
+
if (contentType.includes("json")) {
|
|
4795
|
+
this.#body = {
|
|
4796
|
+
type: "json",
|
|
4797
|
+
value: await req.json(),
|
|
4798
|
+
};
|
|
4813
4799
|
}
|
|
4814
|
-
else if (
|
|
4815
|
-
|
|
4800
|
+
else if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
|
4801
|
+
const bodyText = await req.text();
|
|
4802
|
+
this.#body = {
|
|
4803
|
+
type: "form",
|
|
4804
|
+
value: qs__default["default"].parse(bodyText),
|
|
4805
|
+
};
|
|
4816
4806
|
}
|
|
4817
|
-
else if (
|
|
4818
|
-
|
|
4807
|
+
else if (contentType.startsWith("multipart/form-data")) {
|
|
4808
|
+
this.#body = {
|
|
4809
|
+
type: "form-data",
|
|
4810
|
+
value: await parseFormDataBody(req),
|
|
4811
|
+
};
|
|
4819
4812
|
}
|
|
4820
|
-
|
|
4821
|
-
|
|
4813
|
+
this.#bodyParsed = true;
|
|
4814
|
+
}
|
|
4815
|
+
get rawRequest() {
|
|
4816
|
+
return this.#raw;
|
|
4817
|
+
}
|
|
4818
|
+
get headers() {
|
|
4819
|
+
return this.#headers;
|
|
4820
|
+
}
|
|
4821
|
+
get cookies() {
|
|
4822
|
+
if (!this.#parsedCookies) {
|
|
4823
|
+
this.#parsedCookies = getCookies(this.#headers);
|
|
4822
4824
|
}
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4825
|
+
return this.#parsedCookies;
|
|
4826
|
+
}
|
|
4827
|
+
get body() {
|
|
4828
|
+
if (!this.#bodyParsed) {
|
|
4829
|
+
throw new Error("Request body not parsed, you should call 'parseBody()' method before getting the body.");
|
|
4826
4830
|
}
|
|
4831
|
+
return this.#body;
|
|
4827
4832
|
}
|
|
4828
4833
|
}
|
|
4829
4834
|
|
|
@@ -5367,9 +5372,9 @@ class MetaService {
|
|
|
5367
5372
|
await server.tryQueryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
|
|
5368
5373
|
}
|
|
5369
5374
|
}
|
|
5370
|
-
const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
|
|
5371
|
-
FROM information_schema.columns c
|
|
5372
|
-
INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
|
|
5375
|
+
const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
|
|
5376
|
+
FROM information_schema.columns c
|
|
5377
|
+
INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
|
|
5373
5378
|
LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
|
|
5374
5379
|
const columnsInDb = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
|
|
5375
5380
|
for (const model of applicationConfig.models) {
|
|
@@ -9476,9 +9481,9 @@ class EntityAccessControlPlugin {
|
|
|
9476
9481
|
if (!userId) {
|
|
9477
9482
|
return;
|
|
9478
9483
|
}
|
|
9479
|
-
const actions = await server.queryDatabaseObject(`select distinct a.* from sys_actions a
|
|
9480
|
-
inner join oc_role_sys_action_links ra on a.id = ra.action_id
|
|
9481
|
-
inner join oc_role_user_links ru on ru.role_id = ra.role_id
|
|
9484
|
+
const actions = await server.queryDatabaseObject(`select distinct a.* from sys_actions a
|
|
9485
|
+
inner join oc_role_sys_action_links ra on a.id = ra.action_id
|
|
9486
|
+
inner join oc_role_user_links ru on ru.role_id = ra.role_id
|
|
9482
9487
|
where ru.user_id = $1;`, [userId]);
|
|
9483
9488
|
routeContext.state.allowedActions = actions.map((item) => item.code);
|
|
9484
9489
|
}
|