@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.
- package/dist/core/pluginManager.d.ts +3 -0
- package/dist/core/routeContext.d.ts +1 -0
- package/dist/core/server.d.ts +6 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +189 -93
- package/dist/plugins/entityAccessControl/EntityAccessControlPlugin.d.ts +17 -0
- package/dist/plugins/fileManage/FileManagePlugin.d.ts +0 -10
- package/dist/server.d.ts +4 -2
- package/dist/types.d.ts +11 -0
- package/dist/utilities/accessControlUtility.d.ts +5 -0
- package/package.json +2 -2
- package/rollup.config.js +1 -18
- package/src/core/pluginManager.ts +12 -0
- package/src/core/routeContext.ts +1 -0
- package/src/core/routesBuilder.ts +9 -4
- package/src/core/server.ts +6 -2
- package/src/dataAccess/dataAccessor.ts +7 -7
- package/src/dataAccess/entityManager.ts +24 -24
- package/src/helpers/inputHelper.ts +3 -3
- package/src/helpers/runCollectionEntityActionHandler.ts +1 -2
- package/src/index.ts +2 -1
- package/src/plugins/auth/AuthPlugin.ts +0 -1
- package/src/plugins/dataManage/DataManagePlugin.ts +0 -1
- package/src/plugins/dataManage/actionHandlers/findCollectionEntities.ts +0 -1
- package/src/plugins/dataManage/actionHandlers/queryDatabase.ts +2 -2
- package/src/plugins/entityAccessControl/EntityAccessControlPlugin.ts +104 -0
- package/src/plugins/fileManage/FileManagePlugin.ts +0 -31
- package/src/plugins/metaManage/MetaManagePlugin.ts +6 -6
- package/src/plugins/webhooks/WebhooksPlugin.ts +2 -2
- package/src/queryBuilder/queryBuilder.ts +3 -3
- package/src/server.ts +31 -9
- package/src/types.ts +13 -0
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
70
|
+
lodash.set(options, "pagination.limit", 1);
|
|
90
71
|
const list = await this.find(options);
|
|
91
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
625
|
-
|
|
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: " +
|
|
619
|
+
throw new Error("Unknown handler: " + actionCode);
|
|
628
620
|
}
|
|
629
|
-
const result = handler(handlerContext,
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
1784
|
-
entity[relationProperty.code] =
|
|
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 =
|
|
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] =
|
|
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,
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
1975
|
-
link.targetEntity =
|
|
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
|
-
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
3051
|
+
return lodash.mergeWith({}, defaultInput, input, fixedInput, doNotMergeArray);
|
|
3039
3052
|
}
|
|
3040
3053
|
function doNotMergeArray(objValue, srcValue) {
|
|
3041
|
-
if (
|
|
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 (!
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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;
|