@ruiapp/rapid-core 0.1.19 → 0.1.20

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.
Files changed (33) hide show
  1. package/dist/core/pluginManager.d.ts +3 -0
  2. package/dist/core/routeContext.d.ts +1 -0
  3. package/dist/core/server.d.ts +6 -2
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +189 -93
  6. package/dist/plugins/entityAccessControl/EntityAccessControlPlugin.d.ts +17 -0
  7. package/dist/plugins/fileManage/FileManagePlugin.d.ts +0 -10
  8. package/dist/server.d.ts +4 -2
  9. package/dist/types.d.ts +11 -0
  10. package/dist/utilities/accessControlUtility.d.ts +5 -0
  11. package/package.json +2 -2
  12. package/rollup.config.js +1 -18
  13. package/src/core/pluginManager.ts +12 -0
  14. package/src/core/routeContext.ts +1 -0
  15. package/src/core/routesBuilder.ts +9 -4
  16. package/src/core/server.ts +6 -2
  17. package/src/dataAccess/dataAccessor.ts +7 -7
  18. package/src/dataAccess/entityManager.ts +24 -24
  19. package/src/helpers/inputHelper.ts +3 -3
  20. package/src/helpers/runCollectionEntityActionHandler.ts +1 -2
  21. package/src/index.ts +2 -1
  22. package/src/plugins/auth/AuthPlugin.ts +0 -1
  23. package/src/plugins/dataManage/DataManagePlugin.ts +0 -1
  24. package/src/plugins/dataManage/actionHandlers/findCollectionEntities.ts +0 -1
  25. package/src/plugins/dataManage/actionHandlers/queryDatabase.ts +2 -2
  26. package/src/plugins/entityAccessControl/EntityAccessControlPlugin.ts +104 -0
  27. package/src/plugins/fileManage/FileManagePlugin.ts +0 -31
  28. package/src/plugins/metaManage/MetaManagePlugin.ts +6 -6
  29. package/src/plugins/webhooks/WebhooksPlugin.ts +2 -2
  30. package/src/queryBuilder/queryBuilder.ts +3 -3
  31. package/src/server.ts +31 -9
  32. package/src/types.ts +13 -0
  33. package/src/utilities/accessControlUtility.ts +33 -0
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var _ = require('lodash');
5
+ var lodash = require('lodash');
6
6
  var events = require('events');
7
7
  var Router = require('koa-tree-router');
8
8
  var qs = require('qs');
@@ -14,25 +14,6 @@ var uuid = require('uuid');
14
14
 
15
15
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
16
16
 
17
- function _interopNamespace(e) {
18
- if (e && e.__esModule) return e;
19
- var n = Object.create(null);
20
- if (e) {
21
- Object.keys(e).forEach(function (k) {
22
- if (k !== 'default') {
23
- var d = Object.getOwnPropertyDescriptor(e, k);
24
- Object.defineProperty(n, k, d.get ? d : {
25
- enumerable: true,
26
- get: function () { return e[k]; }
27
- });
28
- }
29
- });
30
- }
31
- n["default"] = e;
32
- return Object.freeze(n);
33
- }
34
-
35
- var ___namespace = /*#__PURE__*/_interopNamespace(_);
36
17
  var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
37
18
  var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
38
19
  var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
@@ -63,7 +44,7 @@ class DataAccessor {
63
44
  };
64
45
  const query = this.#queryBuilder.insert(this.#model, options);
65
46
  const result = await this.#databaseAccessor.queryDatabaseObject(query.command, query.params);
66
- return ___namespace.first(result);
47
+ return lodash.first(result);
67
48
  }
68
49
  async updateById(id, entity) {
69
50
  const options = {
@@ -78,7 +59,7 @@ class DataAccessor {
78
59
  };
79
60
  const query = this.#queryBuilder.update(this.#model, options);
80
61
  const result = await this.#databaseAccessor.queryDatabaseObject(query.command, query.params);
81
- return ___namespace.first(result);
62
+ return lodash.first(result);
82
63
  }
83
64
  async find(options) {
84
65
  console.debug(`DataAccessor '${this.#model.singularCode}' find with options:`, options);
@@ -86,9 +67,9 @@ class DataAccessor {
86
67
  return await this.#databaseAccessor.queryDatabaseObject(query.command, query.params);
87
68
  }
88
69
  async findOne(options) {
89
- ___namespace.set(options, "pagination.limit", 1);
70
+ lodash.set(options, "pagination.limit", 1);
90
71
  const list = await this.find(options);
91
- return ___namespace.first(list);
72
+ return lodash.first(list);
92
73
  }
93
74
  async findById(id) {
94
75
  const options = {
@@ -102,12 +83,12 @@ class DataAccessor {
102
83
  };
103
84
  const query = this.#queryBuilder.select(this.#model, options);
104
85
  const result = await this.#databaseAccessor.queryDatabaseObject(query.command, query.params);
105
- return ___namespace.first(result);
86
+ return lodash.first(result);
106
87
  }
107
88
  async count(options) {
108
89
  const query = this.#queryBuilder.count(this.#model, options);
109
90
  const result = await this.#databaseAccessor.queryDatabaseObject(query.command, query.params);
110
- const row = ___namespace.first(result);
91
+ const row = lodash.first(result);
111
92
  if (row) {
112
93
  return row;
113
94
  }
@@ -233,7 +214,7 @@ class QueryBuilder {
233
214
  }
234
215
  let property = null;
235
216
  if (model) {
236
- property = ___namespace.find(model.properties, (e) => e.code === propertyName);
217
+ property = lodash.find(model.properties, (e) => e.code === propertyName);
237
218
  }
238
219
  if (property && property.type === "json") {
239
220
  params.push(JSON.stringify(entity[propertyName]));
@@ -268,7 +249,7 @@ class QueryBuilder {
268
249
  }
269
250
  let property = null;
270
251
  if (model) {
271
- property = ___namespace.find(model.properties, (e) => (e.columnName || e.code) === propertyName);
252
+ property = lodash.find(model.properties, (e) => (e.columnName || e.code) === propertyName);
272
253
  }
273
254
  if (property && property.type === "json") {
274
255
  params.push(JSON.stringify(entity[propertyName]));
@@ -554,6 +535,14 @@ class PluginManager {
554
535
  }
555
536
  }
556
537
  }
538
+ /** 在接收到HTTP请求,执行 actions 前调用。 */
539
+ async beforeRunRouteActions(handlerContext) {
540
+ for (const plugin of this.#plugins) {
541
+ if (plugin.beforeRunRouteActions) {
542
+ await plugin.beforeRunRouteActions(this.#server, handlerContext);
543
+ }
544
+ }
545
+ }
557
546
  }
558
547
 
559
548
  class EventManager {
@@ -596,6 +585,7 @@ async function buildRoutes(server, applicationConfig) {
596
585
  }
597
586
  const routePath = baseUrl + routeConfig.endpoint;
598
587
  router[routeConfig.method.toLowerCase()](routePath, async (routerContext, next) => {
588
+ routerContext.routeConfig = lodash.cloneDeep(routeConfig);
599
589
  const { request, params } = routerContext;
600
590
  let search = request.url.search;
601
591
  if (search && search.startsWith("?")) {
@@ -621,12 +611,14 @@ async function buildRoutes(server, applicationConfig) {
621
611
  applicationConfig,
622
612
  input,
623
613
  };
624
- for (const handlerConfig of routeConfig.actions) {
625
- const handler = server.getActionHandlerByCode(handlerConfig.code);
614
+ await server.beforeRunRouteActions(handlerContext);
615
+ for (const actionConfig of routeConfig.actions) {
616
+ const actionCode = actionConfig.code;
617
+ const handler = server.getActionHandlerByCode(actionCode);
626
618
  if (!handler) {
627
- throw new Error("Unknown handler: " + handlerConfig.code);
619
+ throw new Error("Unknown handler: " + actionCode);
628
620
  }
629
- const result = handler(handlerContext, handlerConfig.config);
621
+ const result = handler(handlerContext, actionConfig.config);
630
622
  if (result instanceof Promise) {
631
623
  await result;
632
624
  }
@@ -645,12 +637,12 @@ function mergeHeaders(target, source) {
645
637
  target.set(keyValuePair[0], keyValuePair[1]);
646
638
  }
647
639
  }
648
- else if (_.isArray(source)) {
640
+ else if (lodash.isArray(source)) {
649
641
  for (const keyValuePair of source) {
650
642
  target.set(keyValuePair[0], keyValuePair[1]);
651
643
  }
652
644
  }
653
- else if (_.isObject(source)) {
645
+ else if (lodash.isObject(source)) {
654
646
  Object.entries(source).forEach(([key, value]) => target.set(key, value));
655
647
  }
656
648
  return target;
@@ -704,6 +696,7 @@ class RouteContext {
704
696
  method;
705
697
  path;
706
698
  params;
699
+ routeConfig;
707
700
  constructor(request) {
708
701
  this.request = request;
709
702
  this.state = {};
@@ -1739,7 +1732,7 @@ async function findEntities(server, dataAccessor, options) {
1739
1732
  const fieldsToSelect = [];
1740
1733
  const relationPropertiesToSelect = [];
1741
1734
  if (options.properties) {
1742
- ___namespace.forEach(options.properties, (propertyCode) => {
1735
+ lodash.forEach(options.properties, (propertyCode) => {
1743
1736
  const property = model.properties.find((e) => e.code === propertyCode);
1744
1737
  if (!property) {
1745
1738
  throw new Error(`Collection '${model.namespace}.${model.singularCode}' does not have a property '${propertyCode}'.`);
@@ -1780,8 +1773,8 @@ async function findEntities(server, dataAccessor, options) {
1780
1773
  }
1781
1774
  if (isManyRelation) {
1782
1775
  const relationLinks = await findManyRelationLinksViaLinkTable(server, targetModel, relationProperty, entityIds);
1783
- ___namespace.forEach(entities, (entity) => {
1784
- entity[relationProperty.code] = ___namespace.filter(relationLinks, (link) => {
1776
+ lodash.forEach(entities, (entity) => {
1777
+ entity[relationProperty.code] = lodash.filter(relationLinks, (link) => {
1785
1778
  return link[relationProperty.selfIdColumnName] == entity["id"];
1786
1779
  }).map(link => mapDbRowToEntity(targetModel, link.targetEntity, false));
1787
1780
  });
@@ -1793,7 +1786,7 @@ async function findEntities(server, dataAccessor, options) {
1793
1786
  relatedEntities = await findManyRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, entityIds);
1794
1787
  }
1795
1788
  else {
1796
- const targetEntityIds = ___namespace.uniq(___namespace.reject(___namespace.map(entities, (entity) => entity[relationProperty.targetIdColumnName]), isNullOrUndefined));
1789
+ const targetEntityIds = lodash.uniq(lodash.reject(lodash.map(entities, (entity) => entity[relationProperty.targetIdColumnName]), isNullOrUndefined));
1797
1790
  relatedEntities = await findOneRelatedEntitiesViaIdPropertyCode(server, model, relationProperty, targetEntityIds);
1798
1791
  }
1799
1792
  const targetModel = server.getModel({
@@ -1801,12 +1794,12 @@ async function findEntities(server, dataAccessor, options) {
1801
1794
  });
1802
1795
  entities.forEach((entity) => {
1803
1796
  if (isManyRelation) {
1804
- entity[relationProperty.code] = ___namespace.filter(relatedEntities, (relatedEntity) => {
1797
+ entity[relationProperty.code] = lodash.filter(relatedEntities, (relatedEntity) => {
1805
1798
  return relatedEntity[relationProperty.selfIdColumnName] == entity.id;
1806
1799
  }).map(item => mapDbRowToEntity(targetModel, item, false));
1807
1800
  }
1808
1801
  else {
1809
- entity[relationProperty.code] = mapDbRowToEntity(targetModel, ___namespace.find(relatedEntities, (relatedEntity) => {
1802
+ entity[relationProperty.code] = mapDbRowToEntity(targetModel, lodash.find(relatedEntities, (relatedEntity) => {
1810
1803
  // TODO: id property code should be configurable.
1811
1804
  return relatedEntity["id"] == entity[relationProperty.targetIdColumnName];
1812
1805
  }), false);
@@ -1819,7 +1812,7 @@ async function findEntities(server, dataAccessor, options) {
1819
1812
  }
1820
1813
  async function findEntity(server, dataAccessor, options) {
1821
1814
  const entities = await findEntities(server, dataAccessor, options);
1822
- return ___namespace.first(entities);
1815
+ return lodash.first(entities);
1823
1816
  }
1824
1817
  async function findById(server, dataAccessor, id, keepNonPropertyFields = false) {
1825
1818
  return await findEntity(server, dataAccessor, {
@@ -1845,7 +1838,7 @@ async function replaceFiltersWithFiltersOperator(server, model, filters) {
1845
1838
  replacedFilters.push(filter);
1846
1839
  }
1847
1840
  else if (operator === "exists" || operator === "notExists") {
1848
- const relationProperty = ___namespace.find(model.properties, (property) => property.code === filter.field);
1841
+ const relationProperty = lodash.find(model.properties, (property) => property.code === filter.field);
1849
1842
  if (!relationProperty) {
1850
1843
  throw new Error(`Invalid filters. Property '${filter.field}' was not found in model '${model.namespace}.${model.singularCode}'`);
1851
1844
  }
@@ -1890,7 +1883,7 @@ async function replaceFiltersWithFiltersOperator(server, model, filters) {
1890
1883
  filters: filter.filters,
1891
1884
  properties: ["id"],
1892
1885
  });
1893
- const entityIds = ___namespace.map(entities, (entity) => entity["id"]);
1886
+ const entityIds = lodash.map(entities, (entity) => entity["id"]);
1894
1887
  replacedFilters.push({
1895
1888
  field: relationProperty.targetIdColumnName,
1896
1889
  operator: operator === "exists" ? "in" : "notIn",
@@ -1909,7 +1902,7 @@ async function replaceFiltersWithFiltersOperator(server, model, filters) {
1909
1902
  filters: filter.filters,
1910
1903
  properties: [relationProperty.selfIdColumnName],
1911
1904
  });
1912
- const selfEntityIds = ___namespace.map(targetEntities, (entity) => entity[relationProperty.selfIdColumnName]);
1905
+ const selfEntityIds = lodash.map(targetEntities, (entity) => entity[relationProperty.selfIdColumnName]);
1913
1906
  replacedFilters.push({
1914
1907
  field: "id",
1915
1908
  operator: operator === "exists" ? "in" : "notIn",
@@ -1934,7 +1927,7 @@ async function replaceFiltersWithFiltersOperator(server, model, filters) {
1934
1927
  filters: filter.filters,
1935
1928
  properties: ['id'],
1936
1929
  });
1937
- const targetEntityIds = ___namespace.map(targetEntities, (entity) => entity['id']);
1930
+ const targetEntityIds = lodash.map(targetEntities, (entity) => entity['id']);
1938
1931
  const command = `SELECT * FROM ${server.queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })} WHERE ${server.queryBuilder.quoteObject(relationProperty.targetIdColumnName)} = ANY($1::int[])`;
1939
1932
  const params = [targetEntityIds];
1940
1933
  const links = await server.queryDatabaseObject(command, params);
@@ -1971,8 +1964,8 @@ async function findManyRelationLinksViaLinkTable(server, targetModel, relationPr
1971
1964
  singularCode: targetModel.singularCode,
1972
1965
  });
1973
1966
  const targetEntities = await dataAccessor.find(findEntityOptions);
1974
- ___namespace.forEach(links, (link) => {
1975
- link.targetEntity = ___namespace.find(targetEntities, (e) => e["id"] == link[relationProperty.targetIdColumnName]);
1967
+ lodash.forEach(links, (link) => {
1968
+ link.targetEntity = lodash.find(targetEntities, (e) => e["id"] == link[relationProperty.targetIdColumnName]);
1976
1969
  });
1977
1970
  return links;
1978
1971
  }
@@ -2011,7 +2004,7 @@ async function createEntity(server, dataAccessor, options) {
2011
2004
  const { entity } = options;
2012
2005
  const oneRelationPropertiesToCreate = [];
2013
2006
  const manyRelationPropertiesToCreate = [];
2014
- ___namespace.keys(entity).forEach((propertyCode) => {
2007
+ lodash.keys(entity).forEach((propertyCode) => {
2015
2008
  const property = model.properties.find((e) => e.code === propertyCode);
2016
2009
  if (!property) {
2017
2010
  // Unknown property
@@ -2030,7 +2023,7 @@ async function createEntity(server, dataAccessor, options) {
2030
2023
  // save one-relation properties
2031
2024
  for (const property of oneRelationPropertiesToCreate) {
2032
2025
  const fieldValue = entity[property.code];
2033
- if (___namespace.isObject(fieldValue)) {
2026
+ if (lodash.isObject(fieldValue)) {
2034
2027
  if (!fieldValue["id"]) {
2035
2028
  const targetDataAccessor = server.getDataAccessor({
2036
2029
  singularCode: property.targetSingularCode,
@@ -2059,12 +2052,12 @@ async function createEntity(server, dataAccessor, options) {
2059
2052
  singularCode: property.targetSingularCode,
2060
2053
  });
2061
2054
  const relatedEntitiesToBeSaved = entity[property.code];
2062
- if (!___namespace.isArray(relatedEntitiesToBeSaved)) {
2055
+ if (!lodash.isArray(relatedEntitiesToBeSaved)) {
2063
2056
  throw new Error(`Value of field '${property.code}' should be an array.`);
2064
2057
  }
2065
2058
  for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
2066
2059
  let relatedEntityId;
2067
- if (___namespace.isObject(relatedEntityToBeSaved)) {
2060
+ if (lodash.isObject(relatedEntityToBeSaved)) {
2068
2061
  relatedEntityId = relatedEntityToBeSaved["id"];
2069
2062
  if (!relatedEntityId) {
2070
2063
  // related entity is to be created
@@ -2138,7 +2131,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2138
2131
  }
2139
2132
  const oneRelationPropertiesToUpdate = [];
2140
2133
  const manyRelationPropertiesToUpdate = [];
2141
- ___namespace.keys(changes).forEach((propertyCode) => {
2134
+ lodash.keys(changes).forEach((propertyCode) => {
2142
2135
  const property = model.properties.find((e) => e.code === propertyCode);
2143
2136
  if (!property) {
2144
2137
  // Unknown property
@@ -2156,7 +2149,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2156
2149
  const row = mapEntityToDbRow(model, changes);
2157
2150
  oneRelationPropertiesToUpdate.forEach(property => {
2158
2151
  const fieldValue = changes[property.code];
2159
- if (___namespace.isObject(fieldValue)) {
2152
+ if (lodash.isObject(fieldValue)) {
2160
2153
  row[property.targetIdColumnName] = fieldValue["id"];
2161
2154
  }
2162
2155
  else {
@@ -2175,7 +2168,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2175
2168
  singularCode: property.targetSingularCode,
2176
2169
  });
2177
2170
  const relatedEntitiesToBeSaved = changes[property.code];
2178
- if (!___namespace.isArray(relatedEntitiesToBeSaved)) {
2171
+ if (!lodash.isArray(relatedEntitiesToBeSaved)) {
2179
2172
  throw new Error(`Value of field '${property.code}' should be an array.`);
2180
2173
  }
2181
2174
  if (property.linkTableName) {
@@ -2184,7 +2177,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
2184
2177
  }
2185
2178
  for (const relatedEntityToBeSaved of relatedEntitiesToBeSaved) {
2186
2179
  let relatedEntityId;
2187
- if (___namespace.isObject(relatedEntityToBeSaved)) {
2180
+ if (lodash.isObject(relatedEntityToBeSaved)) {
2188
2181
  relatedEntityId = relatedEntityToBeSaved["id"];
2189
2182
  if (!relatedEntityId) {
2190
2183
  // related entity is to be created
@@ -2407,12 +2400,13 @@ class RapidServer {
2407
2400
  const { models, routes } = config;
2408
2401
  if (models) {
2409
2402
  for (const model of models) {
2410
- const originalModel = ___namespace.find(this.#applicationConfig.models, (item) => item.singularCode == model.singularCode);
2403
+ const originalModel = lodash.find(this.#applicationConfig.models, (item) => item.singularCode == model.singularCode);
2411
2404
  if (originalModel) {
2405
+ lodash.merge(originalModel, lodash.omit(model, ["id", "maintainedBy", "namespace", "singularCode", "pluralCode", "schema", "tableName", "properties", "extensions"]));
2412
2406
  originalModel.name = model.name;
2413
2407
  const originalProperties = originalModel.properties;
2414
2408
  for (const property of model.properties) {
2415
- const originalProperty = ___namespace.find(originalProperties, (item) => item.code == property.code);
2409
+ const originalProperty = lodash.find(originalProperties, (item) => item.code == property.code);
2416
2410
  if (originalProperty) {
2417
2411
  originalProperty.name = property.name;
2418
2412
  }
@@ -2428,7 +2422,7 @@ class RapidServer {
2428
2422
  }
2429
2423
  if (routes) {
2430
2424
  for (const route of routes) {
2431
- const originalRoute = ___namespace.find(this.#applicationConfig.routes, (item) => item.code == route.code);
2425
+ const originalRoute = lodash.find(this.#applicationConfig.routes, (item) => item.code == route.code);
2432
2426
  if (originalRoute) {
2433
2427
  originalRoute.name = route.name;
2434
2428
  originalRoute.actions = route.actions;
@@ -2439,8 +2433,24 @@ class RapidServer {
2439
2433
  }
2440
2434
  }
2441
2435
  }
2436
+ appendModelProperties(modelSingularCode, properties) {
2437
+ const originalModel = lodash.find(this.#applicationConfig.models, (item) => item.singularCode == modelSingularCode);
2438
+ if (!originalModel) {
2439
+ throw new Error(`Cannot append model properties. Model '${modelSingularCode}' was not found.`);
2440
+ }
2441
+ const originalProperties = originalModel.properties;
2442
+ for (const property of properties) {
2443
+ const originalProperty = lodash.find(originalProperties, (item) => item.code == property.code);
2444
+ if (originalProperty) {
2445
+ originalProperty.name = property.name;
2446
+ }
2447
+ else {
2448
+ originalProperties.push(property);
2449
+ }
2450
+ }
2451
+ }
2442
2452
  registerActionHandler(plugin, options) {
2443
- const handler = ___namespace.bind(options.handler, null, plugin);
2453
+ const handler = lodash.bind(options.handler, null, plugin);
2444
2454
  this.#actionHandlersMapByCode.set(options.code, handler);
2445
2455
  }
2446
2456
  getActionHandlerByCode(code) {
@@ -2513,7 +2523,7 @@ class RapidServer {
2513
2523
  await pluginManager.onApplicationReady(this.#applicationConfig);
2514
2524
  }
2515
2525
  async configureApplication() {
2516
- this.#applicationConfig = ___namespace.merge({}, this.#bootstrapApplicationConfig);
2526
+ this.#applicationConfig = lodash.cloneDeep(this.#bootstrapApplicationConfig);
2517
2527
  const pluginManager = this.#pluginManager;
2518
2528
  await pluginManager.onLoadingApplication(this.#applicationConfig);
2519
2529
  await pluginManager.configureModels(this.#applicationConfig);
@@ -2542,10 +2552,13 @@ class RapidServer {
2542
2552
  const rapidRequest = new RapidRequest(request);
2543
2553
  await rapidRequest.parseBody();
2544
2554
  const routeContext = new RouteContext(rapidRequest);
2545
- this.#pluginManager.onPrepareRouteContext(routeContext);
2555
+ await this.#pluginManager.onPrepareRouteContext(routeContext);
2546
2556
  await this.#buildedRoutes(routeContext, next);
2547
2557
  return routeContext.response.getResponse();
2548
2558
  }
2559
+ async beforeRunRouteActions(handlerContext) {
2560
+ await this.#pluginManager.beforeRunRouteActions(handlerContext);
2561
+ }
2549
2562
  }
2550
2563
 
2551
2564
  // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
@@ -2857,7 +2870,7 @@ async function syncDatabaseSchema(server, applicationConfig) {
2857
2870
  console.debug(`Checking data table for '${model.namespace}.${model.singularCode}'...`);
2858
2871
  const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
2859
2872
  const expectedTableName = model.tableName;
2860
- const tableInDb = ___namespace.find(tablesInDb, { table_schema: expectedTableSchema, table_name: expectedTableName });
2873
+ const tableInDb = lodash.find(tablesInDb, { table_schema: expectedTableSchema, table_name: expectedTableName });
2861
2874
  if (!tableInDb) {
2862
2875
  await server.queryDatabaseObject(`CREATE TABLE IF NOT EXISTS ${queryBuilder.quoteTable(model)} ()`, []);
2863
2876
  }
@@ -2875,7 +2888,7 @@ async function syncDatabaseSchema(server, applicationConfig) {
2875
2888
  if (!targetModel) {
2876
2889
  console.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
2877
2890
  }
2878
- const columnInDb = ___namespace.find(columnsInDb, {
2891
+ const columnInDb = lodash.find(columnsInDb, {
2879
2892
  table_schema: model.schema || "public",
2880
2893
  table_name: model.tableName,
2881
2894
  column_name: property.targetIdColumnName,
@@ -2893,7 +2906,7 @@ async function syncDatabaseSchema(server, applicationConfig) {
2893
2906
  }
2894
2907
  else if (property.relation === "many") {
2895
2908
  if (property.linkTableName) {
2896
- const tableInDb = ___namespace.find(tablesInDb, { table_schema: property.linkSchema || server.databaseConfig.dbDefaultSchema, table_name: property.linkTableName });
2909
+ const tableInDb = lodash.find(tablesInDb, { table_schema: property.linkSchema || server.databaseConfig.dbDefaultSchema, table_name: property.linkTableName });
2897
2910
  if (!tableInDb) {
2898
2911
  columnDDL = generateLinkTableDDL(queryBuilder, {
2899
2912
  linkSchema: property.linkSchema,
@@ -2909,7 +2922,7 @@ async function syncDatabaseSchema(server, applicationConfig) {
2909
2922
  console.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
2910
2923
  continue;
2911
2924
  }
2912
- const columnInDb = ___namespace.find(columnsInDb, {
2925
+ const columnInDb = lodash.find(columnsInDb, {
2913
2926
  table_schema: targetModel.schema || "public",
2914
2927
  table_name: targetModel.tableName,
2915
2928
  column_name: property.selfIdColumnName,
@@ -2935,7 +2948,7 @@ async function syncDatabaseSchema(server, applicationConfig) {
2935
2948
  }
2936
2949
  else {
2937
2950
  const columnName = property.columnName || property.code;
2938
- const columnInDb = ___namespace.find(columnsInDb, {
2951
+ const columnInDb = lodash.find(columnsInDb, {
2939
2952
  table_schema: model.schema || "public",
2940
2953
  table_name: model.tableName,
2941
2954
  column_name: columnName,
@@ -3035,10 +3048,10 @@ const pgPropertyTypeColumnMap = {
3035
3048
  };
3036
3049
 
3037
3050
  function mergeInput(defaultInput, input, fixedInput) {
3038
- return ___namespace.mergeWith({}, defaultInput, input, fixedInput, doNotMergeArray);
3051
+ return lodash.mergeWith({}, defaultInput, input, fixedInput, doNotMergeArray);
3039
3052
  }
3040
3053
  function doNotMergeArray(objValue, srcValue) {
3041
- if (___namespace.isArray(srcValue)) {
3054
+ if (lodash.isArray(srcValue)) {
3042
3055
  return srcValue;
3043
3056
  }
3044
3057
  }
@@ -3185,7 +3198,7 @@ async function handler$d(plugin, ctx, options) {
3185
3198
  const { defaultInput, fixedInput } = options;
3186
3199
  console.debug(`Running ${code$d} handler...`);
3187
3200
  const { entities } = input;
3188
- if (!_.isArray(entities)) {
3201
+ if (!lodash.isArray(entities)) {
3189
3202
  throw new Error("input.entities should be an array.");
3190
3203
  }
3191
3204
  console.debug(`defaultInput: ${JSON.stringify(defaultInput)}`);
@@ -3300,7 +3313,7 @@ async function handler$8(plugin, ctx, options) {
3300
3313
  console.debug(`mergedInput: ${JSON.stringify(mergedInput)}`);
3301
3314
  const result = await server.queryDatabaseObject(sql, mergedInput);
3302
3315
  if (querySingle) {
3303
- ctx.output = ___namespace.first(result);
3316
+ ctx.output = lodash.first(result);
3304
3317
  }
3305
3318
  else {
3306
3319
  ctx.output = result;
@@ -3735,7 +3748,7 @@ class WebhooksPlugin {
3735
3748
  return;
3736
3749
  }
3737
3750
  for (const webhook of this.#webhooks) {
3738
- if (___namespace.indexOf(webhook.events, event) === -1) {
3751
+ if (lodash.indexOf(webhook.events, event) === -1) {
3739
3752
  continue;
3740
3753
  }
3741
3754
  if (webhook.namespace != payload.namespace ||
@@ -4136,7 +4149,7 @@ const code$1 = "uploadFile";
4136
4149
  async function handler$1(plugin, ctx, options) {
4137
4150
  const { server, applicationConfig, routerContext, input } = ctx;
4138
4151
  let file = input.file || input.files;
4139
- if (_.isArray(file)) {
4152
+ if (lodash.isArray(file)) {
4140
4153
  file = file[0];
4141
4154
  }
4142
4155
  if (!file) {
@@ -4225,35 +4238,14 @@ class FileManager {
4225
4238
  get configurations() {
4226
4239
  return [];
4227
4240
  }
4228
- async initPlugin(server) {
4229
- }
4230
- async registerMiddlewares(server) {
4231
- }
4232
4241
  async registerActionHandlers(server) {
4233
4242
  server.registerActionHandler(this, downloadDocumentActionHandler);
4234
4243
  server.registerActionHandler(this, downloadFileActionHandler);
4235
4244
  server.registerActionHandler(this, uploadFileActionHandler);
4236
4245
  }
4237
- async registerEventHandlers(server) {
4238
- }
4239
- async registerMessageHandlers(server) {
4240
- }
4241
- async registerTaskProcessors(server) {
4242
- }
4243
- async onLoadingApplication(server, applicationConfig) {
4244
- }
4245
- async configureModels(server, applicationConfig) {
4246
- }
4247
- async configureModelProperties(server, applicationConfig) {
4248
- }
4249
4246
  async configureRoutes(server, applicationConfig) {
4250
4247
  server.appendApplicationConfig({ routes: pluginRoutes });
4251
4248
  }
4252
- async onApplicationLoaded(server, applicationConfig) {
4253
- console.log("fileManager.onApplicationLoaded");
4254
- }
4255
- async onApplicationReady(server, applicationConfig) {
4256
- }
4257
4249
  }
4258
4250
 
4259
4251
  const code = "runServerOperation";
@@ -4421,10 +4413,114 @@ class EntityWatchPlugin {
4421
4413
  }
4422
4414
  }
4423
4415
 
4416
+ function isAccessAllowed(policy, allowedActions) {
4417
+ let isAnyCheckPassed = true;
4418
+ let isAllCheckPassed = true;
4419
+ if (policy.any) {
4420
+ isAnyCheckPassed = false;
4421
+ for (const action of policy.any) {
4422
+ if (lodash.find(allowedActions, item => item === action) != null) {
4423
+ isAnyCheckPassed = true;
4424
+ break;
4425
+ }
4426
+ }
4427
+ }
4428
+ if (policy.all) {
4429
+ isAllCheckPassed = true;
4430
+ for (const action of policy.all) {
4431
+ if (lodash.find(allowedActions, item => item === action) == null) {
4432
+ isAnyCheckPassed = false;
4433
+ break;
4434
+ }
4435
+ }
4436
+ }
4437
+ return isAnyCheckPassed && isAllCheckPassed;
4438
+ }
4439
+
4440
+ class EntityAccessControlPlugin {
4441
+ constructor() {
4442
+ }
4443
+ get code() {
4444
+ return "entityAccessControl";
4445
+ }
4446
+ get description() {
4447
+ return "";
4448
+ }
4449
+ get extendingAbilities() {
4450
+ return [];
4451
+ }
4452
+ get configurableTargets() {
4453
+ return [];
4454
+ }
4455
+ get configurations() {
4456
+ return [];
4457
+ }
4458
+ async onLoadingApplication(server, applicationConfig) {
4459
+ const properties = [
4460
+ {
4461
+ name: "permissionPolicies",
4462
+ code: "permissionPolicies",
4463
+ columnName: "permission_policies",
4464
+ type: "json",
4465
+ },
4466
+ ];
4467
+ server.appendModelProperties("model", properties);
4468
+ }
4469
+ async configureRoutes(server, applicationConfig) {
4470
+ const model = lodash.find(applicationConfig.models, (item) => item.singularCode === "model");
4471
+ if (!model) {
4472
+ return;
4473
+ }
4474
+ const { permissionPolicies } = model;
4475
+ if (!permissionPolicies) {
4476
+ return;
4477
+ }
4478
+ const routes = applicationConfig.routes;
4479
+ for (const route of routes) {
4480
+ const { actions } = route;
4481
+ if (!actions) {
4482
+ continue;
4483
+ }
4484
+ for (const action of route.actions) {
4485
+ if (action.code === "findCollectionEntityById") {
4486
+ if (permissionPolicies.find) {
4487
+ lodash.set(action, "config.permissionPolicy", permissionPolicies.find);
4488
+ }
4489
+ }
4490
+ }
4491
+ }
4492
+ }
4493
+ async onPrepareRouteContext(server, routeContext) {
4494
+ const userId = routeContext.state.userId;
4495
+ if (!userId) {
4496
+ return;
4497
+ }
4498
+ const actions = await server.queryDatabaseObject(`select distinct a.* from sys_actions a
4499
+ inner join oc_role_sys_action_links ra on a.id = ra.action_id
4500
+ inner join oc_role_user_links ru on ru.role_id = ra.role_id
4501
+ where ru.user_id = $1;`, [userId]);
4502
+ routeContext.state.allowedActions = actions.map(item => item.code);
4503
+ }
4504
+ async beforeRunRouteActions(server, handlerContext) {
4505
+ // Check permission
4506
+ const { routerContext } = handlerContext;
4507
+ const { routeConfig } = routerContext;
4508
+ for (const actionConfig of routeConfig.actions) {
4509
+ const permissionPolicy = actionConfig.config?.permissionPolicy;
4510
+ if (permissionPolicy) {
4511
+ if (!isAccessAllowed(permissionPolicy, routerContext.state.allowedActions || [])) {
4512
+ throw new Error(`Your action of '${actionConfig.code}' is not permitted.`);
4513
+ }
4514
+ }
4515
+ }
4516
+ }
4517
+ }
4518
+
4424
4519
  fixBigIntJSONSerialize();
4425
4520
 
4426
4521
  exports.AuthPlugin = AuthPlugin;
4427
4522
  exports.DataManagePlugin = DataManager;
4523
+ exports.EntityAccessControlPlugin = EntityAccessControlPlugin;
4428
4524
  exports.EntityWatchPlugin = EntityWatchPlugin;
4429
4525
  exports.FileManagePlugin = FileManager;
4430
4526
  exports.GlobalRequest = GlobalRequest;
@@ -0,0 +1,17 @@
1
+ import type { RpdApplicationConfig } from "../../types";
2
+ import { IRpdServer, RapidPlugin, RpdConfigurationItemOptions, RpdServerPluginConfigurableTargetOptions, RpdServerPluginExtendingAbilities } from "../../core/server";
3
+ import { ActionHandlerContext } from "../../core/actionHandler";
4
+ import { RouteContext } from "../../core/routeContext";
5
+ declare class EntityAccessControlPlugin implements RapidPlugin {
6
+ constructor();
7
+ get code(): string;
8
+ get description(): string;
9
+ get extendingAbilities(): RpdServerPluginExtendingAbilities[];
10
+ get configurableTargets(): RpdServerPluginConfigurableTargetOptions[];
11
+ get configurations(): RpdConfigurationItemOptions[];
12
+ onLoadingApplication(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
13
+ configureRoutes(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
14
+ onPrepareRouteContext(server: IRpdServer, routeContext: RouteContext): Promise<void>;
15
+ beforeRunRouteActions(server: IRpdServer, handlerContext: ActionHandlerContext): Promise<any>;
16
+ }
17
+ export default EntityAccessControlPlugin;
@@ -9,17 +9,7 @@ declare class FileManager implements RapidPlugin {
9
9
  get extendingAbilities(): RpdServerPluginExtendingAbilities[];
10
10
  get configurableTargets(): RpdServerPluginConfigurableTargetOptions[];
11
11
  get configurations(): RpdConfigurationItemOptions[];
12
- initPlugin(server: IRpdServer): Promise<any>;
13
- registerMiddlewares(server: IRpdServer): Promise<any>;
14
12
  registerActionHandlers(server: IRpdServer): Promise<any>;
15
- registerEventHandlers(server: IRpdServer): Promise<any>;
16
- registerMessageHandlers(server: IRpdServer): Promise<any>;
17
- registerTaskProcessors(server: IRpdServer): Promise<any>;
18
- onLoadingApplication(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
19
- configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
20
- configureModelProperties(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
21
13
  configureRoutes(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
22
- onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
23
- onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
24
14
  }
25
15
  export default FileManager;