diff --git a/packages/app/src/api/migrations/init.ts b/packages/app/src/api/migrations/init.ts
index 5753f856a..e4ada9195 100644
--- a/packages/app/src/api/migrations/init.ts
+++ b/packages/app/src/api/migrations/init.ts
@@ -1,3 +1,11 @@
+// @ts-ignore
+global.sync = {
+ force: true,
+ alter: {
+ drop: true,
+ },
+};
+
import api from '../app';
import Database from '@nocobase/database';
@@ -135,6 +143,9 @@ const data = [
const tables = database.getTables([]);
for (let table of tables) {
console.log(table.getName());
+ if (table.getName() === 'roles') {
+ console.log('roles', table.getOptions())
+ }
await Collection.import(table.getOptions(), { update: true, migrate: false });
}
await Page.import(data);
@@ -182,6 +193,7 @@ const data = [
const roles = await Role.bulkCreate([
{ title: '系统开发组', type: -1 },
{ title: '匿名用户组', type: 0 },
+ { title: '普通用户组', default: true },
]);
await roles[0].updateAssociations({
users: user
diff --git a/packages/app/src/components/views/SortableTable/index.tsx b/packages/app/src/components/views/SortableTable/index.tsx
index 477efa26c..4aa4dffdd 100644
--- a/packages/app/src/components/views/SortableTable/index.tsx
+++ b/packages/app/src/components/views/SortableTable/index.tsx
@@ -8,6 +8,8 @@ import get from 'lodash/get';
import './style.less';
import Field from '../Field';
import cloneDeep from 'lodash/cloneDeep';
+import { Checkbox, message } from 'antd';
+import api from '@/api-client';
export const SortableItem = sortableElement(props =>
);
export const SortableContainer = sortableContainer(props => );
@@ -58,11 +60,38 @@ export const components = ({data = {}, rowKey, mutate, onMoved, isFieldComponent
};
};
-export function fields2columns(fields) {
+export function fields2columns(fields, ctx: any = {}) {
const columns: any[] = fields.map(item => {
const field = cloneDeep(item);
field.render = (value, record) => field.interface === 'sort' ? : ;
field.className = `${field.className||''} noco-field-${field.interface}`;
+ if (field.editable && field.interface === 'boolean') {
+ field.title = (
+
+ {
+ try {
+ await api.resource(field.resource).update({
+ associatedKey: ctx.associatedKey,
+ // resourceKey: data.id,
+ // tableName: data.tableName||'pages',
+ values: {
+ accessible: e.target.checked,
+ },
+ });
+ message.success('保存成功');
+ if (ctx.refresh) {
+ ctx.refresh();
+ }
+ } catch (error) {
+ message.error('保存失败');
+ }
+
+ }}/>
+ {' '}
+ {field.title}
+
+ );
+ }
return {
...field,
...(field.component||{}),
diff --git a/packages/app/src/components/views/Table.tsx b/packages/app/src/components/views/Table.tsx
index b6895f0db..1a3cab4be 100644
--- a/packages/app/src/components/views/Table.tsx
+++ b/packages/app/src/components/views/Table.tsx
@@ -154,7 +154,7 @@ export function Table(props: TableProps) {
indicator: icon,
// className: 'spinning--absolute m32',
}}
- columns={fields2columns(fields)}
+ columns={fields2columns(fields, {associatedKey, refresh})}
dataSource={data?.list||(data as any)}
onChange={(pagination, filters, sorter, extra) => {
run({...params[0], sorter});
diff --git a/packages/database/src/database.ts b/packages/database/src/database.ts
index ace0c5919..dcf166189 100644
--- a/packages/database/src/database.ts
+++ b/packages/database/src/database.ts
@@ -68,6 +68,8 @@ export default class Database {
protected hooks = {};
+ protected extTableOptions = new Map();
+
constructor(options: DatabaseOptions) {
this.options = options;
this.sequelize = new Sequelize(options);
@@ -94,10 +96,21 @@ export default class Database {
files.forEach((file: string) => {
const result = requireModule(file);
if (result instanceof Extend) {
- const table = this.extend(result.tableOptions, result.mergeOptions);
- tables.set(table.getName(), table);
+ // 如果还没初始化,extend 的先暂存起来,后续处理
+ if (!this.tables.has(result.tableOptions.name)) {
+ this.extTableOptions.set(result.tableOptions.name, result);
+ } else {
+ const table = this.extend(result.tableOptions, result.mergeOptions);
+ tables.set(table.getName(), table);
+ }
} else {
- const table = this.extend(typeof result === 'function' ? result(this) : result);
+ let table = this.extend(typeof result === 'function' ? result(this) : result);
+ // 如果有未处理的 extend 取回来合并
+ if (this.extTableOptions.has(table.getName())) {
+ const result = this.extTableOptions.get(table.getName());
+ table = this.extend(result.tableOptions, result.mergeOptions);
+ this.extTableOptions.delete(table.getName());
+ }
tables.set(table.getName(), table);
}
});
diff --git a/packages/plugin-collections/src/hooks/fields-before-validate.ts b/packages/plugin-collections/src/hooks/fields-before-validate.ts
index d745aaff0..03742cca7 100644
--- a/packages/plugin-collections/src/hooks/fields-before-validate.ts
+++ b/packages/plugin-collections/src/hooks/fields-before-validate.ts
@@ -18,7 +18,7 @@ export default async function (model: FieldModel, options) {
const dataSource = model.get('dataSource');
if (Array.isArray(dataSource)) {
model.set('dataSource', dataSource.map(item => {
- if (!item.value) {
+ if (item.value === null || typeof item.value === 'undefined') {
item.value = generateValueName();
}
return {...item};
diff --git a/packages/plugin-pages/src/actions/getRoutes.ts b/packages/plugin-pages/src/actions/getRoutes.ts
index 258a66224..48544c024 100644
--- a/packages/plugin-pages/src/actions/getRoutes.ts
+++ b/packages/plugin-pages/src/actions/getRoutes.ts
@@ -40,6 +40,21 @@ export default async function getRoutes(ctx, next) {
const database: Database = ctx.database;
const Page = database.getModel('pages');
const Collection = database.getModel('collections');
+ const RoutePermission = database.getModel('routes_permissions');
+ const roles = await ctx.ac.getRoles();
+ // TODO(optimize): isRoot 的判断需要在内部完成,尽量不要交给调用者
+ const isRoot = ctx.ac.constructor.isRoot(roles);
+ const routesPermissionsMap = new Map();
+ if (!isRoot) {
+ const routesPermissions = await RoutePermission.findAll({
+ where: {
+ role_id: roles.map(({ id }) => id)
+ }
+ });
+ routesPermissions.forEach(permission => {
+ routesPermissionsMap.set(`${permission.routable_type}:${permission.routable_id}`, permission);
+ });
+ }
let pages = await Page.findAll(Page.parseApiJson(ctx.state.developerMode ? {
filter: {
},
@@ -52,6 +67,15 @@ export default async function getRoutes(ctx, next) {
}));
const items = [];
for (const page of pages) {
+ if (!isRoot
+ && !routesPermissionsMap.has(`pages:${page.id}`)
+ // 以下路径先临时处理
+ && page.get('path') !== '/'
+ && page.get('path') !== '/register'
+ && page.get('path') !== '/login'
+ ) {
+ continue;
+ }
items.push(page.toJSON());
if (page.get('path') === '/collections') {
const collections = await Collection.findAll(Collection.parseApiJson(ctx.state.developerMode ? {
@@ -67,6 +91,9 @@ export default async function getRoutes(ctx, next) {
sort: ['sort'],
}));
for (const collection of collections) {
+ if (!isRoot && !routesPermissionsMap.has(`collections:${collection.id}`)) {
+ continue;
+ }
const pageId = `collection-${collection.id}`;
items.push({
id: pageId,
@@ -90,6 +117,9 @@ export default async function getRoutes(ctx, next) {
});
if (views.length > 1) {
for (const view of views) {
+ if (!isRoot && !routesPermissionsMap.has(`views:${view.id}`)) {
+ continue;
+ }
items.push({
id: `view-${view.get('id')}`,
type: 'collection',
diff --git a/packages/plugin-pages/src/actions/getView.ts b/packages/plugin-pages/src/actions/getView.ts
index 16a0c5c7a..3612ce9d9 100644
--- a/packages/plugin-pages/src/actions/getView.ts
+++ b/packages/plugin-pages/src/actions/getView.ts
@@ -201,7 +201,7 @@ export default async (ctx, next) => {
}));
let throughName;
const { resourceKey: resourceKey2, associatedName, resourceFieldName, associatedKey } = values;
- const permissions = await ctx.can(resourceName).permissions();
+ const permissions = await ctx.ac.can(resourceName).permissions();
ctx.listFields = [];
ctx.createFields = [];
ctx.updateFields = [];
@@ -209,7 +209,7 @@ export default async (ctx, next) => {
for (const action of permissions.actions) {
ctx.allowedActions.push(action.name);
}
- console.log(ctx.allowedActions);
+ // console.log(ctx.allowedActions);
for (const permissionField of permissions.fields) {
const pfc = permissionField.actions;
if (pfc.includes(`${resourceName}:list`)) {
@@ -222,11 +222,11 @@ export default async (ctx, next) => {
ctx.updateFields.push(permissionField.field_id);
}
}
- console.log({
- listFields: ctx.listFields,
- createFields: ctx.createFields,
- updateFields: ctx.updateFields,
- })
+ // console.log({
+ // listFields: ctx.listFields,
+ // createFields: ctx.createFields,
+ // updateFields: ctx.updateFields,
+ // })
if (associatedName) {
const table = ctx.db.getTable(associatedName);
const resourceField = table.getField(resourceFieldName);
@@ -420,6 +420,23 @@ export default async (ctx, next) => {
"showInDetail": true
},
"dataIndex": ["title"]
+ },
+ {
+ "title": "描述",
+ "name": "permissions[0].description",
+ "interface": "string",
+ "type": "string",
+ "parent_id": null,
+ "required": true,
+ "developerMode": false,
+ "component": {
+ "type": "string",
+ "className": "drag-visible",
+ "showInForm": true,
+ "showInTable": true,
+ "showInDetail": true
+ },
+ "dataIndex": ["permissions", 0, 'description']
}
],
};
@@ -496,7 +513,7 @@ export default async (ctx, next) => {
};
} else {
let allowedUpdate = false;
- if (view.type === 'details' && await ctx.can(resourceName).act('update').one(resourceKey2)) {
+ if (view.type === 'details' && await ctx.ac.can(resourceName).act('update').one(resourceKey2)) {
allowedUpdate = true;
}
ctx.body = {
diff --git a/packages/plugin-pages/src/actions/roles.pages.ts b/packages/plugin-pages/src/actions/roles.pages.ts
index 06b32e1d7..84c3486bd 100644
--- a/packages/plugin-pages/src/actions/roles.pages.ts
+++ b/packages/plugin-pages/src/actions/roles.pages.ts
@@ -3,9 +3,8 @@ import Database from '@nocobase/database';
import { flatToTree } from '../utils';
import { Op } from 'sequelize';
-export async function list(ctx: actions.Context, next: actions.Next) {
+async function getRoutes(ctx) {
const database: Database = ctx.db;
- const { associatedKey } = ctx.action.params;
const Page = database.getModel('pages');
const Collection = database.getModel('collections');
let pages = await Page.findAll(Page.parseApiJson(ctx.state.developerMode ? {
@@ -21,6 +20,80 @@ export async function list(ctx: actions.Context, next: actions.Next) {
sort: ['sort'],
}));
const items = [];
+ for (const page of pages) {
+ items.push({
+ routable_type: 'pages',
+ routable_id: page.id,
+ });
+ if (page.get('path') === '/collections') {
+ const collections = await Collection.findAll(Collection.parseApiJson(ctx.state.developerMode ? {
+ filter: {
+ showInDataMenu: true,
+ },
+ sort: ['sort'],
+ }: {
+ filter: {
+ developerMode: {'$isFalsy': true},
+ showInDataMenu: true,
+ },
+ sort: ['sort'],
+ }));
+ for (const collection of collections) {
+ items.push({
+ routable_type: 'collections',
+ routable_id: collection.id,
+ });
+ const views = await collection.getViews({
+ where: {
+ [Op.or]: [
+ { showInDataMenu: true },
+ { default: true }
+ ]
+ },
+ order: [['sort', 'asc']]
+ });
+ if (views.length > 1) {
+ for (const view of views) {
+ items.push({
+ routable_id: view.id,
+ routable_type: 'views',
+ });
+ }
+ }
+ }
+ }
+ }
+ return items;
+}
+
+export async function list(ctx: actions.Context, next: actions.Next) {
+ const database: Database = ctx.db;
+ const { associatedKey, associated } = ctx.action.params;
+ const Page = database.getModel('pages');
+ const Collection = database.getModel('collections');
+ // TODO(optimize): isRoot 的判断需要在内部完成,尽量不要交给调用者
+ const isRoot = ctx.ac.constructor.isRoot(associated);
+ const routesPermissionsMap = new Map();
+ if (!isRoot) {
+ const routesPermissions = await associated.getRoutes();
+
+ routesPermissions.forEach(permission => {
+ routesPermissionsMap.set(`${permission.routable_type}:${permission.routable_id}`, permission);
+ });
+ }
+ let pages = await Page.findAll(Page.parseApiJson(ctx.state.developerMode ? {
+ filter: {
+ 'parent_id.$notNull': true,
+ },
+ sort: ['sort'],
+ } : {
+ filter: {
+ 'parent_id.$notNull': true,
+ developerMode: {'$isFalsy': true},
+ },
+ sort: ['sort'],
+ }));
+ const items = [];
for (const page of pages) {
items.push({
id: page.id,
@@ -29,7 +102,7 @@ export async function list(ctx: actions.Context, next: actions.Next) {
tableName: 'pages',
parent_id: `page-${page.parent_id}`,
associatedKey,
- accessible: false, // TODO 对接权限
+ accessible: isRoot || routesPermissionsMap.has(`pages:${page.id}`), // TODO 对接权限
});
if (page.get('path') === '/collections') {
const collections = await Collection.findAll(Collection.parseApiJson(ctx.state.developerMode ? {
@@ -52,7 +125,7 @@ export async function list(ctx: actions.Context, next: actions.Next) {
tableName: 'collections',
title: collection.get('title'),
parent_id: `page-${page.id}`,
- accessible: false, // TODO 对接权限
+ accessible: isRoot || routesPermissionsMap.has(`collections:${collection.id}`), // TODO 对接权限
});
const views = await collection.getViews({
where: {
@@ -72,7 +145,7 @@ export async function list(ctx: actions.Context, next: actions.Next) {
title: view.title,
key: `view-${view.id}`,
parent_id: `collection-${collection.id}`,
- accessible: false, // TODO 对接权限
+ accessible: isRoot || routesPermissionsMap.has(`views:${view.id}`), // TODO 对接权限
});
}
}
@@ -106,6 +179,46 @@ export async function list(ctx: actions.Context, next: actions.Next) {
}
export async function update(ctx: actions.Context, next: actions.Next) {
- ctx.body = {};
+ const {
+ associated,
+ resourceKey,
+ values: {
+ tableName,
+ accessible
+ }
+ } = ctx.action.params;
+
+ if (!resourceKey) {
+ if (accessible === false) {
+ await associated.updateAssociations({
+ routes: [],
+ });
+ } else if (accessible === true) {
+ const routes = await getRoutes(ctx);
+ // console.log(routes);
+ await associated.updateAssociations({
+ routes,
+ });
+ }
+ ctx.body = {};
+ return next();
+ }
+
+ console.log(ctx.action.params, { routable_type: tableName, routable_id: resourceKey });
+ let [route] = await associated.getRoutes({
+ where: { routable_type: tableName, routable_id: resourceKey },
+ limit: 1
+ });
+ if (accessible) {
+ if (!route) {
+ route = await associated.createRoute({ routable_type: tableName, routable_id: resourceKey });
+ }
+ ctx.body = route;
+ } else {
+ if (route) {
+ await route.destroy();
+ }
+ }
+
await next();
}
diff --git a/packages/plugin-pages/src/collections/pages.ts b/packages/plugin-pages/src/collections/pages.ts
index 0bced642a..9f9fc75dd 100644
--- a/packages/plugin-pages/src/collections/pages.ts
+++ b/packages/plugin-pages/src/collections/pages.ts
@@ -192,6 +192,15 @@ export default {
type: 'number',
},
},
+ // {
+ // type: 'belongsToMany',
+ // name: 'roles',
+ // through: 'routes_permissions',
+ // foreignKey: 'routable_id',
+ // otherKey: 'role_id',
+ // morphType: 'routable',
+ // constraints: false,
+ // },
{
interface: 'json',
type: 'json',
diff --git a/packages/plugin-pages/src/collections/roles.ts b/packages/plugin-pages/src/collections/roles.ts
new file mode 100644
index 000000000..f0f525511
--- /dev/null
+++ b/packages/plugin-pages/src/collections/roles.ts
@@ -0,0 +1,43 @@
+import { extend } from '@nocobase/database';
+
+export default extend({
+ name: 'roles',
+ fields: [
+ {
+ type: 'hasMany',
+ name: 'routes',
+ target: 'routes_permissions',
+ },
+ {
+ interface: 'linkTo',
+ type: 'belongsToMany',
+ name: 'pages',
+ title: '可访问的页面',
+ through: 'routes_permissions',
+ foreignKey: 'role_id',
+ otherKey: 'routable_id',
+ morphType: 'routable', // 现在没有多态关联的设置,暂时先这么写了
+ constraints: false, // 多态关联建立外键约束会有问题
+ }
+ ],
+ tabs: [
+ {
+ type: 'association',
+ name: 'pages',
+ title: '系统菜单权限',
+ association: 'pages',
+ viewName: 'permissionTable',
+ },
+ ]
+}, {
+ customMerge(key) {
+ if (['tabs'].includes(key)) {
+ return (x = [], y = []) => {
+ const last = x.pop();
+ const tabs = x.concat(y);
+ tabs.push(last);
+ return tabs;
+ };
+ }
+ }
+});
diff --git a/packages/plugin-pages/src/collections/routes_permissions.ts b/packages/plugin-pages/src/collections/routes_permissions.ts
new file mode 100644
index 000000000..68cb3114d
--- /dev/null
+++ b/packages/plugin-pages/src/collections/routes_permissions.ts
@@ -0,0 +1,30 @@
+import { TableOptions } from '@nocobase/database';
+
+export default {
+ name: 'routes_permissions',
+ title: '页面权限',
+ developerMode: true,
+ internal: true,
+ fields: [
+ {
+ type: 'integer',
+ name: 'id',
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ {
+ type: 'string',
+ name: 'routable_type',
+ title: '关联的表', // 仅 pages 和 collections
+ },
+ {
+ type: 'integer',
+ name: 'routable_id',
+ title: '关联的对象'
+ },
+ {
+ type: 'belongsTo',
+ name: 'role'
+ },
+ ],
+} as TableOptions;
diff --git a/packages/plugin-permissions/src/AccessController.ts b/packages/plugin-permissions/src/AccessController.ts
index ada154802..5e5b97c0f 100644
--- a/packages/plugin-permissions/src/AccessController.ts
+++ b/packages/plugin-permissions/src/AccessController.ts
@@ -5,7 +5,7 @@ import { ROLE_TYPE_ANONYMOUS, ROLE_TYPE_ROOT, ROLE_TYPE_USER } from './constants
function getPermissions(roles) {
- return roles.reduce((permissions, role) => permissions.concat(role.get('permissions')), []);
+ return roles.reduce((permissions, role) => permissions.concat(role.permissions), []);
}
function getActionPermissions(permissions) {
@@ -97,10 +97,16 @@ export type PermissionParams = true | null | {
// ctx.can('collection').act('update').any()
// ctx.can('collection').act('update').one(resourceKey)
// ctx.can('collection').act('get').one(resourceKey)
+// ctx.as(roles).can('collection').permissions()
+
+// TODO(optimize): 需要优化链式调用结构和上下文,以避免顺序或重置等问题
+export default class AccessController {
+ static isRoot(roles): boolean {
+ return (Array.isArray(roles) ? roles : [roles]).some(role => role.type === ROLE_TYPE_ROOT);
+ }
-export default class AccessController {
context;
-
+ roles;
resourceName: string | null = null;
actionName: string | null = null;
@@ -108,11 +114,21 @@ export default class AccessController {
this.context = ctx;
}
- can = (resourceName: string | null) => {
+ /**
+ * 用于临时创建新的身份进行相关操作
+ * @param roles Array
+ */
+ as(roles) {
+ const instance = new (this.constructor as T)(this.context);
+ instance.roles = Array.isArray(roles) ? roles : [roles];
+ return instance;
+ }
+
+ can(resourceName: string | null) {
this.resourceName = resourceName;
this.actionName = null;
return this;
- };
+ }
act(name: string | null) {
this.actionName = name;
@@ -121,7 +137,7 @@ export default class AccessController {
async permissions(): Promise {
const roles = await this.getRolesWithPermissions();
- if (roles.some(role => role.type === ROLE_TYPE_ROOT)) {
+ if ((this.constructor as T).isRoot(roles)) {
return this.getRootPermissions();
}
@@ -136,7 +152,7 @@ export default class AccessController {
async any(): Promise {
const roles = await this.getRolesWithPermissions();
- if (roles.some(role => role.type === ROLE_TYPE_ROOT)) {
+ if ((this.constructor as T).isRoot(roles)) {
return true;
}
// 只处理 actions 表里的权限,其余跳过
@@ -180,7 +196,7 @@ export default class AccessController {
const Collection = this.context.db.getModel(this.resourceName);
const existed = await Collection.count({
where: {
- ...Collection.parseApiJson({ filter }).where,
+ ...Collection.parseApiJson({ filter, context: this.context }).where,
[Collection.primaryKeyAttribute]: resourceKey
}
});
@@ -188,25 +204,78 @@ export default class AccessController {
return existed ? any : null;
}
+ async isRoot(): Promise {
+ const { context } = this;
+ const { currentUser } = context.state;
+ if (!currentUser) {
+ return false;
+ }
+
+ const rootRoles = await currentUser.countRoles({
+ where: {
+ type: ROLE_TYPE_ROOT
+ }
+ });
+ if (!rootRoles.length) {
+ return false;
+ }
+
+ return true;
+ }
+
+ async getRoles() {
+ if (this.roles) {
+ return this.roles;
+ }
+ const { context } = this;
+ let userRoles = [];
+ const { currentUser } = context.state;
+ if (currentUser) {
+ const rootRoles = await currentUser.getRoles({
+ where: {
+ type: ROLE_TYPE_ROOT
+ }
+ });
+ if (rootRoles.length) {
+ return rootRoles;
+ }
+
+ userRoles = await currentUser.getRoles({
+ where: {
+ type: ROLE_TYPE_USER
+ }
+ });
+ }
+
+ const Role = context.db.getModel('roles');
+ const anonymousRoles = await Role.findAll({
+ where: {
+ type: ROLE_TYPE_ANONYMOUS
+ }
+ });
+
+ return [...userRoles, ...anonymousRoles];
+ }
+
async getRolesWithPermissions() {
const { context, resourceName, actionName = null } = this;
if (!resourceName) {
throw new Error('resource name must be set first by `can(resourceName)`');
}
- const Role = context.db.getModel('roles');
- const permissionInclusion = {
- association: 'permissions',
+
+ const permissionOptions = {
where: {
collection_name: resourceName
},
- required: true,
include: [
{
association: 'actions',
- where: actionName ? {
- name: `${resourceName}:${actionName}`
- } : {},
- required: true,
+ ...(actionName ? {
+ where: {
+ name: `${resourceName}:${actionName}`
+ },
+ required: true,
+ } : {}),
// 对 hasMany 关系可以进行拆分查询,避免联表过多标识符超过 PG 的 64 字符限制
separate: true,
include: [
@@ -228,8 +297,24 @@ export default class AccessController {
association: 'tabs_permissions',
separate: true,
}
- ],
+ ]
};
+ const permissionInclusion = {
+ ...permissionOptions,
+ association: 'permissions',
+ required: true
+ };
+
+ if (this.roles) {
+ if ((this.constructor as T).isRoot(this.roles)) {
+ return this.roles;
+ }
+ for (const role of this.roles) {
+ role.permissions = await role.getPermissions(permissionOptions);
+ role.set('permissions', role.permissions);
+ }
+ return this.roles;
+ }
let userRoles = [];
// 获取登入用户的角色及权限
@@ -255,6 +340,7 @@ export default class AccessController {
}
// 获取匿名用户的角色及权限
+ const Role = context.db.getModel('roles');
const anonymousRoles = await Role.findAll({
where: {
type: ROLE_TYPE_ANONYMOUS
diff --git a/packages/plugin-permissions/src/actions/roles.collections.ts b/packages/plugin-permissions/src/actions/roles.collections.ts
index 33ab8376c..053535a6b 100644
--- a/packages/plugin-permissions/src/actions/roles.collections.ts
+++ b/packages/plugin-permissions/src/actions/roles.collections.ts
@@ -1,10 +1,24 @@
import { Op } from 'sequelize';
import { actions } from '@nocobase/actions';
+import _ from 'lodash';
export async function list(ctx: actions.Context, next: actions.Next) {
+ const { associated } = ctx.action.params;
// TODO: 暂时 action 中间件就这么写了
- ctx.action.mergeParams({associated: null});
- return actions.common.list(ctx, next);
+ ctx.action.mergeParams({
+ associated: null
+ });
+ await actions.common.list(ctx, async () => {
+ const permissions = await associated.getPermissions();
+ ctx.body.rows.forEach(item => {
+ const permission = permissions.find(p => p.collection_name === item.get('name'));
+ if (permission) {
+ // item.permissions = [permission]; // 不输出
+ item.set('permissions', [permission]); // 输出
+ }
+ });
+ });
+ await next();
}
export async function get(ctx: actions.Context, next: actions.Next) {
@@ -13,47 +27,22 @@ export async function get(ctx: actions.Context, next: actions.Next) {
associated
} = ctx.action.params;
- const [permission] = await associated.getPermissions({
- where: {
- collection_name: resourceKey
- },
- include: [
- {
- association: 'actions',
- // 对 hasMany 关系可以进行拆分查询,避免联表过多标识符超过 PG 的 64 字符限制
- separate: true,
- include: [
- {
- association: 'scope'
- }
- ]
- },
- {
- association: 'fields_permissions',
- separate: true,
- },
- {
- association: 'tabs_permissions',
- separate: true,
- }
- ],
- distinct: true,
- limit: 1
- });
-
- const result = permission
- ? {
- actions: permission.actions || [],
- fields: permission.fields_permissions || [],
- tabs: (permission.tabs_permissions || []).map(item => item.tab_id),
- }
- : {
- actions: [],
- fields: [],
- tabs: []
- };
+ const permissions = await ctx.ac.as(associated).can(resourceKey).permissions();
- ctx.body = result;
+ const permission = await associated.getPermissions({
+ where: {
+ collection_name: resourceKey,
+ },
+ plain: true,
+ limit: 1,
+ });
+
+ console.log(permission);
+
+ ctx.body = {
+ ...permissions,
+ description: _.get(permission, 'description'),
+ };
await next();
}
diff --git a/packages/plugin-permissions/src/actions/roles.pages.ts b/packages/plugin-permissions/src/actions/roles.pages.ts
deleted file mode 100644
index 6e37286eb..000000000
--- a/packages/plugin-permissions/src/actions/roles.pages.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { actions } from '@nocobase/actions';
-
-export async function list(ctx: actions.Context, next: actions.Next) {
- // TODO: 暂时 action 中间件就这么写了
- ctx.action.mergeParams({associated: null});
- const { associatedKey } = ctx.action.params;
- ctx.action.mergeParams({
- filter: {
- 'parent_id.$notNull': true,
- }
- })
- const done = async () => {
- ctx.body.rows = ctx.body.rows.map(row => {
- row.setDataValue('tableName', 'pages');
- row.setDataValue('associatedKey', parseInt(associatedKey));
- return row.get();
- });
- console.log(ctx.body.rows);
- await next();
- }
- return actions.common.list(ctx, done);
-}
-
-export async function update(ctx: actions.Context, next: actions.Next) {
- ctx.body = {};
- await next();
-}
diff --git a/packages/plugin-permissions/src/collections/actions_premissions.ts b/packages/plugin-permissions/src/collections/actions_permissions.ts
similarity index 100%
rename from packages/plugin-permissions/src/collections/actions_premissions.ts
rename to packages/plugin-permissions/src/collections/actions_permissions.ts
diff --git a/packages/plugin-permissions/src/collections/collections.ts b/packages/plugin-permissions/src/collections/collections.ts
index 7c62563df..b9fbf7984 100644
--- a/packages/plugin-permissions/src/collections/collections.ts
+++ b/packages/plugin-permissions/src/collections/collections.ts
@@ -27,6 +27,12 @@ export default extend({
through: 'permissions',
sourceKey: 'name'
},
+ // {
+ // type: 'hasMany',
+ // name: 'permissions',
+ // sourceKey: 'name',
+ // foreignKey: 'collection_name'
+ // }
],
views: [
{
diff --git a/packages/plugin-permissions/src/collections/permissions.ts b/packages/plugin-permissions/src/collections/permissions.ts
index 6c5160e10..f56e1d14f 100644
--- a/packages/plugin-permissions/src/collections/permissions.ts
+++ b/packages/plugin-permissions/src/collections/permissions.ts
@@ -21,7 +21,7 @@ export default {
},
{
type: 'string',
- name: 'desctiption',
+ name: 'description',
},
{
comment: '关联的角色',
diff --git a/packages/plugin-permissions/src/collections/roles.ts b/packages/plugin-permissions/src/collections/roles.ts
index efa20ca2d..4af3e615a 100644
--- a/packages/plugin-permissions/src/collections/roles.ts
+++ b/packages/plugin-permissions/src/collections/roles.ts
@@ -24,12 +24,39 @@ export default {
title: '角色类型',
type: 'integer',
name: 'type',
+ developerMode: true,
dataSource: [
{ value: ROLE_TYPE_ROOT, label: '系统角色' },
{ value: ROLE_TYPE_ANONYMOUS, label: '匿名角色' },
{ value: ROLE_TYPE_USER, label: '自定义角色' },
],
- defaultValue: ROLE_TYPE_USER
+ defaultValue: ROLE_TYPE_USER,
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ }
+ },
+ {
+ interface: 'boolean',
+ title: '默认角色',
+ type: 'radio',
+ name: 'default',
+ component: {
+ showInTable: true,
+ showInForm: true,
+ showInDetail: true,
+ }
+ },
+ {
+ interface: 'textarea',
+ title: '描述',
+ type: 'text',
+ name: 'description',
+ component: {
+ showInTable: true,
+ showInForm: true,
+ showInDetail: true,
+ },
},
// TODO(feature): 用户组后续考虑
// TODO(feature): 用户表应通过插件配置关联,考虑到今后会有多账户系统的情况
@@ -51,12 +78,6 @@ export default {
through: 'permissions',
targetKey: 'name'
},
- {
- interface: 'linkTo',
- title: '页面',
- type: 'belongsToMany',
- name: 'pages',
- },
{
comment: '权限集(方便访问)',
type: 'hasMany',
@@ -134,13 +155,6 @@ export default {
association: 'collections',
viewName: 'permissionTable',
},
- {
- type: 'association',
- name: 'pages',
- title: '系统菜单权限',
- association: 'pages',
- viewName: 'permissionTable',
- },
{
type: 'association',
name: 'users',
diff --git a/packages/plugin-permissions/src/server.ts b/packages/plugin-permissions/src/server.ts
index 1c95ec3a2..84876d908 100644
--- a/packages/plugin-permissions/src/server.ts
+++ b/packages/plugin-permissions/src/server.ts
@@ -63,6 +63,19 @@ export class Permissions {
}
});
+ database.getModel('users').addHook('afterCreate', async(model, options) => {
+ const { transaction = await database.sequelize.transaction() } = options;
+ const Role = database.getModel('roles');
+ const defaultRole = await Role.findOne({ where: { default: true }, transaction });
+ if (defaultRole) {
+ // @ts-ignore
+ await model.addRole(defaultRole, { transaction });
+ }
+ if (!options.transaction) {
+ await transaction.commit();
+ }
+ });
+
// 针对“自己创建的” scope 添加特殊的操作符以生成查询条件
if (!Operator.has('$currentUser')) {
Operator.register('$currentUser', (value, { ctx }) => {
@@ -76,7 +89,7 @@ export class Permissions {
}
injection = async (ctx, next) => {
- ctx.can = new AccessController(ctx).can;
+ ctx.ac = new AccessController(ctx);
return next();
};
@@ -93,9 +106,9 @@ export class Permissions {
// 关系数据的权限
if (associatedName && resourceField) {
- result = await ctx.can(resourceField.options.target).act(actionName).any();
+ result = await ctx.ac.can(resourceField.options.target).act(actionName).any();
} else {
- result = await ctx.can(resourceName).act(actionName).any();
+ result = await ctx.ac.can(resourceName).act(actionName).any();
}
if (!result) {