relation的一堆代码
This commit is contained in:
parent
81440dd064
commit
5b749e94bd
|
|
@ -1,8 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.relations = exports.RelationCascadePathGraph = exports.ActionCascadePathGraph = void 0;
|
||||
exports.ActionCascadePathGraph = [
|
||||
["userRelation", "user", "userRelation", false]
|
||||
];
|
||||
exports.ActionCascadePathGraph = [];
|
||||
exports.RelationCascadePathGraph = [];
|
||||
exports.relations = [];
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export declare function registerIgnoredRelationPathSet(set: Record<string, string[]>): void;
|
||||
export declare function registerIgnoredForeignKeyMap(map: Record<string, string[]>): void;
|
||||
export declare function registerIgnoredRelationPathMap(map: Record<string, string[]>): void;
|
||||
export declare function registerDeducedRelationMap(map: Record<string, string>): void;
|
||||
export declare function analyzeEntities(inputDir: string, relativePath?: string): void;
|
||||
export declare function buildSchema(outputDir: string): void;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.buildSchema = exports.analyzeEntities = exports.registerIgnoredRelationPathSet = void 0;
|
||||
exports.buildSchema = exports.analyzeEntities = exports.registerDeducedRelationMap = exports.registerIgnoredRelationPathMap = exports.registerIgnoredForeignKeyMap = void 0;
|
||||
var tslib_1 = require("tslib");
|
||||
var path_1 = tslib_1.__importDefault(require("path"));
|
||||
var assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
|
|
@ -3445,35 +3445,68 @@ function analyzeInModi() {
|
|||
}
|
||||
}
|
||||
}
|
||||
var IGNORED_RELATION_PATH_SET = {};
|
||||
function registerIgnoredRelationPathSet(set) {
|
||||
IGNORED_RELATION_PATH_SET = set;
|
||||
var IGNORED_FOREIGN_KEY_MAP = {};
|
||||
var IGNORED_RELATION_PATH_MAP = {};
|
||||
var DEDUCED_RELATION_MAP = {};
|
||||
function registerIgnoredForeignKeyMap(map) {
|
||||
IGNORED_FOREIGN_KEY_MAP = map;
|
||||
}
|
||||
exports.registerIgnoredRelationPathSet = registerIgnoredRelationPathSet;
|
||||
exports.registerIgnoredForeignKeyMap = registerIgnoredForeignKeyMap;
|
||||
function registerIgnoredRelationPathMap(map) {
|
||||
for (var k in map) {
|
||||
IGNORED_RELATION_PATH_MAP[(0, string_1.firstLetterUpperCase)(k)] = map[k];
|
||||
}
|
||||
}
|
||||
exports.registerIgnoredRelationPathMap = registerIgnoredRelationPathMap;
|
||||
function registerDeducedRelationMap(map) {
|
||||
var _loop_12 = function (k) {
|
||||
var entity = (0, string_1.firstLetterUpperCase)(k);
|
||||
(0, assert_1.default)(Schema.hasOwnProperty(entity), "config/relation.ts\u4E2D\u914D\u7F6E\u7684DeducedRelationMap\u5305\u542B\u4E0D\u5408\u6CD5\u7684\u5BF9\u8C61\u540D\u79F0\u300C".concat(k, "\u300D"));
|
||||
// 定义的deduce的属性一定是多对一的外键,此时ReversePointerEntities还未处理
|
||||
if (ReversePointerEntities[entity] && map[k] === 'entity') {
|
||||
}
|
||||
else {
|
||||
var mto = ManyToOne[entity].find(function (ele) { return ele[1] === map[k]; });
|
||||
(0, assert_1.default)(mto, "config/relation.ts\u4E2D\u914D\u7F6E\u7684DeducedRelationMap\u6240\u5B9A\u4E49\u7684\u300C".concat(k, "\u300D\u7684deduce\u5C5E\u6027\u300C").concat(map[k], "\u300D\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684\u5916\u952E\u6307\u9488"));
|
||||
}
|
||||
DEDUCED_RELATION_MAP[entity] = map[k];
|
||||
};
|
||||
for (var k in map) {
|
||||
_loop_12(k);
|
||||
}
|
||||
}
|
||||
exports.registerDeducedRelationMap = registerDeducedRelationMap;
|
||||
/**
|
||||
* 输出所有和User相关的对象的后继
|
||||
*/
|
||||
function outputRelation(outputDir, printer) {
|
||||
var ExcludedEntities = ['Oper', 'User', 'OperEntity', 'Modi', 'ModiEntity', 'UserEntityGrant'];
|
||||
var ExcludedEntities = ['Oper', 'User', 'OperEntity', 'Modi', 'ModiEntity', 'UserRelation'];
|
||||
var actionPath = [];
|
||||
var relationPath = [];
|
||||
var outputRecursively = function (root, entity, path, paths, isRelation) {
|
||||
var _a;
|
||||
if (ExcludedEntities.includes(entity)) {
|
||||
return;
|
||||
}
|
||||
if (paths.length > 32) {
|
||||
if ((_a = IGNORED_RELATION_PATH_MAP[entity]) === null || _a === void 0 ? void 0 : _a.find(function (ele) { return path.includes(ele); })) {
|
||||
return;
|
||||
}
|
||||
if (paths.length > 12) {
|
||||
throw new Error('对象之间的关系深度过长,请优化设计加以避免');
|
||||
}
|
||||
actionPath.push([(0, string_1.firstLetterLowerCase)(entity), path, root, isRelation]);
|
||||
if (!DEDUCED_RELATION_MAP[entity]) {
|
||||
actionPath.push([(0, string_1.firstLetterLowerCase)(entity), path, root, isRelation]);
|
||||
}
|
||||
if (Schema[entity].hasRelationDef) {
|
||||
// assert(!DEDUCED_RELATION_MAP[entity], `${entity}对象定义了deducedRelationMap,但它有relation`);
|
||||
relationPath.push([(0, string_1.firstLetterLowerCase)(entity), path, root, isRelation]);
|
||||
}
|
||||
var _a = OneToMany, _b = entity, parent = _a[_b];
|
||||
var _b = OneToMany, _c = entity, parent = _b[_c];
|
||||
if (parent) {
|
||||
parent.forEach(function (_a) {
|
||||
var _b;
|
||||
var _c = tslib_1.__read(_a, 2), child = _c[0], foreignKey = _c[1];
|
||||
if (child !== entity && !paths.includes((0, string_1.firstLetterLowerCase)(child)) && !((_b = IGNORED_RELATION_PATH_SET[(0, string_1.firstLetterLowerCase)(child)]) === null || _b === void 0 ? void 0 : _b.includes(foreignKey))) {
|
||||
if (child !== entity && !paths.includes((0, string_1.firstLetterLowerCase)(child)) && !((_b = IGNORED_FOREIGN_KEY_MAP[(0, string_1.firstLetterLowerCase)(child)]) === null || _b === void 0 ? void 0 : _b.includes(foreignKey))) {
|
||||
// 如果有递归直接忽略,递归对象在设计时不要进入这个链条
|
||||
var fk = foreignKey === 'entity' ? (0, string_1.firstLetterLowerCase)(entity) : foreignKey;
|
||||
var path2 = path ? "".concat(fk, ".").concat(path) : fk;
|
||||
|
|
@ -3516,7 +3549,10 @@ function outputRelation(outputDir, printer) {
|
|||
}
|
||||
}
|
||||
var stmts = [
|
||||
factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("AuthCascadePath"))])), factory.createStringLiteral("".concat((0, env_1.TYPE_PATH_IN_OAK_DOMAIN)(1), "Entity")), undefined),
|
||||
factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([
|
||||
factory.createImportSpecifier(false, undefined, factory.createIdentifier("AuthCascadePath")),
|
||||
factory.createImportSpecifier(false, undefined, factory.createIdentifier("AuthDeduceRelationMap"))
|
||||
])), factory.createStringLiteral("".concat((0, env_1.TYPE_PATH_IN_OAK_DOMAIN)(1), "Entity")), undefined),
|
||||
factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, undefined, factory.createIdentifier("EntityDict"))])), factory.createStringLiteral("./EntityDict"), undefined),
|
||||
factory.createImportDeclaration(undefined, undefined, factory.createImportClause(false, undefined, factory.createNamedImports([factory.createImportSpecifier(false, factory.createIdentifier("CreateOperationData"), factory.createIdentifier("Relation"))])), factory.createStringLiteral("./Relation/Schema"), undefined),
|
||||
factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("ActionCascadePathGraph"), undefined, factory.createArrayTypeNode(factory.createTypeReferenceNode(factory.createIdentifier("AuthCascadePath"), [factory.createTypeReferenceNode(factory.createIdentifier("EntityDict"), undefined)])), factory.createArrayLiteralExpression(actionPath.map(function (_a) {
|
||||
|
|
@ -3546,6 +3582,13 @@ function outputRelation(outputDir, printer) {
|
|||
], true); });
|
||||
})), true))], ts.NodeFlags.Const))
|
||||
];
|
||||
if (Object.keys(DEDUCED_RELATION_MAP).length > 0) {
|
||||
stmts.push(factory.createVariableStatement([
|
||||
factory.createToken(ts.SyntaxKind.ExportKeyword)
|
||||
], factory.createVariableDeclarationList([
|
||||
factory.createVariableDeclaration(factory.createIdentifier("DeducedRelationMap"), undefined, factory.createTypeReferenceNode(factory.createIdentifier("AuthDeduceRelationMap"), [factory.createTypeReferenceNode(factory.createIdentifier("EntityDict"), undefined)]), factory.createObjectLiteralExpression(Object.keys(DEDUCED_RELATION_MAP).map(function (ele) { return factory.createPropertyAssignment(factory.createIdentifier((0, string_1.firstLetterLowerCase)(ele)), factory.createStringLiteral(DEDUCED_RELATION_MAP[ele])); }), true))
|
||||
], ts.NodeFlags.Const)));
|
||||
}
|
||||
var result = printer.printList(ts.ListFormat.SourceFileStatements, factory.createNodeArray(stmts), ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS));
|
||||
var filename = path_1.default.join(outputDir, 'Relation.ts');
|
||||
(0, fs_1.writeFileSync)(filename, result, { flag: 'w' });
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { EntityDict } from "../base-app-domain";
|
||||
import { StorageSchema, Trigger } from "../types";
|
||||
import { AuthCascadePath, EntityDict as BaseEntityDict } from "../types/Entity";
|
||||
import { AuthCascadePath, EntityDict as BaseEntityDict, AuthDeduceRelationMap } from "../types/Entity";
|
||||
import { AsyncContext } from "./AsyncRowStore";
|
||||
import { SyncContext } from "./SyncRowStore";
|
||||
export declare class RelationAuth<ED extends EntityDict & BaseEntityDict> {
|
||||
private actionCascadePathGraph;
|
||||
private relationCascadePathGraph;
|
||||
private authDeduceRelationMap;
|
||||
private schema;
|
||||
private relationalFilterMaker;
|
||||
private relationalCreateChecker;
|
||||
private directActionAuthMap;
|
||||
private freeActionAuthMap;
|
||||
private constructFilterMaker;
|
||||
constructor(actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], schema: StorageSchema<ED>);
|
||||
constructor(schema: StorageSchema<ED>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>);
|
||||
private makeDirectionActionAuthMap;
|
||||
setDirectionActionAuths(directActionAuths: ED['directActionAuth']['OpSchema'][]): void;
|
||||
setFreeActionAuths(freeActionAuths: ED['freeActionAuth']['OpSchema'][]): void;
|
||||
|
|
@ -20,6 +21,14 @@ export declare class RelationAuth<ED extends EntityDict & BaseEntityDict> {
|
|||
private upsertDirectActionAuth;
|
||||
private removeDirectActionAuth;
|
||||
checkRelationSync<T extends keyof ED, Cxt extends SyncContext<ED>>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt): void;
|
||||
private checkActionAsync;
|
||||
/**
|
||||
* 在entity上执行Operation,等同于在其path路径的父对象上执行相关的action操作,进行relation判定
|
||||
* @param entity
|
||||
* @param operation
|
||||
* @param context
|
||||
*/
|
||||
private checkCascadeActionAsync;
|
||||
checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt): Promise<void>;
|
||||
/**
|
||||
* 后台需要注册数据变化的监听器,以保证缓存的维度数据准确
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ var AsyncRowStore_1 = require("./AsyncRowStore");
|
|||
var filter_1 = require("./filter");
|
||||
var relation_1 = require("./relation");
|
||||
var RelationAuth = /** @class */ (function () {
|
||||
function RelationAuth(actionCascadePathGraph, relationCascadePathGraph, schema) {
|
||||
function RelationAuth(schema, actionCascadePathGraph, relationCascadePathGraph, authDeduceRelationMap) {
|
||||
this.directActionAuthMap = {};
|
||||
this.actionCascadePathGraph = actionCascadePathGraph;
|
||||
this.relationCascadePathGraph = relationCascadePathGraph;
|
||||
this.schema = schema;
|
||||
this.relationalFilterMaker = {};
|
||||
this.relationalCreateChecker = {};
|
||||
this.authDeduceRelationMap = authDeduceRelationMap;
|
||||
this.constructFilterMaker();
|
||||
}
|
||||
RelationAuth.prototype.constructFilterMaker = function () {
|
||||
|
|
@ -112,6 +113,9 @@ var RelationAuth = /** @class */ (function () {
|
|||
};
|
||||
};
|
||||
var _loop_1 = function (entity) {
|
||||
/* if (pathGroup[entity]!.length > 6) {
|
||||
throw new Error(`${entity as string}上的actionPath数量大于6,请优化}`);
|
||||
} */
|
||||
var filterMakers = pathGroup[entity].map(function (ele) {
|
||||
var _a = tslib_1.__read(ele, 4), e = _a[0], p = _a[1], r = _a[2], ir = _a[3]; // entity, path, root, isRelation
|
||||
var daKey = "".concat(e, "-").concat(p, "-").concat(r);
|
||||
|
|
@ -149,30 +153,10 @@ var RelationAuth = /** @class */ (function () {
|
|||
return function (userId, directActionAuth, data, filter) {
|
||||
if (data) {
|
||||
var id_1 = data.id;
|
||||
return function (context) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
||||
var actionAuths, assignPossibleRelation;
|
||||
return tslib_1.__generator(this, function (_a) {
|
||||
actionAuths = context.select('actionAuth', {
|
||||
data: {
|
||||
id: 1,
|
||||
relationId: 1,
|
||||
destEntity: 1,
|
||||
relation: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
path: '',
|
||||
deActions: {
|
||||
$contains: 'create',
|
||||
},
|
||||
relation: {
|
||||
entity: e,
|
||||
},
|
||||
},
|
||||
}, {});
|
||||
assignPossibleRelation = function (aas) {
|
||||
return function (context) {
|
||||
// 只对后台需要创建,前台直接返回
|
||||
if (context instanceof AsyncRowStore_1.AsyncContext) {
|
||||
var assignPossibleRelation_1 = function (aas) {
|
||||
if (aas.length > 0) {
|
||||
(0, assert_1.default)(aas.length === 1, "\u5728".concat(e, "\u4E0A\u7684\u81EA\u8EAB\u5173\u7CFB\u4E0A\u5B9A\u4E49\u4E86\u8D85\u8FC7\u4E00\u79CDcreate\u7684\u6743\u9650\uFF0C\u300C").concat(aas.map(function (ele) { return ele.relation.name; }).join(','), "\u300D"));
|
||||
var relationId = aas[0].relationId;
|
||||
|
|
@ -191,12 +175,29 @@ var RelationAuth = /** @class */ (function () {
|
|||
}
|
||||
return false;
|
||||
};
|
||||
if (actionAuths instanceof Promise) {
|
||||
return [2 /*return*/, actionAuths.then(function (aas) { return assignPossibleRelation(aas); })];
|
||||
}
|
||||
return [2 /*return*/, assignPossibleRelation(actionAuths)];
|
||||
});
|
||||
}); };
|
||||
return context.select('actionAuth', {
|
||||
data: {
|
||||
id: 1,
|
||||
relationId: 1,
|
||||
destEntity: 1,
|
||||
relation: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
path: '',
|
||||
deActions: {
|
||||
$contains: 'create',
|
||||
},
|
||||
relation: {
|
||||
entity: e,
|
||||
},
|
||||
},
|
||||
}, {}).then(function (actionAuths) { return assignPossibleRelation_1(actionAuths); }).then(function () { return true; });
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
return function () { return true; };
|
||||
};
|
||||
|
|
@ -205,20 +206,25 @@ var RelationAuth = /** @class */ (function () {
|
|||
// 同样是直接关联在本对象上,在create的时候直接赋予userId
|
||||
var rel_1 = (0, relation_1.judgeRelation)(_this.schema, e, paths[0]);
|
||||
return function (userId, directActionAuth, data, filter) {
|
||||
var _a;
|
||||
if (data) {
|
||||
if (rel_1 === 2) {
|
||||
Object.assign(data, {
|
||||
entity: 'user',
|
||||
entityId: userId,
|
||||
});
|
||||
}
|
||||
else {
|
||||
(0, assert_1.default)(typeof rel_1 === 'string');
|
||||
Object.assign(data, (_a = {},
|
||||
_a["".concat(paths[0], "Id")] = userId,
|
||||
_a));
|
||||
}
|
||||
return function (context) {
|
||||
var _a;
|
||||
if (context instanceof AsyncRowStore_1.AsyncContext) {
|
||||
if (rel_1 === 2) {
|
||||
Object.assign(data, {
|
||||
entity: 'user',
|
||||
entityId: userId,
|
||||
});
|
||||
}
|
||||
else {
|
||||
(0, assert_1.default)(typeof rel_1 === 'string');
|
||||
Object.assign(data, (_a = {},
|
||||
_a["".concat(paths[0], "Id")] = userId,
|
||||
_a));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
return function () { return true; };
|
||||
};
|
||||
|
|
@ -271,11 +277,11 @@ var RelationAuth = /** @class */ (function () {
|
|||
// 其它情况都是检查其data或者filter中的外键指向是否满足relation约束关系
|
||||
return function (userId, directActionAuthMap, data, filter) {
|
||||
if (!filter && !data) {
|
||||
return function () { return false; };
|
||||
return false;
|
||||
}
|
||||
var result = translateFilterToSelect(e, (filter || data), 0, userId, directActionAuthMap);
|
||||
if (!result) {
|
||||
return function () { return false; };
|
||||
return false;
|
||||
}
|
||||
return function (context) {
|
||||
var entity = result.entity, filter = result.filter, relationalFilter = result.relationalFilter;
|
||||
|
|
@ -284,7 +290,10 @@ var RelationAuth = /** @class */ (function () {
|
|||
};
|
||||
});
|
||||
this_1.relationalCreateChecker[entity] = function (userId, directActionAuthMap, data, filter) {
|
||||
var callbacks = createCheckers.map(function (ele) { return ele(userId, directActionAuthMap, data, filter); });
|
||||
var callbacks = createCheckers.map(function (ele) { return ele(userId, directActionAuthMap, data, filter); }).filter(function (ele) { return typeof ele === 'function'; });
|
||||
if (callbacks.length > 6) {
|
||||
throw new types_1.OakDataException("\u5728create\u300C".concat(entity, "\u300D\u65F6relation\u76F8\u5173\u7684\u6743\u9650\u68C0\u67E5\u8FC7\u591A\uFF0C\u8BF7\u4F18\u5316actionAuth\u7684\u8DEF\u5F84"));
|
||||
}
|
||||
return function (context) {
|
||||
var result = callbacks.map(function (ele) { return ele(context); });
|
||||
// 回调中只要有一个通过就算过
|
||||
|
|
@ -432,14 +441,74 @@ var RelationAuth = /** @class */ (function () {
|
|||
}
|
||||
throw new types_1.OakUnloggedInException();
|
||||
};
|
||||
RelationAuth.prototype.checkActionAsync = function (entity, operation, context) {
|
||||
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
||||
var action, userId, _a, data, filter, callback, filter, operationFilter;
|
||||
var _this = this;
|
||||
return tslib_1.__generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
action = operation.action || 'select';
|
||||
userId = context.getCurrentUserId();
|
||||
if (!(action === 'create' && this.relationalCreateChecker[entity])) return [3 /*break*/, 5];
|
||||
_a = operation, data = _a.data, filter = _a.filter;
|
||||
if (!(data instanceof Array)) return [3 /*break*/, 2];
|
||||
return [4 /*yield*/, Promise.all(data.map(function (ele) {
|
||||
var callback = _this.relationalCreateChecker[entity](userId, _this.directActionAuthMap, ele);
|
||||
return callback(context);
|
||||
}))];
|
||||
case 1:
|
||||
_b.sent();
|
||||
return [3 /*break*/, 4];
|
||||
case 2:
|
||||
(0, assert_1.default)(data);
|
||||
callback = this.relationalCreateChecker[entity](userId, this.directActionAuthMap, data);
|
||||
return [4 /*yield*/, callback(context)];
|
||||
case 3:
|
||||
_b.sent();
|
||||
_b.label = 4;
|
||||
case 4: return [3 /*break*/, 7];
|
||||
case 5:
|
||||
if (!(action !== 'create' && this.relationalFilterMaker[entity])) return [3 /*break*/, 7];
|
||||
filter = this.relationalFilterMaker[entity](action, userId, this.directActionAuthMap);
|
||||
operationFilter = operation.filter;
|
||||
(0, assert_1.default)(filter, "\u5728\u68C0\u67E5".concat(entity, "\u4E0A\u6267\u884C").concat(action, "\u64CD\u4F5C\u65F6\u6CA1\u6709\u4F20\u5165filter"));
|
||||
return [4 /*yield*/, (0, filter_1.checkFilterContains)(entity, context, filter, operationFilter, true)];
|
||||
case 6:
|
||||
if (_b.sent()) {
|
||||
return [2 /*return*/];
|
||||
}
|
||||
throw new types_1.OakUserUnpermittedException("\u5F53\u524D\u7528\u6237\u4E0D\u5141\u8BB8\u5728".concat(entity, "\u4E0A\u6267\u884C").concat(action, "\u64CD\u4F5C"));
|
||||
case 7: throw new types_1.OakUnloggedInException();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 在entity上执行Operation,等同于在其path路径的父对象上执行相关的action操作,进行relation判定
|
||||
* @param entity
|
||||
* @param operation
|
||||
* @param context
|
||||
*/
|
||||
RelationAuth.prototype.checkCascadeActionAsync = function (entity, operation, path, action, context) {
|
||||
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
||||
var childData, childFilter, childAction, paths;
|
||||
return tslib_1.__generator(this, function (_a) {
|
||||
childData = operation.data, childFilter = operation.filter;
|
||||
childAction = operation.action || 'select';
|
||||
(0, assert_1.default)(path);
|
||||
paths = path.split('.');
|
||||
return [2 /*return*/];
|
||||
});
|
||||
});
|
||||
};
|
||||
// 后台检查filter是否满足relation约束
|
||||
RelationAuth.prototype.checkRelationAsync = function (entity, operation, context) {
|
||||
var _a;
|
||||
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
||||
var action, userId, _b, data, filter, callback, callback, filter, operationFilter;
|
||||
var _this = this;
|
||||
return tslib_1.__generator(this, function (_c) {
|
||||
switch (_c.label) {
|
||||
var action, userId;
|
||||
return tslib_1.__generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
if (context.isRoot()) {
|
||||
return [2 /*return*/];
|
||||
|
|
@ -453,43 +522,26 @@ var RelationAuth = /** @class */ (function () {
|
|||
if (!userId) {
|
||||
throw new types_1.OakNoRelationDefException(entity, action);
|
||||
}
|
||||
if (!(action === 'create' && this.relationalCreateChecker[entity])) return [3 /*break*/, 7];
|
||||
_b = operation, data = _b.data, filter = _b.filter;
|
||||
if (!filter) return [3 /*break*/, 2];
|
||||
callback = this.relationalCreateChecker[entity](userId, this.directActionAuthMap, undefined, filter);
|
||||
return [4 /*yield*/, callback(context)];
|
||||
// 对compile中放过的几个特殊meta对象的处理
|
||||
/* switch (entity as string) {
|
||||
case 'modi': {
|
||||
if (action === 'select') {
|
||||
return this.checkActionAsync()
|
||||
}
|
||||
}
|
||||
} */
|
||||
return [4 /*yield*/, this.checkActionAsync(entity, operation, context)];
|
||||
case 1:
|
||||
_c.sent();
|
||||
return [3 /*break*/, 6];
|
||||
case 2:
|
||||
if (!(data instanceof Array)) return [3 /*break*/, 4];
|
||||
return [4 /*yield*/, Promise.all(data.map(function (ele) {
|
||||
var callback = _this.relationalCreateChecker[entity](userId, _this.directActionAuthMap, ele);
|
||||
return callback(context);
|
||||
}))];
|
||||
case 3:
|
||||
_c.sent();
|
||||
return [3 /*break*/, 6];
|
||||
case 4:
|
||||
(0, assert_1.default)(data);
|
||||
callback = this.relationalCreateChecker[entity](userId, this.directActionAuthMap, data);
|
||||
return [4 /*yield*/, callback(context)];
|
||||
case 5:
|
||||
_c.sent();
|
||||
_c.label = 6;
|
||||
case 6: return [3 /*break*/, 9];
|
||||
case 7:
|
||||
if (!(action !== 'create' && this.relationalFilterMaker[entity])) return [3 /*break*/, 9];
|
||||
filter = this.relationalFilterMaker[entity](action, userId, this.directActionAuthMap);
|
||||
operationFilter = operation.filter;
|
||||
(0, assert_1.default)(filter, "\u5728\u68C0\u67E5".concat(entity, "\u4E0A\u6267\u884C").concat(action, "\u64CD\u4F5C\u65F6\u6CA1\u6709\u4F20\u5165filter"));
|
||||
return [4 /*yield*/, (0, filter_1.checkFilterContains)(entity, context, filter, operationFilter, true)];
|
||||
case 8:
|
||||
if (_c.sent()) {
|
||||
return [2 /*return*/];
|
||||
}
|
||||
throw new types_1.OakUserUnpermittedException("\u5F53\u524D\u7528\u6237\u4E0D\u5141\u8BB8\u5728".concat(entity, "\u4E0A\u6267\u884C").concat(action, "\u64CD\u4F5C"));
|
||||
case 9: throw new types_1.OakUnloggedInException();
|
||||
// 对compile中放过的几个特殊meta对象的处理
|
||||
/* switch (entity as string) {
|
||||
case 'modi': {
|
||||
if (action === 'select') {
|
||||
return this.checkActionAsync()
|
||||
}
|
||||
}
|
||||
} */
|
||||
_b.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -211,6 +211,73 @@ function createModiRelatedTriggers(schema) {
|
|||
for (var entity in schema) {
|
||||
_loop_2(entity);
|
||||
}
|
||||
return triggers;
|
||||
// modi被应用时的效用,搬到这里了
|
||||
var applyTrigger = {
|
||||
name: '当modi被应用时,将相应的operate完成',
|
||||
entity: 'modi',
|
||||
action: 'apply',
|
||||
when: 'after',
|
||||
fn: function (_a, context, option) {
|
||||
var operation = _a.operation;
|
||||
return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
||||
var filter, modies, modies_1, modies_1_1, modi, targetEntity, id, action, data, filter_1, e_1_1;
|
||||
var e_1, _b;
|
||||
return tslib_1.__generator(this, function (_c) {
|
||||
switch (_c.label) {
|
||||
case 0:
|
||||
filter = operation.filter;
|
||||
return [4 /*yield*/, context.select('modi', {
|
||||
data: {
|
||||
id: 1,
|
||||
action: 1,
|
||||
data: 1,
|
||||
filter: 1,
|
||||
targetEntity: 1,
|
||||
},
|
||||
filter: filter,
|
||||
}, option)];
|
||||
case 1:
|
||||
modies = _c.sent();
|
||||
_c.label = 2;
|
||||
case 2:
|
||||
_c.trys.push([2, 7, 8, 9]);
|
||||
modies_1 = tslib_1.__values(modies), modies_1_1 = modies_1.next();
|
||||
_c.label = 3;
|
||||
case 3:
|
||||
if (!!modies_1_1.done) return [3 /*break*/, 6];
|
||||
modi = modies_1_1.value;
|
||||
targetEntity = modi.targetEntity, id = modi.id, action = modi.action, data = modi.data, filter_1 = modi.filter;
|
||||
return [4 /*yield*/, context.operate(targetEntity, {
|
||||
id: id,
|
||||
action: action,
|
||||
data: data,
|
||||
filter: filter_1,
|
||||
}, Object.assign({}, option, {
|
||||
blockTrigger: true,
|
||||
}))];
|
||||
case 4:
|
||||
_c.sent();
|
||||
_c.label = 5;
|
||||
case 5:
|
||||
modies_1_1 = modies_1.next();
|
||||
return [3 /*break*/, 3];
|
||||
case 6: return [3 /*break*/, 9];
|
||||
case 7:
|
||||
e_1_1 = _c.sent();
|
||||
e_1 = { error: e_1_1 };
|
||||
return [3 /*break*/, 9];
|
||||
case 8:
|
||||
try {
|
||||
if (modies_1_1 && !modies_1_1.done && (_b = modies_1.return)) _b.call(modies_1);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
return [7 /*endfinally*/];
|
||||
case 9: return [2 /*return*/, modies.length];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
return triggers.concat([applyTrigger]);
|
||||
}
|
||||
exports.createModiRelatedTriggers = createModiRelatedTriggers;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { EntityDict as BaseEntityDict } from '../base-app-domain';
|
||||
import { StorageSchema, EntityDict } from '../types';
|
||||
import { AsyncContext } from '../store/AsyncRowStore';
|
||||
declare const _default: import("../types").Trigger<BaseEntityDict, "modi", AsyncContext<BaseEntityDict>>[];
|
||||
export default _default;
|
||||
export declare function createDynamicTriggers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>>(schema: StorageSchema<ED>): import("../types").Trigger<ED, keyof ED, Cxt>[];
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createDynamicTriggers = void 0;
|
||||
var tslib_1 = require("tslib");
|
||||
var modi_1 = tslib_1.__importDefault(require("./modi"));
|
||||
var modi_2 = require("../store/modi");
|
||||
exports.default = tslib_1.__spreadArray([], tslib_1.__read(modi_1.default), false);
|
||||
var modi_1 = require("../store/modi");
|
||||
function createDynamicTriggers(schema) {
|
||||
return (0, modi_2.createModiRelatedTriggers)(schema);
|
||||
return (0, modi_1.createModiRelatedTriggers)(schema);
|
||||
}
|
||||
exports.createDynamicTriggers = createDynamicTriggers;
|
||||
|
|
|
|||
|
|
@ -182,4 +182,7 @@ export declare type Configuration = {
|
|||
static?: boolean;
|
||||
};
|
||||
export declare type AuthCascadePath<ED extends EntityDict> = [keyof ED, string, keyof ED, boolean];
|
||||
export declare type AuthDeduceRelationMap<ED extends EntityDict> = {
|
||||
[T in keyof ED]?: keyof ED[T]['OpSchema'];
|
||||
};
|
||||
export {};
|
||||
|
|
|
|||
|
|
@ -1998,7 +1998,7 @@ function constructProjection(statements: Array<ts.Statement>, entity: string) {
|
|||
}
|
||||
}
|
||||
else {
|
||||
if (!enumAttributes || !enumAttributes[attrName]) {
|
||||
if (!enumAttributes || !enumAttributes[attrName]) {
|
||||
// 引用的非enum类型shape
|
||||
properties.push(
|
||||
[name, false, factory.createUnionTypeNode([
|
||||
|
|
@ -4743,8 +4743,8 @@ function outputSubQuery(outputDir: string, printer: ts.Printer) {
|
|||
/* .filter(
|
||||
([e, f]) => f !== 'entity'
|
||||
) */.map(
|
||||
([e]) => e
|
||||
)) : [];
|
||||
([e]) => e
|
||||
)) : [];
|
||||
fromEntites.push(one);
|
||||
|
||||
const inUnionTypeNode: ts.TypeNode[] = fromEntites.map(
|
||||
|
|
@ -6012,7 +6012,7 @@ function outputStorage(outputDir: string, printer: ts.Printer) {
|
|||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
propertyAssignments.push(
|
||||
factory.createShorthandPropertyAssignment(
|
||||
factory.createIdentifier("actions"),
|
||||
|
|
@ -6262,34 +6262,72 @@ function analyzeInModi() {
|
|||
}
|
||||
|
||||
|
||||
let IGNORED_RELATION_PATH_SET: Record<string, string[]> = {}
|
||||
let IGNORED_FOREIGN_KEY_MAP: Record<string, string[]> = {};
|
||||
let IGNORED_RELATION_PATH_MAP: Record<string, string[]> = {};
|
||||
let DEDUCED_RELATION_MAP: Record<string, string> = {};
|
||||
|
||||
export function registerIgnoredRelationPathSet(set: Record<string, string[]>) {
|
||||
IGNORED_RELATION_PATH_SET = set;
|
||||
export function registerIgnoredForeignKeyMap(map: Record<string, string[]>) {
|
||||
IGNORED_FOREIGN_KEY_MAP = map;
|
||||
}
|
||||
|
||||
export function registerIgnoredRelationPathMap(map: Record<string, string[]>) {
|
||||
for (const k in map) {
|
||||
IGNORED_RELATION_PATH_MAP[firstLetterUpperCase(k)] = map[k];
|
||||
}
|
||||
}
|
||||
|
||||
export function registerDeducedRelationMap(map: Record<string, string>) {
|
||||
for (const k in map) {
|
||||
const entity = firstLetterUpperCase(k);
|
||||
assert(Schema.hasOwnProperty(entity), `config/relation.ts中配置的DeducedRelationMap包含不合法的对象名称「${k}」`);
|
||||
// 定义的deduce的属性一定是多对一的外键,此时ReversePointerEntities还未处理
|
||||
if (ReversePointerEntities[entity] && map[k] === 'entity') {
|
||||
|
||||
}
|
||||
else {
|
||||
const mto = ManyToOne[entity].find(
|
||||
ele => ele[1] === map[k]
|
||||
);
|
||||
assert(mto, `config/relation.ts中配置的DeducedRelationMap所定义的「${k}」的deduce属性「${map[k]}」不是一个有效的外键指针`);
|
||||
}
|
||||
|
||||
DEDUCED_RELATION_MAP[entity] = map[k];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出所有和User相关的对象的后继
|
||||
*/
|
||||
function outputRelation(outputDir: string, printer: ts.Printer) {
|
||||
const ExcludedEntities = ['Oper', 'User', 'OperEntity', 'Modi', 'ModiEntity', 'UserEntityGrant'];
|
||||
const ExcludedEntities = ['Oper', 'User', 'OperEntity', 'Modi', 'ModiEntity', 'UserRelation'];
|
||||
const actionPath: [string, string, string, boolean][] = [];
|
||||
const relationPath: [string, string, string, boolean][] = [];
|
||||
const outputRecursively = (root: string, entity: string, path: string, paths: string[], isRelation: boolean) => {
|
||||
if (ExcludedEntities.includes(entity)) {
|
||||
return;
|
||||
}
|
||||
if (paths.length > 32) {
|
||||
|
||||
if (IGNORED_RELATION_PATH_MAP[entity]?.find(
|
||||
(ele) => path.includes(ele)
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (paths.length > 12) {
|
||||
throw new Error('对象之间的关系深度过长,请优化设计加以避免');
|
||||
}
|
||||
actionPath.push([firstLetterLowerCase(entity), path, root, isRelation]);
|
||||
if (!DEDUCED_RELATION_MAP[entity]) {
|
||||
actionPath.push([firstLetterLowerCase(entity), path, root, isRelation]);
|
||||
}
|
||||
if (Schema[entity].hasRelationDef) {
|
||||
// assert(!DEDUCED_RELATION_MAP[entity], `${entity}对象定义了deducedRelationMap,但它有relation`);
|
||||
relationPath.push([firstLetterLowerCase(entity), path, root, isRelation]);
|
||||
}
|
||||
const { [entity]: parent } = OneToMany;
|
||||
if (parent) {
|
||||
parent.forEach(
|
||||
([child, foreignKey]) => {
|
||||
if (child !== entity && !paths.includes(firstLetterLowerCase(child)) && !IGNORED_RELATION_PATH_SET[firstLetterLowerCase(child)]?.includes(foreignKey)) {
|
||||
if (child !== entity && !paths.includes(firstLetterLowerCase(child)) && !IGNORED_FOREIGN_KEY_MAP[firstLetterLowerCase(child)]?.includes(foreignKey)) {
|
||||
// 如果有递归直接忽略,递归对象在设计时不要进入这个链条
|
||||
const fk = foreignKey === 'entity' ? firstLetterLowerCase(entity) : foreignKey;
|
||||
const path2 = path ? `${fk}.${path}` : fk;
|
||||
|
|
@ -6348,11 +6386,18 @@ function outputRelation(outputDir: string, printer: ts.Printer) {
|
|||
factory.createImportClause(
|
||||
false,
|
||||
undefined,
|
||||
factory.createNamedImports([factory.createImportSpecifier(
|
||||
false,
|
||||
undefined,
|
||||
factory.createIdentifier("AuthCascadePath")
|
||||
)])
|
||||
factory.createNamedImports([
|
||||
factory.createImportSpecifier(
|
||||
false,
|
||||
undefined,
|
||||
factory.createIdentifier("AuthCascadePath")
|
||||
),
|
||||
factory.createImportSpecifier(
|
||||
false,
|
||||
undefined,
|
||||
factory.createIdentifier("AuthDeduceRelationMap")
|
||||
)
|
||||
])
|
||||
),
|
||||
factory.createStringLiteral(`${TYPE_PATH_IN_OAK_DOMAIN(1)}Entity`),
|
||||
undefined
|
||||
|
|
@ -6489,6 +6534,40 @@ function outputRelation(outputDir: string, printer: ts.Printer) {
|
|||
)
|
||||
];
|
||||
|
||||
if (Object.keys(DEDUCED_RELATION_MAP).length > 0) {
|
||||
stmts.push(
|
||||
factory.createVariableStatement(
|
||||
[
|
||||
factory.createToken(ts.SyntaxKind.ExportKeyword)
|
||||
],
|
||||
factory.createVariableDeclarationList(
|
||||
[
|
||||
factory.createVariableDeclaration(
|
||||
factory.createIdentifier("DeducedRelationMap"),
|
||||
undefined,
|
||||
factory.createTypeReferenceNode(
|
||||
factory.createIdentifier("AuthDeduceRelationMap"),
|
||||
[factory.createTypeReferenceNode(
|
||||
factory.createIdentifier("EntityDict"),
|
||||
undefined
|
||||
)]
|
||||
),
|
||||
factory.createObjectLiteralExpression(
|
||||
Object.keys(DEDUCED_RELATION_MAP).map(
|
||||
ele => factory.createPropertyAssignment(
|
||||
factory.createIdentifier(firstLetterLowerCase(ele)),
|
||||
factory.createStringLiteral(DEDUCED_RELATION_MAP[ele])
|
||||
)
|
||||
),
|
||||
true
|
||||
)
|
||||
)
|
||||
],
|
||||
ts.NodeFlags.Const
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const result = printer.printList(
|
||||
ts.ListFormat.SourceFileStatements,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import assert from "assert";
|
||||
import { EntityDict } from "../base-app-domain";
|
||||
import { CreateTrigger, OakNoRelationDefException, OakUnloggedInException, OakUserUnpermittedException, RemoveTrigger, StorageSchema, Trigger, UpdateTrigger } from "../types";
|
||||
import { AuthCascadePath, EntityDict as BaseEntityDict } from "../types/Entity";
|
||||
import { CreateTrigger, OakDataException, OakNoRelationDefException, OakUnloggedInException, OakUserUnpermittedException, RemoveTrigger, StorageSchema, Trigger, UpdateTrigger } from "../types";
|
||||
import { AuthCascadePath, EntityDict as BaseEntityDict, AuthDeduceRelationMap } from "../types/Entity";
|
||||
import { AsyncContext } from "./AsyncRowStore";
|
||||
import { checkFilterContains } from "./filter";
|
||||
import { judgeRelation } from "./relation";
|
||||
|
|
@ -10,6 +10,7 @@ import { SyncContext } from "./SyncRowStore";
|
|||
export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
||||
private actionCascadePathGraph: AuthCascadePath<ED>[];
|
||||
private relationCascadePathGraph: AuthCascadePath<ED>[];
|
||||
private authDeduceRelationMap: AuthDeduceRelationMap<ED>;
|
||||
private schema: StorageSchema<ED>;
|
||||
private relationalFilterMaker: {
|
||||
[T in keyof ED]?: (action: ED[T]['Action'] | 'select', userId: string, directActionAuthMap: Record<string, string[]>) => ED[T]['Selection']['filter'];
|
||||
|
|
@ -134,6 +135,9 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
};
|
||||
|
||||
for (const entity in pathGroup) {
|
||||
/* if (pathGroup[entity]!.length > 6) {
|
||||
throw new Error(`${entity as string}上的actionPath数量大于6,请优化}`);
|
||||
} */
|
||||
const filterMakers = pathGroup[entity]!.map(
|
||||
(ele) => {
|
||||
const [e, p, r, ir] = ele; // entity, path, root, isRelation
|
||||
|
|
@ -180,7 +184,7 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
directActionAuth: Record<string, string[]>,
|
||||
data?: ED[T]['CreateSingle']['data'],
|
||||
filter?: ED[T]['Selection']['filter']
|
||||
) => <Cxt extends AsyncContext<ED> | SyncContext<ED>>(context: Cxt) => boolean | Promise<boolean>)[] = pathGroup[entity]!.map(
|
||||
) => (<Cxt extends AsyncContext<ED> | SyncContext<ED>>(context: Cxt) => boolean | Promise<boolean>) | false)[] = pathGroup[entity]!.map(
|
||||
(ele) => {
|
||||
const [e, p, r, ir] = ele; // entity, path, root, isRelation
|
||||
const daKey = `${e as string}-${p}-${r as string}`;
|
||||
|
|
@ -197,57 +201,56 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
) => {
|
||||
if (data) {
|
||||
const { id } = data;
|
||||
return async (context: AsyncContext<ED> | SyncContext<ED>) => {
|
||||
const actionAuths = context.select('actionAuth', {
|
||||
data: {
|
||||
id: 1,
|
||||
relationId: 1,
|
||||
destEntity: 1,
|
||||
relation: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
path: '',
|
||||
deActions: {
|
||||
$contains: 'create',
|
||||
},
|
||||
relation: {
|
||||
entity: e as string,
|
||||
},
|
||||
},
|
||||
}, {});
|
||||
|
||||
const assignPossibleRelation = (aas: ED['actionAuth']['Schema'][]) => {
|
||||
if (aas.length > 0) {
|
||||
assert(aas.length === 1, `在${e as string}上的自身关系上定义了超过一种create的权限,「${aas.map(ele => ele.relation!.name).join(',')}」`);
|
||||
const { relationId } = aas[0];
|
||||
Object.assign(data, {
|
||||
userRelation$entity: {
|
||||
action: 'create',
|
||||
data: {
|
||||
entity: e as string,
|
||||
entityId: id,
|
||||
relationId,
|
||||
userId,
|
||||
return (context: AsyncContext<ED> | SyncContext<ED>) => {
|
||||
// 只对后台需要创建,前台直接返回
|
||||
if (context instanceof AsyncContext) {
|
||||
const assignPossibleRelation = (aas: ED['actionAuth']['Schema'][]) => {
|
||||
if (aas.length > 0) {
|
||||
assert(aas.length === 1, `在${e as string}上的自身关系上定义了超过一种create的权限,「${aas.map(ele => ele.relation!.name).join(',')}」`);
|
||||
const { relationId } = aas[0];
|
||||
Object.assign(data, {
|
||||
userRelation$entity: {
|
||||
action: 'create',
|
||||
data: {
|
||||
entity: e as string,
|
||||
entityId: id,
|
||||
relationId,
|
||||
userId,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (actionAuths instanceof Promise) {
|
||||
return actionAuths.then(
|
||||
(aas) => assignPossibleRelation(aas as ED['actionAuth']['Schema'][])
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return context.select('actionAuth', {
|
||||
data: {
|
||||
id: 1,
|
||||
relationId: 1,
|
||||
destEntity: 1,
|
||||
relation: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
path: '',
|
||||
deActions: {
|
||||
$contains: 'create',
|
||||
},
|
||||
relation: {
|
||||
entity: e as string,
|
||||
},
|
||||
},
|
||||
}, {}).then(
|
||||
(actionAuths) => assignPossibleRelation(actionAuths as ED['actionAuth']['Schema'][])
|
||||
).then(
|
||||
() => true
|
||||
);
|
||||
}
|
||||
return assignPossibleRelation(actionAuths as ED['actionAuth']['Schema'][]);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
return () => true;
|
||||
};
|
||||
}
|
||||
|
|
@ -261,18 +264,23 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
filter?: ED[T]['Selection']['filter']
|
||||
) => {
|
||||
if (data) {
|
||||
if (rel === 2) {
|
||||
Object.assign(data, {
|
||||
entity: 'user',
|
||||
entityId: userId,
|
||||
});
|
||||
}
|
||||
else {
|
||||
assert(typeof rel === 'string');
|
||||
Object.assign(data, {
|
||||
[`${paths[0]}Id`]: userId,
|
||||
})
|
||||
}
|
||||
return (context) => {
|
||||
if (context instanceof AsyncContext) {
|
||||
if (rel === 2) {
|
||||
Object.assign(data, {
|
||||
entity: 'user',
|
||||
entityId: userId,
|
||||
});
|
||||
}
|
||||
else {
|
||||
assert(typeof rel === 'string');
|
||||
Object.assign(data, {
|
||||
[`${paths[0]}Id`]: userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
return () => true;
|
||||
};
|
||||
|
|
@ -342,11 +350,11 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
filter?: ED[T]['Selection']['filter']
|
||||
) => {
|
||||
if (!filter && !data) {
|
||||
return () => false;
|
||||
return false;
|
||||
}
|
||||
const result = translateFilterToSelect(e, (filter || data)!, 0, userId, directActionAuthMap);
|
||||
if (!result) {
|
||||
return () => false;
|
||||
return false;
|
||||
}
|
||||
return (context) => {
|
||||
const { entity, filter, relationalFilter } = result;
|
||||
|
|
@ -364,7 +372,14 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
) => {
|
||||
const callbacks = createCheckers.map(
|
||||
ele => ele(userId, directActionAuthMap, data, filter)
|
||||
);
|
||||
).filter(
|
||||
ele => typeof ele === 'function'
|
||||
) as (<Cxt extends AsyncContext<ED> | SyncContext<ED>>(context: Cxt) => boolean | Promise<boolean>)[];
|
||||
|
||||
if (callbacks.length > 6) {
|
||||
throw new OakDataException(`在create「${entity}」时relation相关的权限检查过多,请优化actionAuth的路径`);
|
||||
}
|
||||
|
||||
return (context) => {
|
||||
const result = callbacks.map(
|
||||
ele => ele(context)
|
||||
|
|
@ -390,12 +405,13 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
}
|
||||
}
|
||||
|
||||
constructor(actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], schema: StorageSchema<ED>) {
|
||||
constructor(schema: StorageSchema<ED>, actionCascadePathGraph: AuthCascadePath<ED>[], relationCascadePathGraph: AuthCascadePath<ED>[], authDeduceRelationMap: AuthDeduceRelationMap<ED>) {
|
||||
this.actionCascadePathGraph = actionCascadePathGraph;
|
||||
this.relationCascadePathGraph = relationCascadePathGraph;
|
||||
this.schema = schema;
|
||||
this.relationalFilterMaker = {};
|
||||
this.relationalCreateChecker = {};
|
||||
this.authDeduceRelationMap = authDeduceRelationMap;
|
||||
this.constructFilterMaker();
|
||||
}
|
||||
|
||||
|
|
@ -431,7 +447,7 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
const k = `$${destEntity}-${path}-${sourceEntity}`;
|
||||
this.directActionAuthMap[k] = deActions;
|
||||
}
|
||||
|
||||
|
||||
private removeDirectActionAuth(directActionAuth: ED['directActionAuth']['OpSchema']) {
|
||||
const { deActions, destEntity, sourceEntity, path } = directActionAuth;
|
||||
const k = `$${destEntity}-${path}-${sourceEntity}`;
|
||||
|
|
@ -514,32 +530,13 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
throw new OakUnloggedInException();
|
||||
}
|
||||
|
||||
// 后台检查filter是否满足relation约束
|
||||
async checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) {
|
||||
if (context.isRoot()) {
|
||||
return;
|
||||
}
|
||||
private async checkActionAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) {
|
||||
const action = (operation as ED[T]['Operation']).action || 'select';
|
||||
|
||||
// 后台用缓存的faa来判定,减少对数据库的查询(freeActionAuth表很少更新)
|
||||
if (!this.freeActionAuthMap || this.freeActionAuthMap[entity as string]?.includes(action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = context.getCurrentUserId();
|
||||
if (!userId) {
|
||||
throw new OakNoRelationDefException<ED, T>(entity, action);
|
||||
}
|
||||
|
||||
// 后台用缓存的daa来判定,减少对数据库的查询(freeActionAuth表很少更新)
|
||||
const userId = context.getCurrentUserId()!;
|
||||
if (action === 'create' && this.relationalCreateChecker[entity]) {
|
||||
const { data, filter } = operation as ED[T]['Create'];
|
||||
if (filter) {
|
||||
// 如果create传了filter, 前台保证create一定满足此约束,优先判断
|
||||
const callback = this.relationalCreateChecker[entity]!(userId, this.directActionAuthMap, undefined, filter);
|
||||
await callback(context);
|
||||
}
|
||||
else if (data instanceof Array) {
|
||||
// 后台不用判断filter吧
|
||||
if (data instanceof Array) {
|
||||
await Promise.all(
|
||||
data.map(
|
||||
(ele) => {
|
||||
|
|
@ -567,6 +564,55 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
throw new OakUnloggedInException();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在entity上执行Operation,等同于在其path路径的父对象上执行相关的action操作,进行relation判定
|
||||
* @param entity
|
||||
* @param operation
|
||||
* @param context
|
||||
*/
|
||||
private async checkCascadeActionAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(
|
||||
entity: T,
|
||||
operation: ED[T]['Operation'] | ED[T]['Selection'],
|
||||
path: string,
|
||||
action: string,
|
||||
context: Cxt
|
||||
) {
|
||||
const { data: childData, filter: childFilter } = operation;
|
||||
const childAction = (operation as ED[T]['Operation']).action || 'select';
|
||||
assert(path);
|
||||
const paths = path.split('.');
|
||||
|
||||
}
|
||||
|
||||
// 后台检查filter是否满足relation约束
|
||||
async checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: ED[T]['Operation'] | ED[T]['Selection'], context: Cxt) {
|
||||
if (context.isRoot()) {
|
||||
return;
|
||||
}
|
||||
const action = (operation as ED[T]['Operation']).action || 'select';
|
||||
|
||||
// 后台用缓存的faa来判定,减少对数据库的查询(freeActionAuth表很少更新)
|
||||
if (!this.freeActionAuthMap || this.freeActionAuthMap[entity as string]?.includes(action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = context.getCurrentUserId();
|
||||
if (!userId) {
|
||||
throw new OakNoRelationDefException<ED, T>(entity, action);
|
||||
}
|
||||
|
||||
// 对compile中放过的几个特殊meta对象的处理
|
||||
/* switch (entity as string) {
|
||||
case 'modi': {
|
||||
if (action === 'select') {
|
||||
return this.checkActionAsync()
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
await this.checkActionAsync(entity, operation, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台需要注册数据变化的监听器,以保证缓存的维度数据准确
|
||||
* 在集群上要支持跨结点的监听器(todo)
|
||||
|
|
@ -597,7 +643,7 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
action: 'update',
|
||||
when: 'commit',
|
||||
name: 'freeActionAuth更新时,刷新relationAuth中的缓存数据',
|
||||
fn: async({ operation }, context) => {
|
||||
fn: async ({ operation }, context) => {
|
||||
const { data, filter } = operation;
|
||||
assert(typeof filter!.id === 'string'); // freeAuthDict不应当有其它更新的情况
|
||||
assert(!data.destEntity);
|
||||
|
|
@ -623,7 +669,7 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
action: 'remove',
|
||||
when: 'commit',
|
||||
name: 'freeActionAuth删除时,刷新relationAuth中的缓存数据',
|
||||
fn: async({ operation }, context) => {
|
||||
fn: async ({ operation }, context) => {
|
||||
const { data, filter } = operation;
|
||||
assert(typeof filter!.id === 'string'); // freeActionAuth不应当有其它更新的情况
|
||||
const faas = await context.select('freeActionAuth', {
|
||||
|
|
@ -667,7 +713,7 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
action: 'update',
|
||||
when: 'commit',
|
||||
name: 'directActionAuth更新时,刷新relationAuth中的缓存数据',
|
||||
fn: async({ operation }, context) => {
|
||||
fn: async ({ operation }, context) => {
|
||||
const { data, filter } = operation;
|
||||
assert(typeof filter!.id === 'string'); // freeAuthDict不应当有其它更新的情况
|
||||
assert(!data.destEntity && !data.sourceEntity && !data.path);
|
||||
|
|
@ -694,7 +740,7 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
action: 'remove',
|
||||
when: 'commit',
|
||||
name: 'directActionAuth删除时,刷新relationAuth中的缓存数据',
|
||||
fn: async({ operation }, context) => {
|
||||
fn: async ({ operation }, context) => {
|
||||
const { data, filter } = operation;
|
||||
assert(typeof filter!.id === 'string'); // directActionAuth不应当有其它更新的情况
|
||||
const daas = await context.select('directActionAuth', {
|
||||
|
|
@ -715,4 +761,4 @@ export class RelationAuth<ED extends EntityDict & BaseEntityDict>{
|
|||
} as RemoveTrigger<ED, 'directActionAuth', Cxt>,
|
||||
] as Trigger<ED, keyof ED, Cxt>[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import { EntityDict } from '../types/Entity';
|
||||
import { EntityDict as BaseEntityDict } from '../base-app-domain';
|
||||
import { StorageSchema } from '../types/Storage';
|
||||
import { AuthDefDict, RelationChecker } from '../types/Auth';
|
||||
import { AsyncContext } from './AsyncRowStore';
|
||||
import { SyncContext } from './SyncRowStore';
|
||||
|
||||
export default class ArAuth<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED> | SyncContext<ED>> {
|
||||
constructor(schema: StorageSchema<ED>, authDict: AuthDefDict<ED>) {
|
||||
|
||||
}
|
||||
|
||||
getRelationalCheckers(): RelationChecker<ED, keyof ED, Cxt>[] {
|
||||
throw new Error('method not implemented');
|
||||
}
|
||||
|
||||
addRelation(relation: ED['relation']['Schema']) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -187,6 +187,41 @@ export function createModiRelatedTriggers<ED extends EntityDict & BaseEntityDict
|
|||
}
|
||||
}
|
||||
|
||||
return triggers;
|
||||
// modi被应用时的效用,搬到这里了
|
||||
const applyTrigger: Trigger<EntityDict, 'modi', AsyncContext<EntityDict>> = {
|
||||
name: '当modi被应用时,将相应的operate完成',
|
||||
entity: 'modi',
|
||||
action: 'apply',
|
||||
when: 'after',
|
||||
fn: async ({ operation }, context, option) => {
|
||||
const { filter } = operation;
|
||||
const modies = await context.select('modi', {
|
||||
data: {
|
||||
id: 1,
|
||||
action: 1,
|
||||
data: 1,
|
||||
filter: 1,
|
||||
targetEntity: 1,
|
||||
},
|
||||
filter,
|
||||
}, option);
|
||||
|
||||
for (const modi of modies) {
|
||||
const { targetEntity, id, action, data, filter } = modi;
|
||||
await context.operate(targetEntity as keyof EntityDict, {
|
||||
id: id!,
|
||||
action: action as EntityDict[keyof EntityDict]['Action'],
|
||||
data: data as EntityDict[keyof EntityDict]['Update']['data'],
|
||||
filter: filter as EntityDict[keyof EntityDict]['Update']['filter'],
|
||||
}, Object.assign({}, option, {
|
||||
blockTrigger: true,
|
||||
}));
|
||||
}
|
||||
|
||||
return modies.length;
|
||||
}
|
||||
};
|
||||
|
||||
return triggers.concat([applyTrigger as Trigger<ED, keyof ED, Cxt>]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import { EntityDict as BaseEntityDict } from '../base-app-domain';
|
||||
import { StorageSchema, EntityDict } from '../types';
|
||||
import modiTriggers from './modi';
|
||||
import { createModiRelatedTriggers } from '../store/modi';
|
||||
import { AsyncContext } from '../store/AsyncRowStore';
|
||||
|
||||
export default [...modiTriggers];
|
||||
|
||||
export function createDynamicTriggers<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>>(schema: StorageSchema<ED>) {
|
||||
return createModiRelatedTriggers<ED, Cxt>(schema);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
import { EntityDict } from "../base-app-domain";
|
||||
import { AsyncContext } from "../store/AsyncRowStore";
|
||||
import { Trigger, UpdateTrigger } from "../types";
|
||||
|
||||
const triggers: Trigger<EntityDict, 'modi', AsyncContext<EntityDict>>[] = [
|
||||
{
|
||||
name: '当modi被应用时,将相应的operate完成',
|
||||
entity: 'modi',
|
||||
action: 'apply',
|
||||
when: 'after',
|
||||
fn: async ({ operation }, context, option) => {
|
||||
const { filter } = operation;
|
||||
const modies = await context.select('modi', {
|
||||
data: {
|
||||
id: 1,
|
||||
action: 1,
|
||||
data: 1,
|
||||
filter: 1,
|
||||
targetEntity: 1,
|
||||
},
|
||||
filter,
|
||||
}, option);
|
||||
|
||||
for (const modi of modies) {
|
||||
const { targetEntity, id, action, data, filter} = modi;
|
||||
await context.operate(targetEntity as keyof EntityDict, {
|
||||
id: id!,
|
||||
action: action as EntityDict[keyof EntityDict]['Action'],
|
||||
data: data as EntityDict[keyof EntityDict]['Update']['data'],
|
||||
filter: filter as EntityDict[keyof EntityDict]['Update']['filter'],
|
||||
}, Object.assign({}, option, {
|
||||
blockTrigger: true,
|
||||
}));
|
||||
}
|
||||
|
||||
return modies.length;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default triggers;
|
||||
|
|
@ -259,3 +259,6 @@ export type Configuration = {
|
|||
};
|
||||
|
||||
export type AuthCascadePath<ED extends EntityDict> = [keyof ED, string, keyof ED, boolean];
|
||||
export type AuthDeduceRelationMap<ED extends EntityDict> = {
|
||||
[T in keyof ED]?: keyof ED[T]['OpSchema'];
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue