@ruiapp/rapid-core 0.1.60 → 0.1.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +95 -25
- package/dist/plugins/serverOperation/ServerOperationPluginTypes.d.ts +2 -0
- package/package.json +1 -1
- package/src/dataAccess/entityManager.ts +69 -19
- package/src/plugins/entityAccessControl/EntityAccessControlPlugin.ts +56 -16
- package/src/plugins/serverOperation/ServerOperationPlugin.ts +8 -1
- package/src/plugins/serverOperation/ServerOperationPluginTypes.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -2295,7 +2295,10 @@ async function convertEntityFiltersToRowFilters(server, model, baseModel, filter
|
|
|
2295
2295
|
],
|
|
2296
2296
|
});
|
|
2297
2297
|
const targetEntityIds = lodash.map(targetEntities, (entity) => entity["id"]);
|
|
2298
|
-
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
2298
|
+
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
2299
|
+
schema: relationProperty.linkSchema,
|
|
2300
|
+
tableName: relationProperty.linkTableName,
|
|
2301
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} = ANY($1::int[])`;
|
|
2299
2302
|
const params = [targetEntityIds];
|
|
2300
2303
|
const links = await server.queryDatabaseObject(command, params);
|
|
2301
2304
|
const selfEntityIds = links.map((link) => link[relationProperty.selfIdColumnName]);
|
|
@@ -2352,7 +2355,10 @@ async function convertEntityFiltersToRowFilters(server, model, baseModel, filter
|
|
|
2352
2355
|
return replacedFilters;
|
|
2353
2356
|
}
|
|
2354
2357
|
async function findManyRelationLinksViaLinkTable(server, targetModel, relationProperty, entityIds) {
|
|
2355
|
-
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
2358
|
+
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
2359
|
+
schema: relationProperty.linkSchema,
|
|
2360
|
+
tableName: relationProperty.linkTableName,
|
|
2361
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])`;
|
|
2356
2362
|
const params = [entityIds];
|
|
2357
2363
|
const links = await server.queryDatabaseObject(command, params);
|
|
2358
2364
|
const targetEntityIds = links.map((link) => link[relationProperty.targetIdColumnName]);
|
|
@@ -2417,7 +2423,7 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
2417
2423
|
throw newEntityOperationError("Create base entity directly is not allowed.");
|
|
2418
2424
|
}
|
|
2419
2425
|
const { entity, routeContext } = options;
|
|
2420
|
-
const userId = options.routeContext
|
|
2426
|
+
const userId = options.routeContext?.state?.userId;
|
|
2421
2427
|
if (userId) {
|
|
2422
2428
|
const createdByProperty = getEntityPropertyByCode(server, model, "createdBy");
|
|
2423
2429
|
if (createdByProperty) {
|
|
@@ -2536,7 +2542,10 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
2536
2542
|
entity: targetEntity,
|
|
2537
2543
|
});
|
|
2538
2544
|
if (property.linkTableName) {
|
|
2539
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2545
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2546
|
+
schema: property.linkSchema,
|
|
2547
|
+
tableName: property.linkTableName,
|
|
2548
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
2540
2549
|
const params = [newEntity.id, newTargetEntity.id];
|
|
2541
2550
|
await server.queryDatabaseObject(command, params);
|
|
2542
2551
|
}
|
|
@@ -2549,7 +2558,10 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
2549
2558
|
throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`);
|
|
2550
2559
|
}
|
|
2551
2560
|
if (property.linkTableName) {
|
|
2552
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2561
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2562
|
+
schema: property.linkSchema,
|
|
2563
|
+
tableName: property.linkTableName,
|
|
2564
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
2553
2565
|
const params = [newEntity.id, relatedEntityId];
|
|
2554
2566
|
await server.queryDatabaseObject(command, params);
|
|
2555
2567
|
}
|
|
@@ -2568,7 +2580,10 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
2568
2580
|
throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`);
|
|
2569
2581
|
}
|
|
2570
2582
|
if (property.linkTableName) {
|
|
2571
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2583
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2584
|
+
schema: property.linkSchema,
|
|
2585
|
+
tableName: property.linkTableName,
|
|
2586
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
2572
2587
|
const params = [newEntity.id, relatedEntityId];
|
|
2573
2588
|
await server.queryDatabaseObject(command, params);
|
|
2574
2589
|
}
|
|
@@ -2612,7 +2627,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
2612
2627
|
return entity;
|
|
2613
2628
|
}
|
|
2614
2629
|
entityToSave = changes || {};
|
|
2615
|
-
const userId = options.routeContext
|
|
2630
|
+
const userId = options.routeContext?.state?.userId;
|
|
2616
2631
|
if (userId) {
|
|
2617
2632
|
const updatedByProperty = getEntityPropertyByCode(server, model, "updatedBy");
|
|
2618
2633
|
if (updatedByProperty) {
|
|
@@ -2724,7 +2739,10 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
2724
2739
|
}
|
|
2725
2740
|
if (property.linkTableName) {
|
|
2726
2741
|
// TODO: should support removing relation
|
|
2727
|
-
await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
2742
|
+
await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
2743
|
+
schema: property.linkSchema,
|
|
2744
|
+
tableName: property.linkTableName,
|
|
2745
|
+
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id]);
|
|
2728
2746
|
}
|
|
2729
2747
|
for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
|
|
2730
2748
|
let relatedEntityId;
|
|
@@ -2740,7 +2758,10 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
2740
2758
|
entity: targetEntity,
|
|
2741
2759
|
});
|
|
2742
2760
|
if (property.linkTableName) {
|
|
2743
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2761
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2762
|
+
schema: property.linkSchema,
|
|
2763
|
+
tableName: property.linkTableName,
|
|
2764
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
2744
2765
|
const params = [id, newTargetEntity.id];
|
|
2745
2766
|
await server.queryDatabaseObject(command, params);
|
|
2746
2767
|
}
|
|
@@ -2753,7 +2774,10 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
2753
2774
|
throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`);
|
|
2754
2775
|
}
|
|
2755
2776
|
if (property.linkTableName) {
|
|
2756
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2777
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2778
|
+
schema: property.linkSchema,
|
|
2779
|
+
tableName: property.linkTableName,
|
|
2780
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
2757
2781
|
const params = [id, relatedEntityId];
|
|
2758
2782
|
await server.queryDatabaseObject(command, params);
|
|
2759
2783
|
}
|
|
@@ -2772,7 +2796,10 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
2772
2796
|
throw new Error(`Entity with id '${relatedEntityId}' in field '${property.code}' is not exists.`);
|
|
2773
2797
|
}
|
|
2774
2798
|
if (property.linkTableName) {
|
|
2775
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2799
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
2800
|
+
schema: property.linkSchema,
|
|
2801
|
+
tableName: property.linkTableName,
|
|
2802
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
2776
2803
|
const params = [id, relatedEntityId];
|
|
2777
2804
|
await server.queryDatabaseObject(command, params);
|
|
2778
2805
|
}
|
|
@@ -2916,7 +2943,10 @@ class EntityManager {
|
|
|
2916
2943
|
const { queryBuilder } = server;
|
|
2917
2944
|
if (relationProperty.linkTableName) {
|
|
2918
2945
|
for (const relation of relations) {
|
|
2919
|
-
const command = `INSERT INTO ${queryBuilder.quoteTable({
|
|
2946
|
+
const command = `INSERT INTO ${queryBuilder.quoteTable({
|
|
2947
|
+
schema: relationProperty.linkSchema,
|
|
2948
|
+
tableName: relationProperty.linkTableName,
|
|
2949
|
+
})} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
|
|
2920
2950
|
SELECT $1, $2 WHERE NOT EXISTS (
|
|
2921
2951
|
SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}
|
|
2922
2952
|
FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
|
|
@@ -5553,6 +5583,7 @@ class ServerOperationPlugin {
|
|
|
5553
5583
|
code: "runServerOperation",
|
|
5554
5584
|
config: {
|
|
5555
5585
|
operation: operation.handler,
|
|
5586
|
+
permissionCheck: operation.permissionCheck,
|
|
5556
5587
|
},
|
|
5557
5588
|
},
|
|
5558
5589
|
],
|
|
@@ -6003,14 +6034,6 @@ class EntityAccessControlPlugin {
|
|
|
6003
6034
|
async configureRoutes(server, applicationConfig) {
|
|
6004
6035
|
const logger = server.getLogger();
|
|
6005
6036
|
logger.info("Configuring entity access checking policies...");
|
|
6006
|
-
const model = lodash.find(applicationConfig.models, (item) => item.singularCode === "model");
|
|
6007
|
-
if (!model) {
|
|
6008
|
-
return;
|
|
6009
|
-
}
|
|
6010
|
-
const { permissionPolicies } = model;
|
|
6011
|
-
if (!permissionPolicies) {
|
|
6012
|
-
return;
|
|
6013
|
-
}
|
|
6014
6037
|
const routes = applicationConfig.routes;
|
|
6015
6038
|
for (const route of routes) {
|
|
6016
6039
|
const { actions } = route;
|
|
@@ -6018,9 +6041,56 @@ class EntityAccessControlPlugin {
|
|
|
6018
6041
|
continue;
|
|
6019
6042
|
}
|
|
6020
6043
|
for (const action of route.actions) {
|
|
6021
|
-
if (action.code === "findCollectionEntityById") {
|
|
6044
|
+
if (action.code === "findCollectionEntityById" || action.code === "findCollectionEntities" || action.code === "countCollectionEntities") {
|
|
6045
|
+
const model = lodash.find(applicationConfig.models, (item) => item.singularCode === action.config.singularCode);
|
|
6046
|
+
if (!model) {
|
|
6047
|
+
continue;
|
|
6048
|
+
}
|
|
6049
|
+
const { permissionPolicies } = model;
|
|
6050
|
+
if (!permissionPolicies) {
|
|
6051
|
+
continue;
|
|
6052
|
+
}
|
|
6022
6053
|
if (permissionPolicies.find) {
|
|
6023
|
-
lodash.set(action, "config.
|
|
6054
|
+
lodash.set(action, "config.permissionCheck", permissionPolicies.find);
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
else if (action.code === "createCollectionEntity" || action.code === "createCollectionEntitiesBatch") {
|
|
6058
|
+
const model = lodash.find(applicationConfig.models, (item) => item.singularCode === action.config.singularCode);
|
|
6059
|
+
if (!model) {
|
|
6060
|
+
continue;
|
|
6061
|
+
}
|
|
6062
|
+
const { permissionPolicies } = model;
|
|
6063
|
+
if (!permissionPolicies) {
|
|
6064
|
+
continue;
|
|
6065
|
+
}
|
|
6066
|
+
if (permissionPolicies.create) {
|
|
6067
|
+
lodash.set(action, "config.permissionCheck", permissionPolicies.create);
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
else if (action.code === "updateCollectionEntityById" || action.code === "addEntityRelations" || action.code === "removeEntityRelations") {
|
|
6071
|
+
const model = lodash.find(applicationConfig.models, (item) => item.singularCode === action.config.singularCode);
|
|
6072
|
+
if (!model) {
|
|
6073
|
+
continue;
|
|
6074
|
+
}
|
|
6075
|
+
const { permissionPolicies } = model;
|
|
6076
|
+
if (!permissionPolicies) {
|
|
6077
|
+
continue;
|
|
6078
|
+
}
|
|
6079
|
+
if (permissionPolicies.update) {
|
|
6080
|
+
lodash.set(action, "config.permissionCheck", permissionPolicies.update);
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
else if (action.code === "deleteCollectionEntityById") {
|
|
6084
|
+
const model = lodash.find(applicationConfig.models, (item) => item.singularCode === action.config.singularCode);
|
|
6085
|
+
if (!model) {
|
|
6086
|
+
continue;
|
|
6087
|
+
}
|
|
6088
|
+
const { permissionPolicies } = model;
|
|
6089
|
+
if (!permissionPolicies) {
|
|
6090
|
+
continue;
|
|
6091
|
+
}
|
|
6092
|
+
if (permissionPolicies.delete) {
|
|
6093
|
+
lodash.set(action, "config.permissionCheck", permissionPolicies.delete);
|
|
6024
6094
|
}
|
|
6025
6095
|
}
|
|
6026
6096
|
}
|
|
@@ -6042,9 +6112,9 @@ class EntityAccessControlPlugin {
|
|
|
6042
6112
|
const { routerContext } = handlerContext;
|
|
6043
6113
|
const { routeConfig } = routerContext;
|
|
6044
6114
|
for (const actionConfig of routeConfig.actions) {
|
|
6045
|
-
const
|
|
6046
|
-
if (
|
|
6047
|
-
if (!isAccessAllowed(
|
|
6115
|
+
const permissionCheck = actionConfig.config?.permissionCheck;
|
|
6116
|
+
if (permissionCheck) {
|
|
6117
|
+
if (!isAccessAllowed(permissionCheck, routerContext.state.allowedActions || [])) {
|
|
6048
6118
|
throw new Error(`Your action of '${actionConfig.code}' is not permitted.`);
|
|
6049
6119
|
}
|
|
6050
6120
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ActionHandlerContext } from "../../core/actionHandler";
|
|
2
2
|
import { RpdHttpMethod } from "../../types";
|
|
3
|
+
import { PermissionCheckPolicy } from "../../utilities/accessControlUtility";
|
|
3
4
|
export interface ServerOperation {
|
|
4
5
|
code: string;
|
|
5
6
|
description?: string;
|
|
6
7
|
method: RpdHttpMethod;
|
|
8
|
+
permissionCheck?: PermissionCheckPolicy;
|
|
7
9
|
handler: (ctx: ActionHandlerContext) => Promise<void>;
|
|
8
10
|
}
|
|
9
11
|
export interface ServerOperationPluginInitOptions {
|
package/package.json
CHANGED
|
@@ -114,7 +114,9 @@ async function findEntities(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
114
114
|
|
|
115
115
|
// if `keepNonPropertyFields` is true and `properties` are not specified, then select relation columns automatically.
|
|
116
116
|
if (options.keepNonPropertyFields && (!options.properties || !options.properties.length)) {
|
|
117
|
-
const oneRelationPropertiesWithNoLinkTable = getEntityPropertiesIncludingBase(server, model).filter(
|
|
117
|
+
const oneRelationPropertiesWithNoLinkTable = getEntityPropertiesIncludingBase(server, model).filter(
|
|
118
|
+
(property) => property.relation === "one" && !property.linkTableName,
|
|
119
|
+
);
|
|
118
120
|
oneRelationPropertiesWithNoLinkTable.forEach((property) => {
|
|
119
121
|
if (property.targetIdColumnName) {
|
|
120
122
|
columnsToSelect.push({
|
|
@@ -232,7 +234,12 @@ async function findById(server: IRpdServer, dataAccessor: IRpdDataAccessor, opti
|
|
|
232
234
|
});
|
|
233
235
|
}
|
|
234
236
|
|
|
235
|
-
async function convertEntityFiltersToRowFilters(
|
|
237
|
+
async function convertEntityFiltersToRowFilters(
|
|
238
|
+
server: IRpdServer,
|
|
239
|
+
model: RpdDataModel,
|
|
240
|
+
baseModel: RpdDataModel,
|
|
241
|
+
filters: EntityFilterOptions[] | undefined,
|
|
242
|
+
): Promise<RowFilterOptions[]> {
|
|
236
243
|
if (!filters || !filters.length) {
|
|
237
244
|
return [];
|
|
238
245
|
}
|
|
@@ -251,7 +258,9 @@ async function convertEntityFiltersToRowFilters(server: IRpdServer, model: RpdDa
|
|
|
251
258
|
throw new Error(`Invalid filters. Property '${filter.field}' was not found in model '${model.namespace}.${model.singularCode}'`);
|
|
252
259
|
}
|
|
253
260
|
if (!isRelationProperty(relationProperty)) {
|
|
254
|
-
throw new Error(
|
|
261
|
+
throw new Error(
|
|
262
|
+
`Invalid filters. Filter with 'existence' operator on property '${filter.field}' is not allowed. You can only use it on an relation property.`,
|
|
263
|
+
);
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
const relatedEntityFilters = filter.filters;
|
|
@@ -382,7 +391,10 @@ async function convertEntityFiltersToRowFilters(server: IRpdServer, model: RpdDa
|
|
|
382
391
|
});
|
|
383
392
|
const targetEntityIds = map(targetEntities, (entity: any) => entity["id"]);
|
|
384
393
|
|
|
385
|
-
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
394
|
+
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
395
|
+
schema: relationProperty.linkSchema,
|
|
396
|
+
tableName: relationProperty.linkTableName!,
|
|
397
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName!)} = ANY($1::int[])`;
|
|
386
398
|
const params = [targetEntityIds];
|
|
387
399
|
const links = await server.queryDatabaseObject(command, params);
|
|
388
400
|
const selfEntityIds = links.map((link) => link[relationProperty.selfIdColumnName!]);
|
|
@@ -440,7 +452,10 @@ async function convertEntityFiltersToRowFilters(server: IRpdServer, model: RpdDa
|
|
|
440
452
|
}
|
|
441
453
|
|
|
442
454
|
async function findManyRelationLinksViaLinkTable(server: IRpdServer, targetModel: RpdDataModel, relationProperty: RpdDataModelProperty, entityIds: any[]) {
|
|
443
|
-
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
455
|
+
const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
|
|
456
|
+
schema: relationProperty.linkSchema,
|
|
457
|
+
tableName: relationProperty.linkTableName!,
|
|
458
|
+
})} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = ANY($1::int[])`;
|
|
444
459
|
const params = [entityIds];
|
|
445
460
|
const links = await server.queryDatabaseObject(command, params);
|
|
446
461
|
const targetEntityIds = links.map((link) => link[relationProperty.targetIdColumnName!]);
|
|
@@ -514,7 +529,7 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
514
529
|
|
|
515
530
|
const { entity, routeContext } = options;
|
|
516
531
|
|
|
517
|
-
const userId = options.routeContext
|
|
532
|
+
const userId = options.routeContext?.state?.userId;
|
|
518
533
|
if (userId) {
|
|
519
534
|
const createdByProperty = getEntityPropertyByCode(server, model, "createdBy");
|
|
520
535
|
if (createdByProperty) {
|
|
@@ -583,7 +598,9 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
583
598
|
routeContext,
|
|
584
599
|
});
|
|
585
600
|
if (!targetEntity) {
|
|
586
|
-
throw newEntityOperationError(
|
|
601
|
+
throw newEntityOperationError(
|
|
602
|
+
`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`,
|
|
603
|
+
);
|
|
587
604
|
}
|
|
588
605
|
newEntityOneRelationProps[property.code] = targetEntity;
|
|
589
606
|
targetRow[property.targetIdColumnName!] = targetEntityId;
|
|
@@ -596,7 +613,9 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
596
613
|
routeContext,
|
|
597
614
|
});
|
|
598
615
|
if (!targetEntity) {
|
|
599
|
-
throw newEntityOperationError(
|
|
616
|
+
throw newEntityOperationError(
|
|
617
|
+
`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`,
|
|
618
|
+
);
|
|
600
619
|
}
|
|
601
620
|
newEntityOneRelationProps[property.code] = targetEntity;
|
|
602
621
|
targetRow[property.targetIdColumnName!] = targetEntityId;
|
|
@@ -643,7 +662,10 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
643
662
|
});
|
|
644
663
|
|
|
645
664
|
if (property.linkTableName) {
|
|
646
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
665
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
666
|
+
schema: property.linkSchema,
|
|
667
|
+
tableName: property.linkTableName,
|
|
668
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
647
669
|
const params = [newEntity.id, newTargetEntity.id];
|
|
648
670
|
await server.queryDatabaseObject(command, params);
|
|
649
671
|
}
|
|
@@ -657,7 +679,10 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
657
679
|
}
|
|
658
680
|
|
|
659
681
|
if (property.linkTableName) {
|
|
660
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
682
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
683
|
+
schema: property.linkSchema,
|
|
684
|
+
tableName: property.linkTableName,
|
|
685
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
661
686
|
const params = [newEntity.id, relatedEntityId];
|
|
662
687
|
await server.queryDatabaseObject(command, params);
|
|
663
688
|
} else {
|
|
@@ -675,7 +700,10 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
675
700
|
}
|
|
676
701
|
|
|
677
702
|
if (property.linkTableName) {
|
|
678
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
703
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
704
|
+
schema: property.linkSchema,
|
|
705
|
+
tableName: property.linkTableName,
|
|
706
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
679
707
|
const params = [newEntity.id, relatedEntityId];
|
|
680
708
|
await server.queryDatabaseObject(command, params);
|
|
681
709
|
} else {
|
|
@@ -726,7 +754,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
726
754
|
|
|
727
755
|
entityToSave = changes || {};
|
|
728
756
|
|
|
729
|
-
const userId = options.routeContext
|
|
757
|
+
const userId = options.routeContext?.state?.userId;
|
|
730
758
|
if (userId) {
|
|
731
759
|
const updatedByProperty = getEntityPropertyByCode(server, model, "updatedBy");
|
|
732
760
|
if (updatedByProperty) {
|
|
@@ -799,7 +827,9 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
799
827
|
routeContext,
|
|
800
828
|
});
|
|
801
829
|
if (!targetEntity) {
|
|
802
|
-
throw newEntityOperationError(
|
|
830
|
+
throw newEntityOperationError(
|
|
831
|
+
`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`,
|
|
832
|
+
);
|
|
803
833
|
}
|
|
804
834
|
updatedEntityOneRelationProps[property.code] = targetEntity;
|
|
805
835
|
targetRow[property.targetIdColumnName!] = targetEntityId;
|
|
@@ -812,7 +842,9 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
812
842
|
routeContext,
|
|
813
843
|
});
|
|
814
844
|
if (!targetEntity) {
|
|
815
|
-
throw newEntityOperationError(
|
|
845
|
+
throw newEntityOperationError(
|
|
846
|
+
`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`,
|
|
847
|
+
);
|
|
816
848
|
}
|
|
817
849
|
updatedEntityOneRelationProps[property.code] = targetEntity;
|
|
818
850
|
targetRow[property.targetIdColumnName!] = targetEntityId;
|
|
@@ -848,7 +880,13 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
848
880
|
|
|
849
881
|
if (property.linkTableName) {
|
|
850
882
|
// TODO: should support removing relation
|
|
851
|
-
await server.queryDatabaseObject(
|
|
883
|
+
await server.queryDatabaseObject(
|
|
884
|
+
`DELETE FROM ${server.queryBuilder.quoteTable({
|
|
885
|
+
schema: property.linkSchema,
|
|
886
|
+
tableName: property.linkTableName,
|
|
887
|
+
})} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1`,
|
|
888
|
+
[id],
|
|
889
|
+
);
|
|
852
890
|
}
|
|
853
891
|
|
|
854
892
|
for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
|
|
@@ -866,7 +904,10 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
866
904
|
});
|
|
867
905
|
|
|
868
906
|
if (property.linkTableName) {
|
|
869
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
907
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
908
|
+
schema: property.linkSchema,
|
|
909
|
+
tableName: property.linkTableName,
|
|
910
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
870
911
|
const params = [id, newTargetEntity.id];
|
|
871
912
|
await server.queryDatabaseObject(command, params);
|
|
872
913
|
}
|
|
@@ -880,7 +921,10 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
880
921
|
}
|
|
881
922
|
|
|
882
923
|
if (property.linkTableName) {
|
|
883
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
924
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
925
|
+
schema: property.linkSchema,
|
|
926
|
+
tableName: property.linkTableName,
|
|
927
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
884
928
|
const params = [id, relatedEntityId];
|
|
885
929
|
await server.queryDatabaseObject(command, params);
|
|
886
930
|
} else {
|
|
@@ -898,7 +942,10 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
898
942
|
}
|
|
899
943
|
|
|
900
944
|
if (property.linkTableName) {
|
|
901
|
-
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
945
|
+
const command = `INSERT INTO ${server.queryBuilder.quoteTable({
|
|
946
|
+
schema: property.linkSchema,
|
|
947
|
+
tableName: property.linkTableName,
|
|
948
|
+
})} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
|
|
902
949
|
const params = [id, relatedEntityId];
|
|
903
950
|
await server.queryDatabaseObject(command, params);
|
|
904
951
|
} else {
|
|
@@ -1065,7 +1112,10 @@ export default class EntityManager<TEntity = any> {
|
|
|
1065
1112
|
const { queryBuilder } = server;
|
|
1066
1113
|
if (relationProperty.linkTableName) {
|
|
1067
1114
|
for (const relation of relations) {
|
|
1068
|
-
const command = `INSERT INTO ${queryBuilder.quoteTable({
|
|
1115
|
+
const command = `INSERT INTO ${queryBuilder.quoteTable({
|
|
1116
|
+
schema: relationProperty.linkSchema,
|
|
1117
|
+
tableName: relationProperty.linkTableName,
|
|
1118
|
+
})} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)})
|
|
1069
1119
|
SELECT $1, $2 WHERE NOT EXISTS (
|
|
1070
1120
|
SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)}
|
|
1071
1121
|
FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { RpdApplicationConfig, RpdDataModelProperty } from "~/types";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
IRpdServer,
|
|
5
|
+
RapidPlugin,
|
|
6
|
+
RpdConfigurationItemOptions,
|
|
7
|
+
RpdServerPluginConfigurableTargetOptions,
|
|
8
|
+
RpdServerPluginExtendingAbilities,
|
|
9
|
+
} from "~/core/server";
|
|
4
10
|
import { find, set } from "lodash";
|
|
5
11
|
import { ActionHandlerContext } from "~/core/actionHandler";
|
|
6
12
|
import { isAccessAllowed } from "~/utilities/accessControlUtility";
|
|
@@ -45,16 +51,6 @@ class EntityAccessControlPlugin implements RapidPlugin {
|
|
|
45
51
|
const logger = server.getLogger();
|
|
46
52
|
logger.info("Configuring entity access checking policies...");
|
|
47
53
|
|
|
48
|
-
const model = find(applicationConfig.models, (item) => item.singularCode === "model");
|
|
49
|
-
if (!model) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const { permissionPolicies } = model;
|
|
54
|
-
if (!permissionPolicies) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
54
|
const routes = applicationConfig.routes;
|
|
59
55
|
for (const route of routes) {
|
|
60
56
|
const { actions } = route;
|
|
@@ -63,9 +59,53 @@ class EntityAccessControlPlugin implements RapidPlugin {
|
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
for (const action of route.actions) {
|
|
66
|
-
if (action.code === "findCollectionEntityById") {
|
|
62
|
+
if (action.code === "findCollectionEntityById" || action.code === "findCollectionEntities" || action.code === "countCollectionEntities") {
|
|
63
|
+
const model = find(applicationConfig.models, (item) => item.singularCode === action.config.singularCode);
|
|
64
|
+
if (!model) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const { permissionPolicies } = model;
|
|
68
|
+
if (!permissionPolicies) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
67
71
|
if (permissionPolicies.find) {
|
|
68
|
-
set(action, "config.
|
|
72
|
+
set(action, "config.permissionCheck", permissionPolicies.find);
|
|
73
|
+
}
|
|
74
|
+
} else if (action.code === "createCollectionEntity" || action.code === "createCollectionEntitiesBatch") {
|
|
75
|
+
const model = find(applicationConfig.models, (item) => item.singularCode === action.config.singularCode);
|
|
76
|
+
if (!model) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const { permissionPolicies } = model;
|
|
80
|
+
if (!permissionPolicies) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (permissionPolicies.create) {
|
|
84
|
+
set(action, "config.permissionCheck", permissionPolicies.create);
|
|
85
|
+
}
|
|
86
|
+
} else if (action.code === "updateCollectionEntityById" || action.code === "addEntityRelations" || action.code === "removeEntityRelations") {
|
|
87
|
+
const model = find(applicationConfig.models, (item) => item.singularCode === action.config.singularCode);
|
|
88
|
+
if (!model) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const { permissionPolicies } = model;
|
|
92
|
+
if (!permissionPolicies) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (permissionPolicies.update) {
|
|
96
|
+
set(action, "config.permissionCheck", permissionPolicies.update);
|
|
97
|
+
}
|
|
98
|
+
} else if (action.code === "deleteCollectionEntityById") {
|
|
99
|
+
const model = find(applicationConfig.models, (item) => item.singularCode === action.config.singularCode);
|
|
100
|
+
if (!model) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const { permissionPolicies } = model;
|
|
104
|
+
if (!permissionPolicies) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (permissionPolicies.delete) {
|
|
108
|
+
set(action, "config.permissionCheck", permissionPolicies.delete);
|
|
69
109
|
}
|
|
70
110
|
}
|
|
71
111
|
}
|
|
@@ -93,9 +133,9 @@ class EntityAccessControlPlugin implements RapidPlugin {
|
|
|
93
133
|
const { routerContext } = handlerContext;
|
|
94
134
|
const { routeConfig } = routerContext;
|
|
95
135
|
for (const actionConfig of routeConfig.actions) {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
if (!isAccessAllowed(
|
|
136
|
+
const permissionCheck = actionConfig.config?.permissionCheck;
|
|
137
|
+
if (permissionCheck) {
|
|
138
|
+
if (!isAccessAllowed(permissionCheck, routerContext.state.allowedActions || [])) {
|
|
99
139
|
throw new Error(`Your action of '${actionConfig.code}' is not permitted.`);
|
|
100
140
|
}
|
|
101
141
|
}
|
|
@@ -2,7 +2,13 @@ import type { RpdApplicationConfig, RpdRoute } from "~/types";
|
|
|
2
2
|
|
|
3
3
|
import pluginActionHandlers from "./actionHandlers";
|
|
4
4
|
import { ServerOperation, ServerOperationPluginInitOptions } from "./ServerOperationPluginTypes";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
IRpdServer,
|
|
7
|
+
RapidPlugin,
|
|
8
|
+
RpdConfigurationItemOptions,
|
|
9
|
+
RpdServerPluginConfigurableTargetOptions,
|
|
10
|
+
RpdServerPluginExtendingAbilities,
|
|
11
|
+
} from "~/core/server";
|
|
6
12
|
|
|
7
13
|
class ServerOperationPlugin implements RapidPlugin {
|
|
8
14
|
#operations: ServerOperation[];
|
|
@@ -68,6 +74,7 @@ class ServerOperationPlugin implements RapidPlugin {
|
|
|
68
74
|
code: "runServerOperation",
|
|
69
75
|
config: {
|
|
70
76
|
operation: operation.handler,
|
|
77
|
+
permissionCheck: operation.permissionCheck,
|
|
71
78
|
},
|
|
72
79
|
},
|
|
73
80
|
],
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ActionHandlerContext } from "~/core/actionHandler";
|
|
2
2
|
import { RpdHttpMethod } from "~/types";
|
|
3
|
+
import { PermissionCheckPolicy } from "~/utilities/accessControlUtility";
|
|
3
4
|
|
|
4
5
|
export interface ServerOperation {
|
|
5
6
|
code: string;
|
|
6
7
|
description?: string;
|
|
7
8
|
method: RpdHttpMethod;
|
|
9
|
+
permissionCheck?: PermissionCheckPolicy;
|
|
8
10
|
handler: (ctx: ActionHandlerContext) => Promise<void>;
|
|
9
11
|
}
|
|
10
12
|
|