docs: relation repository & acl (#848)
* docs: relation-repository * docs: has many repository * docs: acl * docs: acl * docs: acl * docs: acl * docs: acl/AllowManager * docs: acl/ACLAvailableAction * docs: acl * docs: clean up * feat: doc menus Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
460dbcbc7f
commit
d805fafbfc
@ -55,7 +55,7 @@ export default {
|
||||
'/development/guide/i18n',
|
||||
'/development/guide/migration',
|
||||
{
|
||||
title: 'UI Schema Designer',
|
||||
title: 'UI 设计器',
|
||||
type: 'subMenu',
|
||||
children: [
|
||||
// '/development/guide/ui-schema-designer/index',
|
||||
@ -72,7 +72,6 @@ export default {
|
||||
},
|
||||
'/development/guide/ui-router',
|
||||
'/development/guide/settings-center',
|
||||
'/development/guide/commands',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -177,7 +176,10 @@ export default {
|
||||
'/api/database/collection',
|
||||
'/api/database/field',
|
||||
'/api/database/repository',
|
||||
'/api/database/relation-repository',
|
||||
'/api/database/relation-repository/has-one-repository',
|
||||
'/api/database/relation-repository/has-many-repository',
|
||||
'/api/database/relation-repository/belongs-to-repository',
|
||||
'/api/database/relation-repository/belongs-to-many-repository',
|
||||
'/api/database/operators',
|
||||
],
|
||||
},
|
||||
@ -192,8 +194,17 @@ export default {
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '@nocobase/actions',
|
||||
path: '/api/actions',
|
||||
title: '@nocobase/acl',
|
||||
type: 'subMenu',
|
||||
children: [
|
||||
'/api/acl/index',
|
||||
'/api/acl/acl',
|
||||
'/api/acl/acl-role',
|
||||
'/api/acl/acl-resource',
|
||||
'/api/acl/acl-available-action',
|
||||
'/api/acl/acl-available-strategy',
|
||||
'/api/acl/allow-manager',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '@nocobase/client',
|
||||
@ -225,14 +236,14 @@ export default {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '@nocobase/acl',
|
||||
path: '/api/acl',
|
||||
},
|
||||
{
|
||||
title: '@nocobase/cli',
|
||||
path: '/api/cli',
|
||||
},
|
||||
{
|
||||
title: '@nocobase/actions',
|
||||
path: '/api/actions',
|
||||
},
|
||||
{
|
||||
title: '@nocobase/sdk',
|
||||
path: '/api/sdk',
|
||||
|
@ -1 +0,0 @@
|
||||
# ACL
|
19
docs/zh-CN/api/acl/acl-available-action.md
Normal file
19
docs/zh-CN/api/acl/acl-available-action.md
Normal file
@ -0,0 +1,19 @@
|
||||
# ACLAvailableAction
|
||||
|
||||
用于表示一个可用 ACL Action 的数据结构。
|
||||
|
||||
## 类方法
|
||||
|
||||
### `constructor(public name: string, public options: AvailableActionOptions)`
|
||||
|
||||
实例化 ACLAvailableAction
|
||||
|
||||
**参数**
|
||||
|
||||
* name: string - 动作名称
|
||||
* options: AvailableActionOptions
|
||||
* displayName - action 显示名称
|
||||
* aliases - action 别名
|
||||
* resource - action 所属资源名称
|
||||
* onNewRecord - action 是否是在创建新的数据库记录
|
||||
* allowConfigureFields - action 是否允许配置字段
|
24
docs/zh-CN/api/acl/acl-available-strategy.md
Normal file
24
docs/zh-CN/api/acl/acl-available-strategy.md
Normal file
@ -0,0 +1,24 @@
|
||||
# ACLAvailableStrategy
|
||||
|
||||
ACL 角色的权限策略,可以使用其判断角色是否有权限访问资源。
|
||||
|
||||
## 类方法
|
||||
|
||||
### `constructor(acl: ACL, options: AvailableStrategyOptions)`
|
||||
|
||||
构造函数,创建一个 `ACLAvailableStrategy` 实例。
|
||||
|
||||
### `allow(resourceName: string, actionName: string)`
|
||||
|
||||
判断此策略是否允许给定的资源和动作通过鉴权。
|
||||
|
||||
## 基础数据结构
|
||||
|
||||
### `AvailableStrategyOptions`
|
||||
|
||||
策略定义参数,用以描述一组权限配置规则。
|
||||
|
||||
* displayName - 策略名称
|
||||
* allowConfigure - 此策略是否拥有 **配置资源** 的权限,设置此项为`true`之后,请求判断在 `ACL` 中注册成为 `configResources` 资源的权限,会返回通过。
|
||||
* actions - 策略内的 actions 列表,支持通配符 `*`
|
||||
* resource - 策略内的 resource 定义,支持通配符 `*`
|
57
docs/zh-CN/api/acl/acl-resource.md
Normal file
57
docs/zh-CN/api/acl/acl-resource.md
Normal file
@ -0,0 +1,57 @@
|
||||
# ACLResource
|
||||
|
||||
ACLResource,ACL 系统中的资源类。在 ACL 系统中,为用户授予权限时会自动创建对应的资源。
|
||||
|
||||
## 基础数据结构
|
||||
|
||||
|
||||
|
||||
### `ResourceActions`
|
||||
|
||||
Action 集合对象:
|
||||
|
||||
* key 表示 action 的名称
|
||||
* value 表示 action 的配置参数,见 [`RoleActionParams`](#RoleActionParams)。
|
||||
|
||||
**定义**
|
||||
```typescript
|
||||
type ResourceActions = { [key: string]: RoleActionParams };
|
||||
```
|
||||
|
||||
## 类方法
|
||||
|
||||
### `constructor(options: AclResourceOptions)`
|
||||
|
||||
创建 `ACLResource` 实例
|
||||
|
||||
**AclResourceOptions 参数**
|
||||
|
||||
* options - 资源配置参数
|
||||
* name - 资源名称
|
||||
* role - 资源所属角色
|
||||
* actions - ResourceActions 对象,定义资源的 Action
|
||||
|
||||
### `getActions()`
|
||||
|
||||
获取资源的所有 Action,返回结果为 `ResourceActions` 对象。
|
||||
|
||||
### `getAction(name: string)`
|
||||
|
||||
根据名称返回 Action 的参数配置,返回结果为 `RoleActionParams` 对象。
|
||||
|
||||
## `setAction(name: string, params: RoleActionParams)`
|
||||
|
||||
在资源内部设置一个 Action 的参数配置,返回结果为 `RoleActionParams` 对象。
|
||||
|
||||
**参数**
|
||||
|
||||
* name - 要设置的 action 名称
|
||||
* params - [`RoleActionParams`](#RoleActionParams)
|
||||
|
||||
## `setActions(actions: ResourceActions)`
|
||||
|
||||
批量调用 `setAction` 的便捷方法
|
||||
|
||||
**参数**
|
||||
|
||||
* actions: [RoleActionParams](#RoleActionParams)
|
70
docs/zh-CN/api/acl/acl-role.md
Normal file
70
docs/zh-CN/api/acl/acl-role.md
Normal file
@ -0,0 +1,70 @@
|
||||
# ACL Role
|
||||
|
||||
ACLRole,ACL 系统中的用户角色类。在 ACL 系统中,通常使用 `acl.define` 定义角色。
|
||||
|
||||
## 类方法
|
||||
|
||||
### `constructor(public acl: ACL, public name: string)`
|
||||
|
||||
* acl - ACL 实例
|
||||
* name - 角色名称
|
||||
|
||||
### `grantAction(path: string, options?: RoleActionParams)`
|
||||
|
||||
为角色授予 Action 权限
|
||||
|
||||
* path - 资源Action路径,如 `posts:edit`,表示 `posts` 资源的 `edit` Action, 资源名称和 Action 之间使用 `:` 冒号分隔。
|
||||
* options? - 配置参数,见 [`RoleActionParams`](#RoleActionParams)。
|
||||
|
||||
|
||||
## 参数
|
||||
|
||||
### `RoleActionParams`
|
||||
|
||||
RoleActionParams 为授权时,对应 action 的可配置参数,用以实现更细粒度的权限控制。
|
||||
|
||||
* fields - 可访问的字段
|
||||
```typescript
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'posts:view': {
|
||||
// admin 用户可以请求 posts:view action,但是只有 fields 配置的字段权限
|
||||
fields: ["id", "title", "content"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
* filter - 权限资源过滤配置
|
||||
```typescript
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'posts:view': {
|
||||
// admin 用户可以请求 posts:view action,但是列出的结果必须满足 filter 设置的条件。
|
||||
filter: {
|
||||
createdById: '{{ ctx.state.currentUser.id }}', // 支持模板语法,可以取 ctx 中的值,将在权限判断时替换
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
* own - 是否只能访问自己的数据
|
||||
```typescript
|
||||
const actionsWithOwn = {
|
||||
'posts:view': {
|
||||
"own": true //
|
||||
}
|
||||
}
|
||||
|
||||
// 等价于
|
||||
const actionsWithFilter = {
|
||||
'posts:view': {
|
||||
"filter": {
|
||||
"createdById": "{{ ctx.state.currentUser.id }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
* whitelist - 白名单,只有在白名单中的字段才能被访问
|
||||
* blacklist - 黑名单,黑名单中的字段不能被访问
|
214
docs/zh-CN/api/acl/acl.md
Normal file
214
docs/zh-CN/api/acl/acl.md
Normal file
@ -0,0 +1,214 @@
|
||||
# ACL
|
||||
|
||||
ACL 为权限管理类,系统中的角色与资源都可以在 ACL 中进行注册。
|
||||
|
||||
## 成员变量
|
||||
|
||||
### `availableActions: Map<string, AclAvailableAction>`
|
||||
|
||||
ACL 内的 `AclAvailableAction` 名称映射。
|
||||
|
||||
### `availableStrategy: Map<string, ACLAvailableStrategy>`
|
||||
|
||||
ACL 内的 [`ACLAvailableStrategy`](#ACLAvailableStrategy) 名称映射。
|
||||
|
||||
### `middlewares`
|
||||
|
||||
ACL 鉴权中间件。
|
||||
|
||||
### `roles: Map<string, ACLRole>`
|
||||
|
||||
ACL 内的 `ACLRole` 名称映射。
|
||||
|
||||
### `actionAlias: Map<string, string>`
|
||||
|
||||
Action 别名映射。
|
||||
|
||||
### `configResources: Array<string>`
|
||||
|
||||
配置资源列表。
|
||||
|
||||
## 类方法
|
||||
|
||||
### `constructor()`
|
||||
|
||||
构造函数,创建一个 `ACL` 实例。
|
||||
|
||||
```typescript
|
||||
import { ACL } from '@nocobase/database';
|
||||
|
||||
const acl = new ACL();
|
||||
```
|
||||
### `define(options: DefineOptions)`
|
||||
|
||||
定义系统角色
|
||||
|
||||
**DefineOptions 参数**
|
||||
|
||||
* `role` - 角色名称
|
||||
|
||||
```typescript
|
||||
// 定义一个名称为 admin 的角色
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
});
|
||||
```
|
||||
|
||||
* `allowConfigure` - 是否允许配置权限
|
||||
* `strategy` - 角色的权限策略
|
||||
* 可以为 `string`,为要使用的策略名,表示使用已定义的策略。
|
||||
* 可以为 `AvailableStrategyOptions`,为该角色定义一个新的策略。
|
||||
* `actions` - 定义角色时,可传入角色可访问的 `actions` 对象,
|
||||
之后会依次调用 `aclRole.grantAction` 授予资源权限。详见 [`ResourceActionsOptions`](#ResourceActionsOptions)
|
||||
|
||||
```typescript
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'posts:edit': {}
|
||||
},
|
||||
});
|
||||
// 等同于
|
||||
const role = acl.define({
|
||||
role: 'admin',
|
||||
});
|
||||
|
||||
role.grantAction('posts:edit', {});
|
||||
```
|
||||
|
||||
### `getRole(name: string): ACLRole`
|
||||
|
||||
根据角色名称返回角色对象
|
||||
|
||||
### `removeRole(name: string)`
|
||||
|
||||
根据角色名称移除角色
|
||||
|
||||
### `can({ role, resource, action }: CanArgs): CanResult | null`
|
||||
|
||||
鉴权函数,调用返回为`null`时,表示角色无权限,反之返回`CanResult`对象,表示角色有权限。
|
||||
|
||||
`can` 方法首先会判断角色是否有注册对应的 `Action` 权限,如果没有则会去判断角色的 `strategy` 是否匹配.
|
||||
|
||||
**CanArgs 参数**
|
||||
|
||||
* role - 角色名称
|
||||
* resource - 资源名称
|
||||
* action - 操作名称
|
||||
|
||||
**CanResult 参数**
|
||||
|
||||
* role - 角色名称
|
||||
* resource - 资源名称
|
||||
* action - 操作名称
|
||||
* params - 注册权限时传入的参数
|
||||
|
||||
```typescript
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'posts:edit': {
|
||||
fields: ['title', 'content'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const canResult = acl.can({
|
||||
role: 'admin',
|
||||
resource: 'posts',
|
||||
action: 'edit',
|
||||
});
|
||||
/**
|
||||
* canResult = {
|
||||
* role: 'admin',
|
||||
* resource: 'posts',
|
||||
* action: 'edit',
|
||||
* params: {
|
||||
* fields: ['title', 'content'],
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
acl.can({
|
||||
role: 'admin',
|
||||
resource: 'posts',
|
||||
action: 'destroy',
|
||||
}); // null
|
||||
```
|
||||
### `use(fn: any)`
|
||||
|
||||
向 middlewares 中添加中间件函数。
|
||||
|
||||
### `middleware()`
|
||||
|
||||
返回一个中间件函数,用于在 `@nocobase/server` 中使用。使用此 `middleware` 之后,`@nocobase/server` 在每次请求处理之前都会进行权限判断。
|
||||
|
||||
### `setAvailableStrategy(name: string, options: AvailableStrategyOptions)`
|
||||
|
||||
注册一个可用的权限策略
|
||||
|
||||
### `registerConfigResource(name: string)`
|
||||
|
||||
将传入的资源名称设置为**配置资源**。配置资源是指这样的一种资源,这些资源的改动会调用`ACL`中的角色、权限注册相关方法,例如用户、权限、角色等,这些资源就需要被设置为配置资源。
|
||||
|
||||
### `registerConfigResources(names: string[])`
|
||||
|
||||
`registerConfigResource` 的批量方法
|
||||
|
||||
### `isConfigResource(name: string)`
|
||||
|
||||
判断传入的资源名称是否为配置资源
|
||||
|
||||
### `setAvailableAction(name: string, options: AvailableActionOptions = {})`
|
||||
|
||||
设置 ACL 中有效的 Action 名称。
|
||||
|
||||
**参数**
|
||||
|
||||
* name - action 名称
|
||||
* options - action 选项
|
||||
* displayName - action 显示名称
|
||||
* aliases - action 别名
|
||||
* resource - action 所属资源名称
|
||||
* onNewRecord - action 是否是在创建新的数据库记录
|
||||
* allowConfigureFields - action 是否允许配置字段
|
||||
|
||||
|
||||
### `getAvailableAction(name: string)`
|
||||
|
||||
获取 ACL 中有效的 Action。
|
||||
|
||||
### `setAvailableStrategy(name: string, options: AvailableStrategyOptions)`
|
||||
|
||||
设置可用的权限策略,详见 [`AvailableStrategyOptions`](#AvailableStrategyOptions)
|
||||
|
||||
### `allow(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc)`
|
||||
|
||||
在不指定角色的情况下,开放资源的访问权限。
|
||||
举例来说,例如登录操作,可以被公开访问:
|
||||
|
||||
```typescript
|
||||
// 注册 users:login 可以被公开访问
|
||||
acl.allow('users', 'login');
|
||||
```
|
||||
|
||||
**参数**
|
||||
* resourceName - 资源名称
|
||||
* actionNames - 资源动作名
|
||||
* condition? - 配置生效条件
|
||||
* 传入 `string`,表示使用已定义的条件,注册条件使用 `acl.allowManager.registerCondition` 方法。
|
||||
```typescript
|
||||
acl.allowManager.registerAllowCondition('superUser', async () => {
|
||||
return ctx.state.user?.id === 1;
|
||||
});
|
||||
|
||||
// 开放 users:list 的权限,条件为 superUser
|
||||
acl.allow('users', 'list', 'superUser');
|
||||
```
|
||||
* 传入 ConditionFunc,可接收 `ctx` 参数,返回 `boolean`,表示是否生效。
|
||||
```typescript
|
||||
// 当用户ID为1时,可以访问 user:list
|
||||
acl.allow('users', 'list', (ctx) => {
|
||||
return ctx.state.user?.id === 1;
|
||||
});
|
||||
```
|
55
docs/zh-CN/api/acl/allow-manager.md
Normal file
55
docs/zh-CN/api/acl/allow-manager.md
Normal file
@ -0,0 +1,55 @@
|
||||
# AllowManager
|
||||
|
||||
开放权限管理
|
||||
|
||||
## 类方法
|
||||
### `constructor(public acl: ACL)`
|
||||
|
||||
实例化 AllowManger
|
||||
|
||||
### `allow(resourceName: string, actionName: string, condition?: string | ConditionFunc)`
|
||||
|
||||
注册开放权限
|
||||
|
||||
```typescript
|
||||
// users:login 可以被公开访问
|
||||
allowManager.allow('users', 'login');
|
||||
```
|
||||
|
||||
**参数**
|
||||
* resourceName - 资源名称
|
||||
* actionName - 资源动作名
|
||||
* condition? - 配置生效条件
|
||||
* 传入 `string`,表示使用已定义的条件,注册条件使用 `acl.allowManager.registerCondition` 方法。
|
||||
```typescript
|
||||
acl.allowManager.registerAllowCondition('superUser', async () => {
|
||||
return ctx.state.user?.id === 1;
|
||||
});
|
||||
|
||||
// 开放 users:list 的权限,条件为 superUser
|
||||
acl.allow('users', 'list', 'superUser');
|
||||
```
|
||||
* 传入 ConditionFunc,可接收 `ctx` 参数,返回 `boolean`,表示是否生效。
|
||||
```typescript
|
||||
// 当用户ID为1时,可以访问 user:list
|
||||
acl.allow('users', 'list', (ctx) => {
|
||||
return ctx.state.user?.id === 1;
|
||||
});
|
||||
```
|
||||
|
||||
### `registerAllowCondition(name: string, condition: ConditionFunc)`
|
||||
|
||||
注册开放权限条件
|
||||
|
||||
### `getAllowedConditions(resourceName: string, actionName: string): Array<ConditionFunc | true>`
|
||||
|
||||
获取已注册的开放条件
|
||||
|
||||
**参数**
|
||||
* resourceName - 资源名称
|
||||
* actionName - 资源动作名
|
||||
|
||||
### `aclMiddleware()`
|
||||
|
||||
中间件,注入于 `acl` 实例中,用于判断是否开放权限,若根据条件判断为开放权限,则在 `acl` middleware 中会跳过权限检查。
|
||||
|
12
docs/zh-CN/api/acl/index.md
Normal file
12
docs/zh-CN/api/acl/index.md
Normal file
@ -0,0 +1,12 @@
|
||||
# ACL 权限管理
|
||||
|
||||
ACL 为 Nocobase 中的权限控制模块。在 ACL 中注册角色、资源以及配置相应权限之后,即可对角色进行权限判断。
|
||||
|
||||
## 概念解释
|
||||
|
||||
* 角色 (`ACLRole`):权限判断的对象
|
||||
* 资源 (`ACLResource`):在 Nocobase ACL 中,资源通常对应一个数据库表,概念上可类比为 Restful API 中的 Resource。
|
||||
* Action:对资源的操作,如 `create`、`read`、`update`、`delete` 等。
|
||||
* 策略 (`ACLAvailableStrategy`): 通常每个角色都有自己的权限策略,策略中定义了默认情况下的用户权限。
|
||||
* 授权:在 `ACLRole` 实例中调用 `grantAction` 函数,为角色授予 `Action` 的访问权限。
|
||||
* 鉴权:在 `ACL` 实例中调用 `can` 函数,函数返回结果既为用户的鉴权结果。
|
@ -1,87 +0,0 @@
|
||||
# RelationRepository
|
||||
|
||||
关系型数据仓库抽象类。
|
||||
|
||||
## 基类属性
|
||||
|
||||
### `db`
|
||||
|
||||
### `sourceCollection`
|
||||
|
||||
### `targetCollection`
|
||||
|
||||
### `associationField`
|
||||
|
||||
### `sourceKeyValue`
|
||||
|
||||
### `sourceInstance`
|
||||
|
||||
## HasOneRepository
|
||||
|
||||
### `find()`
|
||||
|
||||
### `update()`
|
||||
|
||||
### `destroy()`
|
||||
|
||||
### `remove()`
|
||||
|
||||
### `set()`
|
||||
|
||||
## BelongsToRepository
|
||||
|
||||
### `find()`
|
||||
|
||||
### `update()`
|
||||
|
||||
### `destroy()`
|
||||
|
||||
### `remove()`
|
||||
|
||||
### `set()`
|
||||
|
||||
## HasManyRepository
|
||||
|
||||
### `count()`
|
||||
|
||||
### `find()`
|
||||
|
||||
### `findOne()`
|
||||
|
||||
### `findAndCount()`
|
||||
|
||||
### `create()`
|
||||
|
||||
### `update()`
|
||||
|
||||
### `destroy()`
|
||||
|
||||
### `add()`
|
||||
|
||||
### `remove()`
|
||||
|
||||
### `set()`
|
||||
|
||||
## BelongsToManyRepository
|
||||
|
||||
### `count()`
|
||||
|
||||
### `find()`
|
||||
|
||||
### `findOne()`
|
||||
|
||||
### `findAndCount()`
|
||||
|
||||
### `create()`
|
||||
|
||||
### `update()`
|
||||
|
||||
### `destroy()`
|
||||
|
||||
### `add()`
|
||||
|
||||
### `remove()`
|
||||
|
||||
### `set()`
|
||||
|
||||
### `toggle()`
|
@ -0,0 +1,23 @@
|
||||
## BelongsToManyRepository
|
||||
|
||||
### `count()`
|
||||
|
||||
### `find()`
|
||||
|
||||
### `findOne()`
|
||||
|
||||
### `findAndCount()`
|
||||
|
||||
### `create()`
|
||||
|
||||
### `update()`
|
||||
|
||||
### `destroy()`
|
||||
|
||||
### `add()`
|
||||
|
||||
### `remove()`
|
||||
|
||||
### `set()`
|
||||
|
||||
### `toggle()`
|
@ -0,0 +1,3 @@
|
||||
## BelongsToRepository
|
||||
|
||||
`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。其接口与 [HasOneRepository](#has-one-repository) 一致。
|
@ -0,0 +1,152 @@
|
||||
|
||||
# HasManyRepository
|
||||
|
||||
`HasManyRepository` 是用于处理 `HasMany` 关系的 `Repository`。
|
||||
|
||||
## 类方法
|
||||
|
||||
### `find()`
|
||||
|
||||
查找关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async find(options?: FindOptions): Promise<M[]>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `FindOptions` | - | 参见 repository.find |
|
||||
|
||||
### `findOne()`
|
||||
|
||||
查找关联对象,仅返回一条记录
|
||||
|
||||
**签名**
|
||||
|
||||
* `async findOne(options?: FindOneOptions): Promise<M>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `FindOneOptions` | - | 参见 repository.findOne |
|
||||
|
||||
### `count()`
|
||||
|
||||
返回符合查询条件的记录数
|
||||
|
||||
**签名**
|
||||
|
||||
* `async count(options?: CountOptions)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `CountOptions` | - | 参见 repository.count |
|
||||
|
||||
|
||||
### `findAndCount()`
|
||||
|
||||
同时返回符合查询条件的记录集合与记录数
|
||||
|
||||
**签名**
|
||||
|
||||
* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `FindAndCountOptions` | - | 参见 repository.findAndCount |
|
||||
|
||||
|
||||
### `create()`
|
||||
|
||||
创建关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async create(options?: CreateOptions): Promise<M>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `CreateOptions` | - | 参见 repository.create |
|
||||
|
||||
|
||||
### `update()`
|
||||
|
||||
更新符合条件的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async update(options?: UpdateOptions): Promise<M>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `UpdateOptions` | - | 参见 repository.update |
|
||||
|
||||
|
||||
### `destroy()`
|
||||
|
||||
删除符合条件的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async destroy(options?: TK | DestroyOptions): Promise<M>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `TK \|DestroyOptions` | - | 传入删除对象的 `targetKeyId`,或者 `targetKeyId` 数组。需传 `transaction` 时 使用 `DestroyOptions` 类型 |
|
||||
|
||||
### `add()`
|
||||
|
||||
添加关联对象
|
||||
|
||||
**签名**
|
||||
* `async add(options: TargetKey | TargetKey[] | AssociatedOptions)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `TargetKey` | - | 关联对象的 `targetKeyId` |
|
||||
| `options` | `TargetKey[]` | - | 多个关联对象的 `targetKeyId` 数组 |
|
||||
| `options` | `AssociatedOptions` | - | `options.tk`, 为 `targetKeyId` 或者 `targetKeyId` 数组;`options.transaction`,为 `Transaction` 对象 |
|
||||
|
||||
|
||||
### `remove()`
|
||||
|
||||
移除符合条件的关联对象
|
||||
|
||||
**签名**
|
||||
* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `TargetKey \| TargetKey[] \| AssociatedOptions` | - | 同 [add](#add) |
|
||||
|
||||
|
||||
### `set()`
|
||||
|
||||
设置当前关系的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async set(options: TargetKey | TargetKey[] | AssociatedOptions)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `TargetKey \| TargetKey[] \| AssociatedOptions` | - | 同 [add](#add) |
|
@ -0,0 +1,180 @@
|
||||
# HasOneRepository
|
||||
`HasOneRepository` 为 `HasOne` 类型的关联 Repository。
|
||||
|
||||
```typescript
|
||||
const User = db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{ type: 'hasOne', name: 'profile' },
|
||||
{ type: 'string', name: 'name' },
|
||||
],
|
||||
});
|
||||
|
||||
const Profile = db.collection({
|
||||
name: 'profiles',
|
||||
fields: [{ type: 'string', name: 'avatar' }],
|
||||
});
|
||||
|
||||
const user = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
// 创建 HasOneRepository 实例
|
||||
const userProfileRepository = new HasOneRepository(User, 'profile', user.get('id'));
|
||||
|
||||
```
|
||||
|
||||
## 类方法
|
||||
|
||||
### `create(options?: CreateOptions)`
|
||||
创建关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async create(options?: CreateOptions): Promise<Model>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `CreateOptions` | - | 参见 repository.create |
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
const profile = await UserProfileRepository.create({
|
||||
values: { avatar: 'avatar1' },
|
||||
});
|
||||
|
||||
console.log(profile.toJSON());
|
||||
/*
|
||||
{
|
||||
id: 1,
|
||||
avatar: 'avatar1',
|
||||
userId: 1,
|
||||
updatedAt: 2022-09-24T13:59:40.025Z,
|
||||
createdAt: 2022-09-24T13:59:40.025Z
|
||||
}
|
||||
*/
|
||||
|
||||
```
|
||||
|
||||
### `find()`
|
||||
|
||||
查找关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async find(options?: SingleRelationFindOption): Promise<Model<any> | null>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.fields` | `Fields` | - | 参见 repository.find.fields |
|
||||
| `options.except` | `Except` | - | 参见 repository.find.except |
|
||||
| `options.appends` | `Appends` | - | 参见 repository.find.appends |
|
||||
| `options.filter` | `Filter` | - | 参见 repository.find.filter |
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
const profile = await UserProfileRepository.find();
|
||||
// 关联对象不存在时,返回 null
|
||||
```
|
||||
|
||||
### `update()`
|
||||
|
||||
更新关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async update(options: UpdateOptions): Promise<Model>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `UpdateOptions` | - | 参见 repository.update |
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
const profile = await UserProfileRepository.update({
|
||||
values: { avatar: 'avatar2' },
|
||||
});
|
||||
|
||||
profile.get('avatar'); // 'avatar2'
|
||||
```
|
||||
|
||||
### `remove()`
|
||||
|
||||
移除关联对象,仅解除关联关系,不删除关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async remove(options?: Transactionable): Promise<void>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.transaction` | `Transaction` | - | Transaction |
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
await UserProfileRepository.remove();
|
||||
await UserProfileRepository.find() == null; // true
|
||||
|
||||
await Profile.repository.count() === 1; // true
|
||||
```
|
||||
|
||||
### `destroy()`
|
||||
|
||||
删除关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async destroy(options?: Transactionable): Promise<Boolean>`
|
||||
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.transaction` | `Transaction` | - | Transaction |
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
await UserProfileRepository.destroy();
|
||||
await UserProfileRepository.find() == null; // true
|
||||
await Profile.repository.count() === 0; // true
|
||||
```
|
||||
|
||||
### `set()`
|
||||
|
||||
设置关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async set(options: TargetKey | SetOption): Promise<void>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | ` TargetKey \| SetOption` | - | 需要 set 的对象的 targetKey,如果需要一同传入 transaction 则修改为 object 类型参数 |
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
const newProfile = await Profile.repository.create({
|
||||
values: { avatar: 'avatar2' },
|
||||
});
|
||||
|
||||
await UserProfileRepository.set(newProfile.get('id'));
|
||||
|
||||
(await UserProfileRepository.find()).get('id') === newProfile.get('id'); // true
|
||||
```
|
45
docs/zh-CN/api/database/relation-repository/index.md
Normal file
45
docs/zh-CN/api/database/relation-repository/index.md
Normal file
@ -0,0 +1,45 @@
|
||||
# RelationRepository
|
||||
|
||||
`RelationRepository` 是关系类型的 `Repository` 对象,`RelationRepository` 可以实现在不加载关联的情况下对关联数据进行操作。基于 `RelationRepository`,每种关联都派生出对应的实现,分别为
|
||||
|
||||
* [`HasOneRepository`](#has-one-repository)
|
||||
* `HasManyRepository`
|
||||
* `BelongsToRepository`
|
||||
* `BelongsToManyRepository`
|
||||
|
||||
|
||||
## 构造函数
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `sourceCollection` | `Collection` | - | 关联中的参照关系(referencing relation)对应的 Collection |
|
||||
| `association` | `string` | - | 关联名称 |
|
||||
| `sourceKeyValue` | `string \| number` | - | 参照关系中对应的 key 值 |
|
||||
|
||||
|
||||
## 基类属性
|
||||
|
||||
### `db: Database`
|
||||
|
||||
数据库对象
|
||||
|
||||
### `sourceCollection`
|
||||
关联中的参照关系(referencing relation)对应的 Collection
|
||||
|
||||
### `targetCollection`
|
||||
关联中被参照关系(referenced relation)对应的 Collection
|
||||
|
||||
### `association`
|
||||
sequelize 中的与当前关联对应的 association 对象
|
||||
|
||||
### `associationField`
|
||||
collection 中的与当前关联对应的字段
|
||||
|
||||
### `sourceKeyValue`
|
||||
参照关系中对应的 key 值
|
@ -1,4 +1,4 @@
|
||||
# Middleware
|
||||
# 中间件
|
||||
|
||||
## 添加方法
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Settings Center
|
||||
# 配置中心
|
||||
|
||||
<img src="./settings-tab.jpg" style="max-width: 100%;"/>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# UI Router
|
||||
# UI 路由
|
||||
|
||||
NocoBase Client 的 Router 基于 [React Router](https://v5.reactrouter.com/web/guides/quick-start),可以通过 `<RouteSwitch routes={[]} />` 来配置 ui routes,例子如下:
|
||||
|
||||
|
@ -38,6 +38,7 @@ describe('acl', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should define role with predicate', () => {
|
||||
acl.setAvailableAction('edit', {
|
||||
type: 'old-data',
|
||||
|
@ -12,6 +12,6 @@ export interface AvailableActionOptions {
|
||||
allowConfigureFields?: boolean;
|
||||
}
|
||||
|
||||
export class AclAvailableAction {
|
||||
export class ACLAvailableAction {
|
||||
constructor(public name: string, public options: AvailableActionOptions) {}
|
||||
}
|
||||
|
@ -9,22 +9,6 @@ export interface AvailableStrategyOptions {
|
||||
resource?: '*';
|
||||
}
|
||||
|
||||
export function strategyValueMatched(strategy: StrategyValue, value: string) {
|
||||
if (strategy === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lodash.isString(strategy) && strategy === value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lodash.isArray(strategy) && strategy.includes(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export const predicate = {
|
||||
own: {
|
||||
filter: {
|
||||
|
@ -53,7 +53,7 @@ export class ACLResource {
|
||||
this.actions.set(name, context.params);
|
||||
}
|
||||
|
||||
setActions(actions: { [key: string]: RoleActionParams }) {
|
||||
setActions(actions: ResourceActions) {
|
||||
for (const actionName of Object.keys(actions)) {
|
||||
this.setAction(actionName, actions[actionName]);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ export interface RoleActionParams {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface ResourceActionsOptions {
|
||||
export interface ResourceActionsOptions {
|
||||
[actionName: string]: RoleActionParams;
|
||||
}
|
||||
|
||||
@ -25,33 +25,16 @@ export class ACLRole {
|
||||
return this.resources.get(name);
|
||||
}
|
||||
|
||||
setResource(name: string, resource: ACLResource) {
|
||||
this.resources.set(name, resource);
|
||||
}
|
||||
|
||||
public setStrategy(value: string | AvailableStrategyOptions) {
|
||||
this.strategy = value;
|
||||
}
|
||||
|
||||
public grantResource(resourceName: string, options: ResourceActionsOptions) {
|
||||
const resource = new ACLResource({
|
||||
role: this,
|
||||
name: resourceName,
|
||||
});
|
||||
|
||||
for (const [actionName, actionParams] of Object.entries(options)) {
|
||||
resource.setAction(actionName, actionParams);
|
||||
}
|
||||
|
||||
this.resources.set(resourceName, resource);
|
||||
}
|
||||
|
||||
public getResourceActionsParams(resourceName: string) {
|
||||
const resource = this.getResource(resourceName);
|
||||
return resource.getActions();
|
||||
}
|
||||
|
||||
public revokeResource(resourceName) {
|
||||
public revokeResource(resourceName: string) {
|
||||
for (const key of [...this.resources.keys()]) {
|
||||
if (key === resourceName || key.includes(`${resourceName}.`)) {
|
||||
this.resources.delete(key);
|
||||
|
@ -4,10 +4,10 @@ import EventEmitter from 'events';
|
||||
import parse from 'json-templates';
|
||||
import compose from 'koa-compose';
|
||||
import lodash from 'lodash';
|
||||
import { AclAvailableAction, AvailableActionOptions } from './acl-available-action';
|
||||
import { ACLAvailableAction, AvailableActionOptions } from './acl-available-action';
|
||||
import { ACLAvailableStrategy, AvailableStrategyOptions, predicate } from './acl-available-strategy';
|
||||
import { ACLRole, RoleActionParams } from './acl-role';
|
||||
import { AllowManager } from './allow-manager';
|
||||
import { ACLRole, ResourceActionsOptions, RoleActionParams } from './acl-role';
|
||||
import { AllowManager, ConditionFunc } from './allow-manager';
|
||||
|
||||
interface CanResult {
|
||||
role: string;
|
||||
@ -19,10 +19,8 @@ interface CanResult {
|
||||
export interface DefineOptions {
|
||||
role: string;
|
||||
allowConfigure?: boolean;
|
||||
strategy?: string | Omit<AvailableStrategyOptions, 'acl'>;
|
||||
actions?: {
|
||||
[key: string]: RoleActionParams;
|
||||
};
|
||||
strategy?: string | AvailableStrategyOptions;
|
||||
actions?: ResourceActionsOptions;
|
||||
routes?: any;
|
||||
}
|
||||
|
||||
@ -44,7 +42,7 @@ interface CanArgs {
|
||||
}
|
||||
|
||||
export class ACL extends EventEmitter {
|
||||
protected availableActions = new Map<string, AclAvailableAction>();
|
||||
protected availableActions = new Map<string, ACLAvailableAction>();
|
||||
protected availableStrategy = new Map<string, ACLAvailableStrategy>();
|
||||
protected middlewares: Toposort<any>;
|
||||
|
||||
@ -131,7 +129,7 @@ export class ACL extends EventEmitter {
|
||||
}
|
||||
|
||||
setAvailableAction(name: string, options: AvailableActionOptions = {}) {
|
||||
this.availableActions.set(name, new AclAvailableAction(name, options));
|
||||
this.availableActions.set(name, new ACLAvailableAction(name, options));
|
||||
|
||||
if (options.aliases) {
|
||||
const aliases = lodash.isArray(options.aliases) ? options.aliases : [options.aliases];
|
||||
@ -150,7 +148,7 @@ export class ACL extends EventEmitter {
|
||||
return this.availableActions;
|
||||
}
|
||||
|
||||
setAvailableStrategy(name: string, options: Omit<AvailableStrategyOptions, 'acl'>) {
|
||||
setAvailableStrategy(name: string, options: AvailableStrategyOptions) {
|
||||
this.availableStrategy.set(name, new ACLAvailableStrategy(this, options));
|
||||
}
|
||||
|
||||
@ -222,7 +220,7 @@ export class ACL extends EventEmitter {
|
||||
this.middlewares.add(fn, options);
|
||||
}
|
||||
|
||||
allow(resourceName: string, actionNames: string[] | string, condition?: any) {
|
||||
allow(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc) {
|
||||
if (!Array.isArray(actionNames)) {
|
||||
actionNames = [actionNames];
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ACL } from './acl';
|
||||
|
||||
type ConditionFunc = (ctx: any) => Promise<boolean>;
|
||||
export type ConditionFunc = (ctx: any) => Promise<boolean>;
|
||||
|
||||
export class AllowManager {
|
||||
protected skipActions = new Map<string, Map<string, string | ConditionFunc | true>>();
|
||||
@ -20,7 +20,7 @@ export class AllowManager {
|
||||
|
||||
const roleInstance = await ctx.db.getRepository('roles').findOne({
|
||||
filter: {
|
||||
name: roleName
|
||||
name: roleName,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -4,4 +4,3 @@ export * from './acl-available-strategy';
|
||||
export * from './acl-resource';
|
||||
export * from './acl-role';
|
||||
export * from './skip-middleware';
|
||||
|
||||
|
@ -31,6 +31,23 @@ describe('has one repository', () => {
|
||||
await db.sync();
|
||||
});
|
||||
|
||||
test('create', async () => {
|
||||
const user = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
const userProfileRepository = new HasOneRepository(User, 'profile', user['id']);
|
||||
let profile = await userProfileRepository.find();
|
||||
|
||||
profile = await userProfileRepository.create({
|
||||
values: {
|
||||
avatar: 'avatar1',
|
||||
},
|
||||
});
|
||||
|
||||
console.log(profile.toJSON());
|
||||
});
|
||||
|
||||
test('find', async () => {
|
||||
const user = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
|
@ -20,7 +20,7 @@ export function getConfigByEnv() {
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
dialect: process.env.DB_DIALECT,
|
||||
dialect: process.env.DB_DIALECT || 'sqlite',
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
storage:
|
||||
process.env.DB_STORAGE && process.env.DB_STORAGE !== ':memory:'
|
||||
|
@ -53,7 +53,7 @@ export abstract class RelationRepository {
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async create(options?: CreateOptions): Promise<any> {
|
||||
async create(options?: CreateOptions): Promise<Model> {
|
||||
const createAccessor = this.accessors().create;
|
||||
|
||||
const guard = UpdateGuard.fromOptions(this.targetModel, options);
|
||||
|
@ -43,7 +43,7 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async find(options?: SingleRelationFindOption): Promise<Model<any>> {
|
||||
async find(options?: SingleRelationFindOption): Promise<Model<any> | null> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
const findOptions = this.buildQueryOptions({
|
||||
...options,
|
||||
|
Loading…
Reference in New Issue
Block a user