@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 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({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} = ANY($1::int[])`;
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({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])`;
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.state?.userId;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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.state?.userId;
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({ schema: property.linkSchema, tableName: property.linkTableName })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1`, [id]);
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
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.permissionPolicy", permissionPolicies.find);
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 permissionPolicy = actionConfig.config?.permissionPolicy;
6046
- if (permissionPolicy) {
6047
- if (!isAccessAllowed(permissionPolicy, routerContext.state.allowedActions || [])) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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((property) => property.relation === "one" && !property.linkTableName);
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(server: IRpdServer, model: RpdDataModel, baseModel: RpdDataModel, filters: EntityFilterOptions[] | undefined): Promise<RowFilterOptions[]> {
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(`Invalid filters. Filter with 'existence' operator on property '${filter.field}' is not allowed. You can only use it on an relation property.`);
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({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName! })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName!)} = ANY($1::int[])`;
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({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName! })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName!)} = ANY($1::int[])`;
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.state?.userId;
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(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
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(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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.state?.userId;
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(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
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(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
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(`DELETE FROM ${server.queryBuilder.quoteTable({ schema: property.linkSchema, tableName: property.linkTableName })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName!)} = $1`, [id]);
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: property.linkSchema, tableName: property.linkTableName })} (${server.queryBuilder.quoteObject(property.selfIdColumnName!)}, ${property.targetIdColumnName}) VALUES ($1, $2) ON CONFLICT DO NOTHING;`;
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({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName!)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName!)})
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 { IRpdServer, RapidPlugin, RpdConfigurationItemOptions, RpdServerPluginConfigurableTargetOptions, RpdServerPluginExtendingAbilities } from "~/core/server";
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.permissionPolicy", permissionPolicies.find);
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 permissionPolicy = actionConfig.config?.permissionPolicy;
97
- if (permissionPolicy) {
98
- if (!isAccessAllowed(permissionPolicy, routerContext.state.allowedActions || [])) {
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 { IRpdServer, RapidPlugin, RpdConfigurationItemOptions, RpdServerPluginConfigurableTargetOptions, RpdServerPluginExtendingAbilities } from "~/core/server";
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