feat: add examples (#718)
* feat: add examples * fix: tsconfig-paths/register * feat: more examples * fix: test errors * feat: update examples * docs: example list * fix: updates * feat: example for test case * feat: api client * fix: updates * feat: tree structures * fix: adjacency-list
This commit is contained in:
parent
7fde444a60
commit
28b73d7143
25
examples/api-client/api.request.ts
Normal file
25
examples/api-client/api.request.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
# 客户端常规请求
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1: 启动服务器
|
||||
yarn run:example api-client/server start
|
||||
|
||||
Step 2: 客户端常规请求 —— api.request()
|
||||
yarn run:example api-client/api.request
|
||||
*/
|
||||
import { APIClient } from '@nocobase/sdk';
|
||||
|
||||
const api = new APIClient({
|
||||
baseURL: 'http://localhost:13000/api',
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const response = await api.request({
|
||||
url: 'test:list',
|
||||
});
|
||||
// 等价于
|
||||
// const response = await api.resource('test').list();
|
||||
console.log(response.data);
|
||||
})();
|
25
examples/api-client/api.resource.ts
Normal file
25
examples/api-client/api.resource.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
# 客户端资源请求
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1: 启动服务器
|
||||
yarn run:example api-client/server start
|
||||
|
||||
Step 2: 客户端资源请求 —— api.resource(name).action(params)
|
||||
yarn run:example api-client/api.resource
|
||||
*/
|
||||
import { APIClient } from '@nocobase/sdk';
|
||||
|
||||
const api = new APIClient({
|
||||
baseURL: 'http://localhost:13000/api',
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const response = await api.resource('test').list();
|
||||
// 等价于
|
||||
// const response = await api.request({
|
||||
// url: 'test:list',
|
||||
// });
|
||||
console.log(response.data);
|
||||
})();
|
51
examples/api-client/server.ts
Normal file
51
examples/api-client/server.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
# 客户端请求
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1: 启动服务器
|
||||
yarn run:example api-client/server start
|
||||
|
||||
Step 2: 客户端常规请求 —— api.request()
|
||||
yarn run:example api-client/api.request
|
||||
|
||||
Step 3: 客户端资源请求 —— api.resource(name).action(params)
|
||||
yarn run:example api-client/api.resource
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
// 定义了一个 test 资源,并提供了相对应的 list 方法
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async list(ctx, next) {
|
||||
ctx.body = 'test list';
|
||||
await next();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
33
examples/app/__tests__/app.test.ts
Normal file
33
examples/app/__tests__/app.test.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
# 编写 Application 测试用例
|
||||
|
||||
# 执行测试
|
||||
yarn jest examples/app/__tests__/app.test.ts
|
||||
*/
|
||||
import { MockServer, mockServer } from '@nocobase/test';
|
||||
|
||||
describe('app test', () => {
|
||||
let app: MockServer;
|
||||
|
||||
beforeEach(() => {
|
||||
app = mockServer();
|
||||
});
|
||||
|
||||
test('test1', async () => {
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async list(ctx, next) {
|
||||
ctx.body = 'test list';
|
||||
await next();
|
||||
},
|
||||
},
|
||||
});
|
||||
const response = await app.agent().resource('test').list();
|
||||
expect(response.body).toEqual({ data: 'test list' });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
});
|
69
examples/app/acl.ts
Normal file
69
examples/app/acl.ts
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Step 1:
|
||||
yarn run:example app/acl start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:export
|
||||
curl --location --request GET 'http://localhost:13000/api/test:export' --header 'X-Role: admin'
|
||||
curl --location --request GET 'http://localhost:13000/api/test:import' --header 'X-Role: admin'
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
app.acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'test:export': {
|
||||
fields: ['a1', 'b1']
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.resourcer.use(async (ctx, next) => {
|
||||
ctx.state.currentRole = ctx.get('X-Role');
|
||||
await next();
|
||||
});
|
||||
|
||||
app.resourcer.use(app.acl.middleware());
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async export(ctx, next) {
|
||||
ctx.body = {
|
||||
'ctx.action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
},
|
||||
async import(ctx, next) {
|
||||
ctx.body = {
|
||||
'ctx.action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
91
examples/app/association2resource.ts
Normal file
91
examples/app/association2resource.ts
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Step 1: 将 collections 同步给数据库(建表和字段)
|
||||
yarn run:example app/association2resource db:sync
|
||||
|
||||
Step 2:
|
||||
yarn run:example app/association2resource start
|
||||
|
||||
Step 3: Create article
|
||||
curl --location --request POST 'http://localhost:13000/api/articles:create' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"title": "My first article",
|
||||
"content": "Hello NocoBase!",
|
||||
"user": {
|
||||
"name": "user 1"
|
||||
}
|
||||
}'
|
||||
|
||||
Step 4: View article user
|
||||
curl http://localhost:13000/api/articles/1/user
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: 'a2r_',
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
// 已定义的 collection 会自动转为同名 resource
|
||||
app.collection({
|
||||
name: 'articles',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'content',
|
||||
},
|
||||
// 自动转为 articles.user 资源
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user',
|
||||
// 以下参数缺失时,自动处理
|
||||
// target: 'users',
|
||||
// foreignKey: 'userId',
|
||||
// targetKey: 'id',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 已定义的 collection 会自动转为同名 resource
|
||||
app.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
},
|
||||
// 自动转为 users.articles 资源
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'articles',
|
||||
// 以下参数缺失时,自动处理
|
||||
// target: 'articles',
|
||||
// foreignKey: 'userId',
|
||||
// sourceKey: 'id',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
59
examples/app/collection2resource.ts
Normal file
59
examples/app/collection2resource.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
Step 1: 将 collections 同步给数据库(建表和字段)
|
||||
yarn run:example app/collection2resource db:sync
|
||||
|
||||
Step 2: 启动应用
|
||||
yarn run:example app/collection2resource start
|
||||
|
||||
Step 3: Create article
|
||||
curl --location --request POST 'http://localhost:13000/api/articles:create' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"title": "My first article",
|
||||
"content": "Hello NocoBase!"
|
||||
}'
|
||||
|
||||
Step 4: View article list
|
||||
curl http://localhost:13000/api/articles:list
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: 'c2r_',
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
// 已定义的 collection 会自动转为同名 resource
|
||||
app.collection({
|
||||
name: 'articles',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'content',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
77
examples/app/context/ctx.action.mergeParams.ts
Normal file
77
examples/app/context/ctx.action.mergeParams.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
# ctx.action.mergeParams 的用法
|
||||
|
||||
一般是在中间件里通过 ctx.action.mergeParams() 方法合并参数
|
||||
|
||||
# 步骤:
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/context/ctx.action.mergeParams start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:list?filter%5Ba%5D=a2&fields=col1
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
});
|
||||
|
||||
async function list(ctx, next) {
|
||||
ctx.body = {
|
||||
'action.resourceName': ctx.action.resourceName,
|
||||
'action.resourceOf': ctx.action.resourceOf,
|
||||
'action.actionName': ctx.action.actionName,
|
||||
'action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
}
|
||||
|
||||
async function get(ctx, next) {
|
||||
ctx.body = {
|
||||
'action.resourceName': ctx.action.resourceName,
|
||||
'action.resourceOf': ctx.action.resourceOf,
|
||||
'action.actionName': ctx.action.actionName,
|
||||
'action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
}
|
||||
|
||||
app.resourcer.use(async (ctx, next) => {
|
||||
// 在 middleware 里修改 action.params
|
||||
ctx.action.mergeParams({
|
||||
filter: {
|
||||
a: 'a1',
|
||||
},
|
||||
fields: ['col1', 'col2'],
|
||||
});
|
||||
await next();
|
||||
});
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
list,
|
||||
get,
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
75
examples/app/context/ctx.action.ts
Normal file
75
examples/app/context/ctx.action.ts
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
# ctx.action 的重要参数说明
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/context/ctx.action start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:list
|
||||
curl http://localhost:13000/api/test/1/nest:get
|
||||
curl http://localhost:13000/api/test:list?filter%5Ba%5D=a&fields=a,b&sort=a,b
|
||||
*/
|
||||
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
});
|
||||
|
||||
async function list(ctx, next) {
|
||||
ctx.body = {
|
||||
'action.resourceName': ctx.action.resourceName,
|
||||
'action.resourceOf': ctx.action.resourceOf,
|
||||
'action.actionName': ctx.action.actionName,
|
||||
'action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
}
|
||||
|
||||
async function get(ctx, next) {
|
||||
ctx.body = {
|
||||
'action.resourceName': ctx.action.resourceName,
|
||||
'action.resourceOf': ctx.action.resourceOf,
|
||||
'action.actionName': ctx.action.actionName,
|
||||
'action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
}
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
list,
|
||||
get,
|
||||
},
|
||||
});
|
||||
|
||||
app.resource({
|
||||
name: 'test.nest',
|
||||
actions: {
|
||||
list,
|
||||
get,
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
56
examples/app/context/ctx.db.ts
Normal file
56
examples/app/context/ctx.db.ts
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
# ctx.db 用法
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/context/ctx.db start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
app.collection({
|
||||
name: 'articles',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'content',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.body = ctx.db.getCollection('articles').options;
|
||||
await next();
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
75
examples/app/context/ctx.i18n.ts
Normal file
75
examples/app/context/ctx.i18n.ts
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
# 国际化多语言设置
|
||||
|
||||
主要介绍 app.i18n 和 ctx.i18n 的区别
|
||||
|
||||
# 步骤:
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/context/ctx.i18n start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/?locale=en-US
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
i18n: {
|
||||
defaultNS: 'test',
|
||||
resources: {
|
||||
'en-US': {
|
||||
test: {
|
||||
hello: 'Hello',
|
||||
},
|
||||
},
|
||||
'zh-CN': {
|
||||
test: {
|
||||
hello: '你好',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.i18n.addResources('zh-CN', 'test', {
|
||||
world: '世界',
|
||||
});
|
||||
|
||||
app.i18n.addResources('en-US', 'test', {
|
||||
world: 'World',
|
||||
});
|
||||
|
||||
// 改变全局 app.i18n 的多语言,一般用于 cli 环境的多语言切换
|
||||
app.i18n.changeLanguage('zh-CN');
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
// ctx.i18n 是 app.i18n 的 clone instance
|
||||
ctx.body = {
|
||||
'ctx.i18n': ctx.i18n.language,
|
||||
'app.i18n': app.i18n.language,
|
||||
'ctx.t': ctx.t('hello') + ' ' + ctx.t('world'),
|
||||
'app.i18n.t': app.i18n.t('hello') + ' ' + app.i18n.t('world'),
|
||||
};
|
||||
await next();
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
39
examples/app/custom-command.ts
Normal file
39
examples/app/custom-command.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
# 自定义命令行
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/custom-command hello
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
app.command('hello').action(() => {
|
||||
console.log('hello cli');
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
||||
|
57
examples/app/custom-plugin.ts
Normal file
57
examples/app/custom-plugin.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
# 编写一个最简单的插件
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1: Start app
|
||||
yarn run:example plugins/custom-plugin start
|
||||
|
||||
Step 2: View test list
|
||||
http://localhost:13000/api/test:list
|
||||
*/
|
||||
import { Application, Plugin } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
// Encapsulate modules into a plugin
|
||||
class MyPlugin extends Plugin {
|
||||
getName() {
|
||||
return 'MyPlugin';
|
||||
}
|
||||
async load() {
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async list(ctx) {
|
||||
ctx.body = 'test list';
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register plugin
|
||||
app.plugin(MyPlugin);
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
72
examples/app/i18n.ts
Normal file
72
examples/app/i18n.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
# 国际化多语言
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/i18n start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:get
|
||||
curl http://localhost:13000/api/test:get?locale=en-US
|
||||
curl --location --request GET 'http://localhost:13000/api/test:get' --header 'X-Locale: en-US'
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
i18n: {
|
||||
defaultNS: 'test',
|
||||
resources: {
|
||||
'en-US': {
|
||||
test: {
|
||||
hello: 'Hello',
|
||||
},
|
||||
},
|
||||
'zh-CN': {
|
||||
test: {
|
||||
hello: '你好',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.i18n.addResources('zh-CN', 'test', {
|
||||
world: '世界',
|
||||
});
|
||||
|
||||
app.i18n.addResources('en-US', 'test', {
|
||||
world: 'World',
|
||||
});
|
||||
|
||||
// 改变全局 app.i18n 的多语言,一般用于 cli 环境的多语言
|
||||
app.i18n.changeLanguage('zh-CN');
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async get(ctx, next) {
|
||||
ctx.body = ctx.t('hello') + ' ' + ctx.t('world');
|
||||
await next();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
87
examples/app/middleware/acl.ts
Normal file
87
examples/app/middleware/acl.ts
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
# app.acl.use 用法
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/middleware/acl start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:export
|
||||
curl http://localhost:13000/api/test:export?skip=1
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
app.acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'test:export': {
|
||||
fields: ['a1', 'b1'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.acl.use(async (ctx, next) => {
|
||||
ctx.permission = {
|
||||
// 是否跳过 acl 判断
|
||||
skip: !!ctx.request.query.skip,
|
||||
// 如果 skip=true 不处理
|
||||
// 如果 skip=false,can.params 会通过 ctx.action.mergeParams() 合并到 ctx.action.params
|
||||
can: {
|
||||
params: {
|
||||
fields: ['a1', 'b1', 'b3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
// acl 中间件里也可以直接给 body 赋值
|
||||
ctx.body = {
|
||||
test: 'test',
|
||||
};
|
||||
await next();
|
||||
});
|
||||
|
||||
app.resourcer.use(async (ctx, next) => {
|
||||
// 当前角色
|
||||
ctx.state.currentRole = ctx.get('X-Role');
|
||||
await next();
|
||||
});
|
||||
|
||||
app.resourcer.use(app.acl.middleware());
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async export(ctx, next) {
|
||||
ctx.body = {
|
||||
...ctx.body,
|
||||
'ctx.action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
43
examples/app/middleware/app.ts
Normal file
43
examples/app/middleware/app.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
# app.use 用法,与 Koa 相同
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/middleware/app start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
// Same as Koa
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.body = 'Hello NocoBase';
|
||||
await next();
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
60
examples/app/middleware/resourcer.ts
Normal file
60
examples/app/middleware/resourcer.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
# app.resourcer.use 用法
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/middleware/resourcer start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:list
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async list(ctx, next) {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push('test list');
|
||||
await next();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
app.resourcer.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push('resourcer middleware');
|
||||
await next();
|
||||
});
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push('app middleware');
|
||||
await next();
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
62
examples/app/migrations/add-migration.ts
Normal file
62
examples/app/migrations/add-migration.ts
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
# Application Migration
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/migrations/add-migration migrator up
|
||||
|
||||
Step 2:
|
||||
yarn run:example app/migrations/add-migration migrator down
|
||||
*/
|
||||
import { DataTypes } from '@nocobase/database';
|
||||
import { Application, Migration } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
class MyMigration extends Migration {
|
||||
async up() {
|
||||
/*
|
||||
可用的属性
|
||||
this.app;
|
||||
this.db;
|
||||
this.queryInterface;
|
||||
this.sequelize;
|
||||
*/
|
||||
await this.queryInterface.createTable('test', {
|
||||
name: DataTypes.STRING,
|
||||
});
|
||||
}
|
||||
|
||||
async down() {
|
||||
await this.queryInterface.dropTable('test');
|
||||
}
|
||||
}
|
||||
|
||||
app.db.addMigration({
|
||||
name: 'my-migration',
|
||||
migration: MyMigration,
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
@ -1,3 +1,11 @@
|
||||
/*
|
||||
# 支持多应用(子应用)
|
||||
|
||||
yarn run:example app/multi-app start
|
||||
|
||||
curl http://localhost:13000/api/test:list
|
||||
curl http://localhost:13000/sub1/api/test:list
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
import { IncomingMessage } from 'http';
|
||||
|
||||
@ -20,10 +28,6 @@ const app = new Application({
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
const subApp1 = app.appManager.createApplication('sub1', {
|
||||
database: app.db,
|
||||
resourcer: {
|
||||
@ -56,7 +60,8 @@ app.appManager.setAppSelector((req: IncomingMessage) => {
|
||||
return null;
|
||||
});
|
||||
|
||||
export default app;
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
// http://localhost:13000/api/test:list
|
||||
// http://localhost:13000/sub1/api/test:list
|
||||
export default app;
|
||||
|
91
examples/app/resource-actions/action-merge-params.ts
Normal file
91
examples/app/resource-actions/action-merge-params.ts
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
# Action 参数的多来源合并
|
||||
|
||||
# 步骤:
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/resource-actions/action-merge-params start
|
||||
|
||||
Step 2: 客户端请求时提供参数也是一种来源
|
||||
curl http://localhost:13000/api/test:list?filter%5Ba%5D=a2&fields=col1
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
});
|
||||
|
||||
// 来源1:resourcer.use 中间件里直接 action.mergeParams
|
||||
app.resourcer.use(async (ctx, next) => {
|
||||
// 在 middleware 里修改 action.params
|
||||
ctx.action.mergeParams({
|
||||
filter: {
|
||||
col1: 'val1',
|
||||
},
|
||||
fields: ['col1', 'col2', 'col4'],
|
||||
});
|
||||
await next();
|
||||
});
|
||||
|
||||
// 来源2:app.acl.use 中间件里的 ctx.permission.can.params
|
||||
app.acl.use(async (ctx, next) => {
|
||||
ctx.permission = {
|
||||
// 是否跳过 acl 判断
|
||||
skip: !!ctx.request.query.skip,
|
||||
// 如果 skip=true 不处理
|
||||
// 如果 skip=false,can.params 会通过 ctx.action.mergeParams() 合并到 ctx.action.params
|
||||
can: {
|
||||
params: {
|
||||
filter: {
|
||||
col1: 'val2',
|
||||
},
|
||||
fields: ['col1', 'col2', 'col3'],
|
||||
},
|
||||
},
|
||||
};
|
||||
await next();
|
||||
});
|
||||
|
||||
app.resourcer.use(app.acl.middleware());
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
// 来源 3:直接配置在 resource 的 action 里
|
||||
list: {
|
||||
filter: {
|
||||
col1: 'val3',
|
||||
},
|
||||
fields: ['col1', 'col2', 'col3', 'col4', 'col5'],
|
||||
handler: async (ctx, next) => {
|
||||
ctx.body = {
|
||||
'action.resourceName': ctx.action.resourceName,
|
||||
'action.resourceOf': ctx.action.resourceOf,
|
||||
'action.actionName': ctx.action.actionName,
|
||||
'action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
54
examples/app/resource-actions/action-with-default-options.ts
Normal file
54
examples/app/resource-actions/action-with-default-options.ts
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
# 带默认参数的 Action
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/resource-actions/action-with-default-options start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:list
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
find: {
|
||||
filter: {
|
||||
field1: 'value1',
|
||||
},
|
||||
handler: async (ctx, next) => {
|
||||
ctx.body = {
|
||||
'ctx.action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
68
examples/app/resource-actions/global-action.ts
Normal file
68
examples/app/resource-actions/global-action.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
# 使用全局 Action
|
||||
|
||||
全局 action 可用于任意 resource 中
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/resource-actions/global-action start
|
||||
|
||||
Step 2: test:export 的 action.params 带 fields
|
||||
curl http://localhost:13000/api/test:export
|
||||
|
||||
Step 3: test:import 有效
|
||||
curl http://localhost:13000/api/test:import
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
app.resourcer.registerActionHandlers({
|
||||
async import(ctx, next) {
|
||||
ctx.body = {
|
||||
'ctx.action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
},
|
||||
async export(ctx, next) {
|
||||
ctx.body = {
|
||||
'ctx.action.params': ctx.action.params,
|
||||
};
|
||||
await next();
|
||||
},
|
||||
});
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
// 全局的 actions 如果有默认参数可以在 actions 里配置
|
||||
actions: {
|
||||
export: {
|
||||
fields: ['field1', 'field2'],
|
||||
},
|
||||
// 如果没有默认参数,可以不配置,如 import action
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
47
examples/app/resource-actions/simple.ts
Normal file
47
examples/app/resource-actions/simple.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
# 最简单的 resource actions
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/resource-actions/simple start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:list
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: process.env.DB_TABLE_PREFIX,
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async list(ctx, next) {
|
||||
ctx.body = 'test list';
|
||||
await next();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (require.main === module) {
|
||||
app.runAsCLI();
|
||||
}
|
||||
|
||||
export default app;
|
@ -1,3 +1,14 @@
|
||||
/*
|
||||
# 最简单的单应用
|
||||
|
||||
# 步骤
|
||||
|
||||
Step 1:
|
||||
yarn run:example app/single-app start
|
||||
|
||||
Step 2:
|
||||
curl http://localhost:13000/api/test:list
|
||||
*/
|
||||
import { Application } from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
@ -19,11 +30,13 @@ const app = new Application({
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
// 定义了一个 test 资源,并提供了相对应的 list 方法
|
||||
app.resource({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async list(ctx) {
|
||||
async list(ctx, next) {
|
||||
ctx.body = 'test list';
|
||||
await next();
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -33,5 +46,3 @@ if (require.main === module) {
|
||||
}
|
||||
|
||||
export default app;
|
||||
|
||||
// http://localhost:13000/api/test:list
|
54
examples/database/collections/tree/adjacency-list.ts
Normal file
54
examples/database/collections/tree/adjacency-list.ts
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
# 树结构设计 —— 邻接表
|
||||
|
||||
yarn run:example database/collections/tree/adjacency-list
|
||||
*/
|
||||
import { Database } from '@nocobase/database';
|
||||
import { uid } from '@nocobase/utils';
|
||||
|
||||
const db = new Database({
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: `t_${uid()}_`,
|
||||
});
|
||||
|
||||
db.collection({
|
||||
name: 'categories',
|
||||
tree: 'adjacency-list',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'parent',
|
||||
target: 'categories',
|
||||
foreignKey: 'parentId',
|
||||
},
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'children',
|
||||
target: 'categories',
|
||||
foreignKey: 'parentId',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await db.sync();
|
||||
await db.close();
|
||||
})();
|
||||
|
||||
export default db;
|
50
examples/database/collections/tree/closure-table.ts
Normal file
50
examples/database/collections/tree/closure-table.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
# 树结构设计 —— 闭包表
|
||||
|
||||
yarn run:example database/collections/tree/closure-table
|
||||
*/
|
||||
import { Database } from '@nocobase/database';
|
||||
import { uid } from '@nocobase/utils';
|
||||
|
||||
const db = new Database({
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: `t_${uid()}_`,
|
||||
});
|
||||
|
||||
db.collection({
|
||||
name: 'categories',
|
||||
tree: 'closure-table',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
},
|
||||
{
|
||||
type: 'treeParent',
|
||||
name: 'parent',
|
||||
},
|
||||
{
|
||||
type: 'treeChildren',
|
||||
name: 'children',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await db.sync();
|
||||
await db.close();
|
||||
})();
|
||||
|
||||
export default db;
|
50
examples/database/collections/tree/materialized-path.ts
Normal file
50
examples/database/collections/tree/materialized-path.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
# 树结构设计 —— 路径枚举
|
||||
|
||||
yarn run:example database/collections/tree/materialized-path
|
||||
*/
|
||||
import { Database } from '@nocobase/database';
|
||||
import { uid } from '@nocobase/utils';
|
||||
|
||||
const db = new Database({
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: `t_${uid()}_`,
|
||||
});
|
||||
|
||||
db.collection({
|
||||
name: 'categories',
|
||||
tree: 'materialized-path',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
},
|
||||
{
|
||||
type: 'treeParent',
|
||||
name: 'parent',
|
||||
},
|
||||
{
|
||||
type: 'treeChildren',
|
||||
name: 'children',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await db.sync();
|
||||
await db.close();
|
||||
})();
|
||||
|
||||
export default db;
|
50
examples/database/collections/tree/nested-set.ts
Normal file
50
examples/database/collections/tree/nested-set.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
# 树结构设计 —— 嵌套集
|
||||
|
||||
yarn run:example database/collections/tree/nested-set
|
||||
*/
|
||||
import { Database } from '@nocobase/database';
|
||||
import { uid } from '@nocobase/utils';
|
||||
|
||||
const db = new Database({
|
||||
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
storage: process.env.DB_STORAGE,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT as any,
|
||||
timezone: process.env.DB_TIMEZONE,
|
||||
tablePrefix: `t_${uid()}_`,
|
||||
});
|
||||
|
||||
db.collection({
|
||||
name: 'categories',
|
||||
tree: 'nested-set',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
},
|
||||
{
|
||||
type: 'treeParent',
|
||||
name: 'parent',
|
||||
},
|
||||
{
|
||||
type: 'treeChildren',
|
||||
name: 'children',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await db.sync();
|
||||
await db.close();
|
||||
})();
|
||||
|
||||
export default db;
|
47
examples/index.md
Normal file
47
examples/index.md
Normal file
@ -0,0 +1,47 @@
|
||||
# 示例
|
||||
|
||||
## Application
|
||||
|
||||
- [最简单的单应用](./app/single-app.ts)
|
||||
- [支持多应用(子应用)](./app/multi-app.ts)
|
||||
- 配置 Resources 和 Actions
|
||||
- [最简单的 resource actions](./app/resource-actions/simple.ts)
|
||||
- [带默认参数的 Action](./app/resource-actions/action-with-default-options.ts)
|
||||
- [使用全局 Action](./app/resource-actions/global-action.ts)
|
||||
- [Action 参数的多来源合并](./app/resource-actions/action-merge-params.ts)
|
||||
- 内置 Actions 的用法
|
||||
- [Collection 自动转 Resource](./app/collection2resource.ts)
|
||||
- [Association 自动转 Resource](./app/association2resource.ts)
|
||||
- Context
|
||||
- [ctx.db 示例](./app/context/ctx.db.ts)
|
||||
- [ctx.action 的重要参数示例](./app/context/ctx.action.ts)
|
||||
- [ctx.action.mergeParams() 示例](./app/context/ctx.action.mergeParams.ts)
|
||||
- [ctx.i18n & ctx.t() 示例](./app/context/ctx.i18n.ts)
|
||||
- 中间件
|
||||
- [app.use 用法](./app/middleware/app.ts)
|
||||
- [app.resourcer.use 用法](./app/middleware/resourcer.ts)
|
||||
- [app.acl.use 用法](./app/middleware/acl.ts)
|
||||
- [ACL](./app/acl.ts)
|
||||
- [国际化多语言](./app/i18n.ts)
|
||||
- [自定义命令行](./app/custom-command.ts)
|
||||
- [编写一个最简单的插件](./app/custom-plugin.ts)
|
||||
- Application Migration
|
||||
- [编写一个新的 Migration 文件](./app/migrations/add-migration.ts)
|
||||
- 编写 Application 测试用例
|
||||
- [最简单的测试用例](./app/__tests__/app.test.ts)
|
||||
- 客户端 SDK(APIClient)示例
|
||||
- [客户端常规请求 —— api.request()](./api-client/api.request.ts)
|
||||
- [客户端资源请求 —— api.resource().action()](./api-client/api.resource.ts)
|
||||
|
||||
## Database
|
||||
|
||||
- 配置 Collections & Fields
|
||||
- 通过 Repository 增删改查数据
|
||||
- 通过 Model 增删改查数据
|
||||
- 关系数据的增删改查
|
||||
- 关系数据的关联操作
|
||||
- 扩展字段
|
||||
- 数据库事件
|
||||
- 数据库迁移
|
||||
|
||||
## Client
|
14
examples/index.ts
Normal file
14
examples/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import Database from '@nocobase/database';
|
||||
import Application from '@nocobase/server';
|
||||
|
||||
const argv = process.argv;
|
||||
const path = argv.splice(2, 1).shift();
|
||||
const app = require(`./${path}`).default;
|
||||
|
||||
if (app instanceof Application) {
|
||||
app.runAsCLI(argv);
|
||||
}
|
||||
|
||||
if (app instanceof Database) {
|
||||
console.log('Table prefix: ', app.getTablePrefix());
|
||||
}
|
@ -24,7 +24,7 @@
|
||||
"version:alpha": "lerna version prerelease --preid alpha --force-publish=* --no-git-tag-version -m \"chore(versions): publish packages %s\"",
|
||||
"release:force": "lerna publish from-package --yes",
|
||||
"release": "lerna publish",
|
||||
"run:example": "ts-node-dev -r dotenv/config"
|
||||
"run:example": "ts-node-dev -r dotenv/config -r tsconfig-paths/register ./examples/index.ts"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^17.0.0",
|
||||
|
@ -127,7 +127,7 @@ export class ACL extends EventEmitter {
|
||||
return this.configResources.includes(name);
|
||||
}
|
||||
|
||||
setAvailableAction(name: string, options: AvailableActionOptions) {
|
||||
setAvailableAction(name: string, options: AvailableActionOptions = {}) {
|
||||
this.availableActions.set(name, new AclAvailableAction(name, options));
|
||||
|
||||
if (options.aliases) {
|
||||
|
@ -21,7 +21,7 @@ import { Collection, CollectionOptions, RepositoryType } from './collection';
|
||||
import { ImporterReader, ImportFileExtension } from './collection-importer';
|
||||
import * as FieldTypes from './fields';
|
||||
import { Field, FieldContext, RelationField } from './fields';
|
||||
import { Migrations } from './migration';
|
||||
import { MigrationItem, Migrations } from './migration';
|
||||
import { Model } from './model';
|
||||
import { ModelHook } from './model-hook';
|
||||
import extendOperators from './operators';
|
||||
@ -205,7 +205,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
addMigration(item) {
|
||||
addMigration(item: MigrationItem) {
|
||||
return this.migrations.add(item);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
export { ModelCtor, Op, SyncOptions } from 'sequelize';
|
||||
export { DataTypes, ModelCtor, Op, SyncOptions } from 'sequelize';
|
||||
export * from './collection';
|
||||
export * from './database';
|
||||
export { Database as default } from './database';
|
||||
|
@ -206,7 +206,7 @@ describe('koa middleware', () => {
|
||||
sort: '-id',
|
||||
});
|
||||
expect(response.body).toMatchObject({
|
||||
sort: '-id',
|
||||
sort: ['-id'],
|
||||
filter: {
|
||||
$and: [
|
||||
{ col1: 'val1', col2: 'val2' },
|
||||
|
@ -32,6 +32,9 @@ function getKeys(target: any) {
|
||||
export const mergeStrategies = new Map<MergeStrategyType, MergeStrategyFunc>();
|
||||
|
||||
mergeStrategies.set('overwrite', (_, y) => {
|
||||
if (typeof y === 'string') {
|
||||
y = y.split(',');
|
||||
}
|
||||
return y;
|
||||
});
|
||||
|
||||
|
@ -49,7 +49,7 @@ describe('i18next', () => {
|
||||
});
|
||||
const response1 = await agent.get('/api/tests:get');
|
||||
expect(response1.text).toEqual('Hello');
|
||||
const response2 = await agent.get('/api/tests:get').set('Accept-Language', 'zh-CN');
|
||||
const response2 = await agent.get('/api/tests:get').set('X-Locale', 'zh-CN');
|
||||
expect(response2.text).toEqual('你好');
|
||||
const response3 = await agent.get('/api/tests:get?locale=zh-CN');
|
||||
expect(response3.text).toEqual('你好');
|
||||
|
@ -55,7 +55,12 @@ export function registerMiddlewares(app: Application, options: ApplicationOption
|
||||
const i18n = app.i18n.cloneInstance({ initImmediate: false });
|
||||
ctx.i18n = i18n;
|
||||
ctx.t = i18n.t.bind(i18n);
|
||||
const lng = ctx.get('X-Locale') || (ctx.request.query.locale as string) || ctx.acceptsLanguages().shift() || 'en-US';
|
||||
const lng =
|
||||
ctx.get('X-Locale') ||
|
||||
(ctx.request.query.locale as string) ||
|
||||
app.i18n.language ||
|
||||
ctx.acceptsLanguages().shift() ||
|
||||
'en-US';
|
||||
if (lng !== '*' && lng) {
|
||||
i18n.changeLanguage(lng);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user