contextMenuFactory更改了计算逻辑,要求显式传路径
This commit is contained in:
parent
7cbfeb2d84
commit
fad31698e7
|
|
@ -1,5 +1,5 @@
|
||||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
import { EntityDict, AuthCascadePath, Aspect } from 'oak-domain/lib/types';
|
import { EntityDict, Aspect, AuthCascadePath } from 'oak-domain/lib/types';
|
||||||
import { CommonAspectDict } from 'oak-common-aspect';
|
import { CommonAspectDict } from 'oak-common-aspect';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
|
|
@ -12,17 +12,14 @@ interface IMenu<ED extends EntityDict & BaseEntityDict, T extends keyof ED> {
|
||||||
action: ED[T]['Action'];
|
action: ED[T]['Action'];
|
||||||
paths?: string[];
|
paths?: string[];
|
||||||
}
|
}
|
||||||
interface IMenuWrapper<ED extends EntityDict & BaseEntityDict, T extends keyof ED> extends IMenu<ED, T> {
|
|
||||||
filtersMaker: (entity: keyof ED, entityId: string) => Array<ED[T]['Selection']['filter']>;
|
|
||||||
}
|
|
||||||
export declare class ContextMenuFactory<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends CommonAspectDict<ED, Cxt> & Record<string, Aspect<ED, Cxt>>> extends Feature {
|
export declare class ContextMenuFactory<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>, FrontCxt extends SyncContext<ED>, AD extends CommonAspectDict<ED, Cxt> & Record<string, Aspect<ED, Cxt>>> extends Feature {
|
||||||
cache: Cache<ED, Cxt, FrontCxt, AD>;
|
cache: Cache<ED, Cxt, FrontCxt, AD>;
|
||||||
menuWrappers?: IMenuWrapper<ED, keyof ED>[];
|
menus?: IMenu<ED, keyof ED>[];
|
||||||
cascadePathGraph: AuthCascadePath<ED>[];
|
cascadePathGraph: AuthCascadePath<ED>[];
|
||||||
relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>;
|
relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>;
|
||||||
private makeMenuWrappers;
|
|
||||||
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>, cascadePathGraph: AuthCascadePath<ED>[]);
|
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>, cascadePathGraph: AuthCascadePath<ED>[]);
|
||||||
setMenus(menus: IMenu<ED, keyof ED>[]): void;
|
setMenus(menus: IMenu<ED, keyof ED>[]): void;
|
||||||
|
makeMenuFilters(destEntity: keyof ED, paths: string[], entity: keyof ED, entityId: string): (true | ED[keyof ED]["Selection"]["filter"])[];
|
||||||
getMenusByContext<OMenu extends IMenu<ED, keyof ED>>(entity: keyof ED, entityId: string): OMenu[];
|
getMenusByContext<OMenu extends IMenu<ED, keyof ED>>(entity: keyof ED, entityId: string): OMenu[];
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
|
||||||
|
|
@ -17,86 +17,65 @@ var ContextMenuFactory = /** @class */ (function (_super) {
|
||||||
_this.relationAuth = relationAuth;
|
_this.relationAuth = relationAuth;
|
||||||
return _this;
|
return _this;
|
||||||
}
|
}
|
||||||
ContextMenuFactory.prototype.makeMenuWrappers = function (menus) {
|
ContextMenuFactory.prototype.setMenus = function (menus) {
|
||||||
var _this = this;
|
(0, assert_1.default)(!this.menus, 'setMenus只应该全局调用一次');
|
||||||
var destEntities = (0, lodash_1.uniq)(menus.map(function (ele) { return ele.entity; }));
|
this.menus = menus;
|
||||||
var pathMap = {};
|
};
|
||||||
this.cascadePathGraph.forEach(function (path) {
|
ContextMenuFactory.prototype.makeMenuFilters = function (destEntity, paths, entity, entityId) {
|
||||||
var _a;
|
var schema = this.cache.getSchema();
|
||||||
var _b = tslib_1.__read(path, 4), destEntity = _b[0], p = _b[1], s = _b[2], ir = _b[3];
|
(0, assert_1.default)(paths.length > 0);
|
||||||
if (ir && destEntities.includes(destEntity)) {
|
var filters = paths.map(function (path) {
|
||||||
// 应用在判定登录者身份时用的对象都是应用级对象,且以relation判定关系
|
if (path === '') {
|
||||||
if (pathMap[destEntity]) {
|
if (entity === destEntity) {
|
||||||
(_a = pathMap[destEntity]) === null || _a === void 0 ? void 0 : _a.push(path);
|
return {
|
||||||
|
id: entityId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pathhh = path.split('.');
|
||||||
|
var judgeIter = function (e2, idx) {
|
||||||
|
var rel = (0, relation_1.judgeRelation)(schema, e2, pathhh[idx]);
|
||||||
|
var e3 = e2;
|
||||||
|
if (typeof rel === 'string') {
|
||||||
|
e3 = rel;
|
||||||
|
}
|
||||||
|
else if (rel === 2) {
|
||||||
|
e3 = pathhh[idx];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pathMap[destEntity] = [path];
|
(0, assert_1.default)(rel instanceof Array);
|
||||||
|
e3 = rel[0];
|
||||||
}
|
}
|
||||||
}
|
if (idx === pathhh.length - 1) {
|
||||||
});
|
if (e3 === 'user') {
|
||||||
return menus.map(function (menu) {
|
// 用user连接说明一定满足
|
||||||
var destEntity = menu.entity, cascadePaths = menu.paths;
|
return true;
|
||||||
var filtersMaker = function (sourceEntity, entityId) {
|
|
||||||
// 在cascadePathMap中找到可能的路径并构建对应的filter
|
|
||||||
var paths = pathMap[destEntity].filter(function (ele) {
|
|
||||||
if (cascadePaths) {
|
|
||||||
(0, assert_1.default)(cascadePaths.length > 0);
|
|
||||||
return ele[2] === sourceEntity && cascadePaths.includes(ele[1]);
|
|
||||||
}
|
}
|
||||||
return ele[2] === sourceEntity;
|
if (e3 === entity) {
|
||||||
});
|
var filter = {};
|
||||||
return paths.map(function (path) {
|
return (0, lodash_1.set)(filter, "".concat(path, ".id"), entityId);
|
||||||
var p = path[1];
|
|
||||||
if (p === '') {
|
|
||||||
return {
|
|
||||||
id: entityId,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else {
|
return undefined;
|
||||||
var ps_1 = p.split('.');
|
}
|
||||||
var makeFilterInner_1 = function (entity, idx) {
|
return judgeIter(e3, idx + 1);
|
||||||
var _a, _b;
|
|
||||||
var attr = ps_1[idx];
|
|
||||||
var rel = (0, relation_1.judgeRelation)(_this.cache.getSchema(), entity, attr);
|
|
||||||
if (idx === ps_1.length - 1) {
|
|
||||||
if (rel === 2) {
|
|
||||||
return {
|
|
||||||
entity: attr,
|
|
||||||
entityId: entityId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
(0, assert_1.default)(typeof rel === 'string');
|
|
||||||
return _a = {},
|
|
||||||
_a["".concat(attr, "Id")] = entityId,
|
|
||||||
_a;
|
|
||||||
}
|
|
||||||
var e = rel === 2 ? attr : rel;
|
|
||||||
return _b = {},
|
|
||||||
_b[attr] = makeFilterInner_1(e, idx + 1),
|
|
||||||
_b;
|
|
||||||
};
|
|
||||||
return makeFilterInner_1(destEntity, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
return Object.assign({}, menu, {
|
return judgeIter(destEntity, 0);
|
||||||
filtersMaker: filtersMaker,
|
}).filter(function (ele) { return !!ele; });
|
||||||
});
|
return filters;
|
||||||
});
|
|
||||||
};
|
|
||||||
ContextMenuFactory.prototype.setMenus = function (menus) {
|
|
||||||
(0, assert_1.default)(!this.menuWrappers, 'setMenus只应该全局调用一次');
|
|
||||||
this.menuWrappers = this.makeMenuWrappers(menus);
|
|
||||||
};
|
};
|
||||||
ContextMenuFactory.prototype.getMenusByContext = function (entity, entityId) {
|
ContextMenuFactory.prototype.getMenusByContext = function (entity, entityId) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
(0, assert_1.default)(this.menuWrappers, '应当先调用setMenus才能动态判定菜单');
|
(0, assert_1.default)(this.menus, '应当先调用setMenus才能动态判定菜单');
|
||||||
var menus = this.menuWrappers.filter(function (wrapper) {
|
var menus = this.menus.filter(function (menu) {
|
||||||
var destEntity = wrapper.entity, filtersMaker = wrapper.filtersMaker, action = wrapper.action;
|
var destEntity = menu.entity, paths = menu.paths, action = menu.action;
|
||||||
var filters = filtersMaker(entity, entityId);
|
var filters = paths ? _this.makeMenuFilters(destEntity, paths, entity, entityId) : [{}]; // 如果没有path,视为无法推断操作的filter,直接返回无任何限制
|
||||||
if (filters.length > 0) {
|
if (filters.length > 0) {
|
||||||
// 这里应该是or关系,paths表达的路径中只要有一条满足就可能满足
|
// 这里应该是or关系,paths表达的路径中只要有一条满足就可能满足
|
||||||
var allows = filters.map(function (filter) {
|
var allows = filters.map(function (filter) {
|
||||||
|
if (filter === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// relationAuth和其它的checker现在分开判断
|
// relationAuth和其它的checker现在分开判断
|
||||||
return _this.relationAuth.checkRelation(destEntity, {
|
return _this.relationAuth.checkRelation(destEntity, {
|
||||||
action: action,
|
action: action,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { uniq, set, omit } from 'oak-domain/lib/utils/lodash';
|
import { uniq, set, omit } from 'oak-domain/lib/utils/lodash';
|
||||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
import { EntityDict, AuthCascadePath, Aspect } from 'oak-domain/lib/types';
|
import { EntityDict, Aspect, AuthCascadePath } from 'oak-domain/lib/types';
|
||||||
import { CommonAspectDict } from 'oak-common-aspect';
|
import { CommonAspectDict } from 'oak-common-aspect';
|
||||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
import { combineFilters } from 'oak-domain/lib/store/filter';
|
|
||||||
import { Cache } from './cache';
|
import { Cache } from './cache';
|
||||||
import { Feature } from '../types/Feature';
|
import { Feature } from '../types/Feature';
|
||||||
import { judgeRelation } from 'oak-domain/lib/store/relation';
|
import { judgeRelation } from 'oak-domain/lib/store/relation';
|
||||||
import { RelationAuth } from './relationAuth';
|
import { RelationAuth } from './relationAuth';
|
||||||
import { generateNewId } from 'oak-domain/lib/utils/uuid';
|
|
||||||
|
|
||||||
interface IMenu<ED extends EntityDict & BaseEntityDict, T extends keyof ED> {
|
interface IMenu<ED extends EntityDict & BaseEntityDict, T extends keyof ED> {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -30,91 +28,10 @@ export class ContextMenuFactory<
|
||||||
AD extends CommonAspectDict<ED, Cxt> & Record<string, Aspect<ED, Cxt>>
|
AD extends CommonAspectDict<ED, Cxt> & Record<string, Aspect<ED, Cxt>>
|
||||||
> extends Feature {
|
> extends Feature {
|
||||||
cache: Cache<ED, Cxt, FrontCxt, AD>;
|
cache: Cache<ED, Cxt, FrontCxt, AD>;
|
||||||
menuWrappers?: IMenuWrapper<ED, keyof ED>[];
|
menus?: IMenu<ED, keyof ED>[];
|
||||||
cascadePathGraph: AuthCascadePath<ED>[];
|
cascadePathGraph: AuthCascadePath<ED>[];
|
||||||
relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>;
|
relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>;
|
||||||
|
|
||||||
private makeMenuWrappers(menus: IMenu<ED, keyof ED>[]): IMenuWrapper<ED, keyof ED>[] {
|
|
||||||
const destEntities = uniq(
|
|
||||||
menus.map(
|
|
||||||
ele => ele.entity
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const pathMap: {
|
|
||||||
[E in keyof ED]?: AuthCascadePath<ED>[];
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
this.cascadePathGraph.forEach(
|
|
||||||
(path) => {
|
|
||||||
const [destEntity, p, s, ir] = path;
|
|
||||||
if (ir && destEntities.includes(destEntity)) {
|
|
||||||
// 应用在判定登录者身份时用的对象都是应用级对象,且以relation判定关系
|
|
||||||
if (pathMap[destEntity]) {
|
|
||||||
pathMap[destEntity]?.push(path);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
pathMap[destEntity] = [path];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return menus.map(
|
|
||||||
(menu) => {
|
|
||||||
const { entity: destEntity, paths: cascadePaths } = menu;
|
|
||||||
const filtersMaker = (sourceEntity: keyof ED, entityId: string) => {
|
|
||||||
// 在cascadePathMap中找到可能的路径并构建对应的filter
|
|
||||||
const paths = pathMap[destEntity]!.filter(
|
|
||||||
(ele) => {
|
|
||||||
if (cascadePaths) {
|
|
||||||
assert(cascadePaths.length > 0);
|
|
||||||
return ele[2] === sourceEntity && cascadePaths.includes(ele[1]);
|
|
||||||
}
|
|
||||||
return ele[2] === sourceEntity;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return paths.map(
|
|
||||||
(path) => {
|
|
||||||
const p = path[1];
|
|
||||||
if (p === '') {
|
|
||||||
return {
|
|
||||||
id: entityId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const ps = p.split('.');
|
|
||||||
const makeFilterInner = (entity: keyof ED, idx: number): ED[keyof ED]['Selection']['filter'] => {
|
|
||||||
const attr = ps[idx];
|
|
||||||
const rel = judgeRelation(this.cache.getSchema(), entity, attr);
|
|
||||||
if (idx === ps.length - 1) {
|
|
||||||
if (rel === 2) {
|
|
||||||
return {
|
|
||||||
entity: attr,
|
|
||||||
entityId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
assert(typeof rel === 'string');
|
|
||||||
return {
|
|
||||||
[`${attr}Id`]: entityId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const e = rel === 2 ? attr : rel as string;
|
|
||||||
return {
|
|
||||||
[attr]: makeFilterInner(e, idx + 1),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return makeFilterInner(destEntity, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
};
|
|
||||||
return Object.assign({}, menu, {
|
|
||||||
filtersMaker,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>, cascadePathGraph: AuthCascadePath<ED>[]) {
|
constructor(cache: Cache<ED, Cxt, FrontCxt, AD>, relationAuth: RelationAuth<ED, Cxt, FrontCxt, AD>, cascadePathGraph: AuthCascadePath<ED>[]) {
|
||||||
super();
|
super();
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
|
|
@ -123,20 +40,75 @@ export class ContextMenuFactory<
|
||||||
}
|
}
|
||||||
|
|
||||||
setMenus(menus: IMenu<ED, keyof ED>[]) {
|
setMenus(menus: IMenu<ED, keyof ED>[]) {
|
||||||
assert(!this.menuWrappers, 'setMenus只应该全局调用一次');
|
assert(!this.menus, 'setMenus只应该全局调用一次');
|
||||||
this.menuWrappers = this.makeMenuWrappers(menus);
|
this.menus = menus;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeMenuFilters(destEntity: keyof ED, paths: string[], entity: keyof ED, entityId: string) {
|
||||||
|
const schema = this.cache.getSchema();
|
||||||
|
assert(paths.length > 0);
|
||||||
|
|
||||||
|
const filters = paths.map(
|
||||||
|
(path) => {
|
||||||
|
if (path === '') {
|
||||||
|
if (entity === destEntity) {
|
||||||
|
return {
|
||||||
|
id: entityId,
|
||||||
|
} as ED[keyof ED]['Selection']['filter'];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pathhh = path.split('.');
|
||||||
|
|
||||||
|
const judgeIter = (e2: keyof ED, idx: number): true | undefined | ED[keyof ED]['Selection']['filter'] => {
|
||||||
|
const rel = judgeRelation(schema, e2, pathhh[idx]);
|
||||||
|
let e3 = e2;
|
||||||
|
if (typeof rel === 'string') {
|
||||||
|
e3 = rel;
|
||||||
|
}
|
||||||
|
else if (rel === 2) {
|
||||||
|
e3 = pathhh[idx];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(rel instanceof Array);
|
||||||
|
e3 = rel[0];
|
||||||
|
}
|
||||||
|
if (idx === pathhh.length - 1) {
|
||||||
|
if (e3 === 'user') {
|
||||||
|
// 用user连接说明一定满足
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (e3 === entity) {
|
||||||
|
const filter: ED[keyof ED]['Selection']['filter'] = {};
|
||||||
|
return set(filter, `${path}.id`, entityId);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return judgeIter(e3, idx + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return judgeIter(destEntity, 0);
|
||||||
|
}
|
||||||
|
).filter(
|
||||||
|
ele => !!ele
|
||||||
|
);
|
||||||
|
|
||||||
|
return filters as (true | ED[keyof ED]['Selection']['filter'])[];
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenusByContext<OMenu extends IMenu<ED, keyof ED>>(entity: keyof ED, entityId: string) {
|
getMenusByContext<OMenu extends IMenu<ED, keyof ED>>(entity: keyof ED, entityId: string) {
|
||||||
assert(this.menuWrappers, '应当先调用setMenus才能动态判定菜单');
|
assert(this.menus, '应当先调用setMenus才能动态判定菜单');
|
||||||
const menus = this.menuWrappers.filter(
|
const menus = this.menus.filter(
|
||||||
(wrapper) => {
|
(menu) => {
|
||||||
const { entity: destEntity, filtersMaker, action } = wrapper;
|
const { entity: destEntity, paths, action } = menu;
|
||||||
const filters = filtersMaker(entity, entityId);
|
const filters = paths ? this.makeMenuFilters(destEntity, paths, entity, entityId) : [{}]; // 如果没有path,视为无法推断操作的filter,直接返回无任何限制
|
||||||
if (filters.length > 0) {
|
if (filters.length > 0) {
|
||||||
// 这里应该是or关系,paths表达的路径中只要有一条满足就可能满足
|
// 这里应该是or关系,paths表达的路径中只要有一条满足就可能满足
|
||||||
const allows = filters.map(
|
const allows = filters.map(
|
||||||
(filter) => {
|
(filter) => {
|
||||||
|
if (filter === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// relationAuth和其它的checker现在分开判断
|
// relationAuth和其它的checker现在分开判断
|
||||||
return this.relationAuth.checkRelation(destEntity, {
|
return this.relationAuth.checkRelation(destEntity, {
|
||||||
action,
|
action,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue