mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 08:21:53 +00:00
feat: initializing resources from the database
This commit is contained in:
parent
b00c24d5c6
commit
812cd0766d
@ -127,7 +127,7 @@ describe('resourcer', () => {
|
|||||||
const resourcer = new Resourcer();
|
const resourcer = new Resourcer();
|
||||||
|
|
||||||
resourcer.registerHandlers({
|
resourcer.registerHandlers({
|
||||||
'test.list': async(ctx, next) => {
|
'test:list': async(ctx, next) => {
|
||||||
ctx.arr.push(1);
|
ctx.arr.push(1);
|
||||||
await next();
|
await next();
|
||||||
ctx.arr.push(2);
|
ctx.arr.push(2);
|
||||||
|
@ -165,6 +165,10 @@ export class Resourcer {
|
|||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDefined(name: string) {
|
||||||
|
return this.resources.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册全局的 action handlers
|
* 注册全局的 action handlers
|
||||||
*
|
*
|
||||||
@ -196,7 +200,7 @@ export class Resourcer {
|
|||||||
getAction(name: string, action: ActionName): Action {
|
getAction(name: string, action: ActionName): Action {
|
||||||
// 支持注册局部 action
|
// 支持注册局部 action
|
||||||
if (this.handlers.has(`${name}.${action}`)) {
|
if (this.handlers.has(`${name}.${action}`)) {
|
||||||
return this.getResource(name).getAction(`${name}.${action}`);
|
return this.getResource(name).getAction(`${name}:${action}`);
|
||||||
}
|
}
|
||||||
return this.getResource(name).getAction(action);
|
return this.getResource(name).getAction(action);
|
||||||
}
|
}
|
||||||
|
87
packages/server/src/__tests__/middleware.test.ts
Normal file
87
packages/server/src/__tests__/middleware.test.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import Koa from 'koa';
|
||||||
|
import request from 'supertest';
|
||||||
|
import http from 'http';
|
||||||
|
import Resourcer from '@nocobase/resourcer';
|
||||||
|
import Database from '@nocobase/database';
|
||||||
|
import middleware from '../middleware';
|
||||||
|
|
||||||
|
describe('middleware', () => {
|
||||||
|
let app: Koa;
|
||||||
|
let resourcer: Resourcer;
|
||||||
|
let database: Database;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
app = new Koa();
|
||||||
|
resourcer = new Resourcer();
|
||||||
|
resourcer.registerHandlers({
|
||||||
|
list: async (ctx, next) => {
|
||||||
|
ctx.body = [1,2];
|
||||||
|
await next();
|
||||||
|
},
|
||||||
|
get: async (ctx, next) => {
|
||||||
|
ctx.body = [3,4];
|
||||||
|
await next();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log({
|
||||||
|
username: process.env.TEST_DB_USER,
|
||||||
|
password: process.env.TEST_DB_PASSWORD,
|
||||||
|
database: process.env.TEST_DB_DATABASE,
|
||||||
|
host: process.env.TEST_DB_HOST,
|
||||||
|
port: process.env.TEST_DB_PORT as any,
|
||||||
|
dialect: process.env.TEST_DB_DIALECT as any,
|
||||||
|
dialectOptions: {
|
||||||
|
charset: 'utf8mb4',
|
||||||
|
collate: 'utf8mb4_unicode_ci',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
database = new Database({
|
||||||
|
username: process.env.TEST_DB_USER,
|
||||||
|
password: process.env.TEST_DB_PASSWORD,
|
||||||
|
database: process.env.TEST_DB_DATABASE,
|
||||||
|
host: process.env.TEST_DB_HOST,
|
||||||
|
port: process.env.TEST_DB_PORT as any,
|
||||||
|
dialect: process.env.TEST_DB_DIALECT as any,
|
||||||
|
dialectOptions: {
|
||||||
|
charset: 'utf8mb4',
|
||||||
|
collate: 'utf8mb4_unicode_ci',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.use(middleware({
|
||||||
|
prefix: '/api',
|
||||||
|
database,
|
||||||
|
resourcer,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
it('shound work', async () => {
|
||||||
|
database.table({
|
||||||
|
name: 'tests',
|
||||||
|
});
|
||||||
|
const response = await request(http.createServer(app.callback())).get('/api/tests');
|
||||||
|
expect(response.body).toEqual([1,2]);
|
||||||
|
});
|
||||||
|
it('shound work', async () => {
|
||||||
|
database.table({
|
||||||
|
name: 'foos',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'hasmany',
|
||||||
|
name: 'bars',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
database.table({
|
||||||
|
name: 'bars',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'foo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
let response = await request(http.createServer(app.callback())).get('/api/foos/1/bars');
|
||||||
|
expect(response.body).toEqual([1,2]);
|
||||||
|
response = await request(http.createServer(app.callback())).get('/api/bars/1/foo');
|
||||||
|
expect(response.body).toEqual([3,4]);
|
||||||
|
});
|
||||||
|
});
|
33
packages/server/src/applicatiion.ts
Normal file
33
packages/server/src/applicatiion.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Koa from 'koa';
|
||||||
|
import Database from '@nocobase/database';
|
||||||
|
import Resourcer, { Action, ParsedParams } from '@nocobase/resourcer';
|
||||||
|
|
||||||
|
export class Application extends Koa {
|
||||||
|
|
||||||
|
database: Database;
|
||||||
|
|
||||||
|
resourcer: Resourcer;
|
||||||
|
|
||||||
|
async plugins(plugins: any[]) {
|
||||||
|
for (const pluginOption of plugins) {
|
||||||
|
let plugin: Function;
|
||||||
|
let options = {};
|
||||||
|
if (Array.isArray(pluginOption)) {
|
||||||
|
plugin = pluginOption.shift();
|
||||||
|
options = pluginOption.shift()||{};
|
||||||
|
if (typeof plugin === 'function') {
|
||||||
|
plugin = plugin.bind(this);
|
||||||
|
} else if (typeof plugin === 'string') {
|
||||||
|
const libDir = __filename.endsWith('.ts') ? 'src' : 'lib';
|
||||||
|
plugin = require(`${plugin}/${libDir}/server`).default;
|
||||||
|
plugin = plugin.bind(this);
|
||||||
|
}
|
||||||
|
} else if (typeof pluginOption === 'function') {
|
||||||
|
plugin = pluginOption.bind(this);
|
||||||
|
}
|
||||||
|
await plugin(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Application;
|
@ -1,47 +1,10 @@
|
|||||||
import Koa from 'koa';
|
|
||||||
import Database from '@nocobase/database';
|
import Database from '@nocobase/database';
|
||||||
import Resourcer, { Action, ParsedParams } from '@nocobase/resourcer';
|
import Resourcer, { Action } from '@nocobase/resourcer';
|
||||||
import actions from '@nocobase/actions';
|
import actions from '@nocobase/actions';
|
||||||
import Router from '@koa/router';
|
import Application from './applicatiion';
|
||||||
import cors from '@koa/cors';
|
|
||||||
import bodyParser from 'koa-bodyparser';
|
import bodyParser from 'koa-bodyparser';
|
||||||
|
import cors from '@koa/cors';
|
||||||
export class Application extends Koa {
|
import middleware from './middleware';
|
||||||
|
|
||||||
database: Database;
|
|
||||||
|
|
||||||
router: Router;
|
|
||||||
|
|
||||||
resourcer: Resourcer;
|
|
||||||
|
|
||||||
uiResourcer: Resourcer;
|
|
||||||
|
|
||||||
async plugins(plugins: any[]) {
|
|
||||||
for (const pluginOption of plugins) {
|
|
||||||
let plugin: Function;
|
|
||||||
let options = {};
|
|
||||||
if (Array.isArray(pluginOption)) {
|
|
||||||
plugin = pluginOption.shift();
|
|
||||||
options = pluginOption.shift()||{};
|
|
||||||
if (typeof plugin === 'function') {
|
|
||||||
plugin = plugin.bind(this);
|
|
||||||
} else if (typeof plugin === 'string') {
|
|
||||||
const libDir = __filename.endsWith('.ts') ? 'src' : 'lib';
|
|
||||||
plugin = require(`${plugin}/${libDir}/server`).default;
|
|
||||||
plugin = plugin.bind(this);
|
|
||||||
}
|
|
||||||
} else if (typeof pluginOption === 'function') {
|
|
||||||
plugin = pluginOption.bind(this);
|
|
||||||
}
|
|
||||||
await plugin(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getNameByParams(params: ParsedParams): string {
|
|
||||||
const { resourceName, associatedName } = params;
|
|
||||||
return associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
create(options: any): Application {
|
create(options: any): Application {
|
||||||
@ -49,14 +12,10 @@ export default {
|
|||||||
|
|
||||||
const app = new Application();
|
const app = new Application();
|
||||||
const database = new Database(options.database);
|
const database = new Database(options.database);
|
||||||
const router = new Router();
|
|
||||||
const resourcer = new Resourcer();
|
const resourcer = new Resourcer();
|
||||||
const uiResourcer = new Resourcer();
|
|
||||||
|
|
||||||
app.database = database;
|
app.database = database;
|
||||||
app.router = router;
|
|
||||||
app.resourcer = resourcer;
|
app.resourcer = resourcer;
|
||||||
app.uiResourcer = uiResourcer;
|
|
||||||
|
|
||||||
app.use(bodyParser());
|
app.use(bodyParser());
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
@ -89,12 +48,11 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(resourcer.middleware(options.resourcer || {
|
app.use(middleware({
|
||||||
prefix: '/api',
|
prefix: '/api',
|
||||||
}));
|
database,
|
||||||
|
resourcer,
|
||||||
app.use(uiResourcer.middleware(options.uiResourcer || {
|
...(options.resourcer||{}),
|
||||||
prefix: '/api/ui',
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
116
packages/server/src/middleware.ts
Normal file
116
packages/server/src/middleware.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import qs from 'qs';
|
||||||
|
import compose from 'koa-compose';
|
||||||
|
import { pathToRegexp } from 'path-to-regexp';
|
||||||
|
import Resourcer, { getNameByParams, KoaMiddlewareOptions, parseRequest, ResourcerContext } from '@nocobase/resourcer';
|
||||||
|
import Database, { BelongsTo, BelongsToMany, HasMany, HasOne } from '@nocobase/database';
|
||||||
|
|
||||||
|
interface MiddlewareOptions extends KoaMiddlewareOptions {
|
||||||
|
resourcer?: Resourcer;
|
||||||
|
database?: Database;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function middleware(options: MiddlewareOptions = {}) {
|
||||||
|
const {
|
||||||
|
prefix,
|
||||||
|
database,
|
||||||
|
resourcer,
|
||||||
|
accessors,
|
||||||
|
paramsKey = 'params',
|
||||||
|
nameRule = getNameByParams,
|
||||||
|
} = options;
|
||||||
|
console.log(resourcer)
|
||||||
|
return async (ctx: ResourcerContext, next: () => Promise<any>) => {
|
||||||
|
ctx.resourcer = resourcer;
|
||||||
|
let params = parseRequest({
|
||||||
|
path: ctx.request.path,
|
||||||
|
method: ctx.request.method,
|
||||||
|
}, {
|
||||||
|
prefix,
|
||||||
|
accessors,
|
||||||
|
});
|
||||||
|
if (!params) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const resourceName = nameRule(params);
|
||||||
|
if (!resourcer.isDefined(resourceName)) {
|
||||||
|
console.log('undefined')
|
||||||
|
const names = resourceName.split('.');
|
||||||
|
const tableName = names.shift();
|
||||||
|
if (database.isDefined(tableName)) {
|
||||||
|
const table = database.getTable(tableName);
|
||||||
|
const field = table.getField(names[0]) as BelongsTo | HasMany | BelongsToMany | HasOne;
|
||||||
|
if (names.length == 0 || field) {
|
||||||
|
let resourceType = 'single';
|
||||||
|
if (field) {
|
||||||
|
if (field instanceof HasOne) {
|
||||||
|
resourceType = 'hasOne';
|
||||||
|
} else if (field instanceof HasMany) {
|
||||||
|
resourceType = 'hasMany';
|
||||||
|
} else if (field instanceof BelongsTo) {
|
||||||
|
resourceType = 'belongsTo';
|
||||||
|
} else if (field instanceof BelongsToMany) {
|
||||||
|
resourceType = 'belongsToMany';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resourcer.define({
|
||||||
|
type: resourceType as any,
|
||||||
|
name: resourceName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const resource = resourcer.getResource(resourceName);
|
||||||
|
// 为关系资源时,暂时需要再执行一遍 parseRequest
|
||||||
|
if (resource.options.type !== 'single') {
|
||||||
|
params = parseRequest({
|
||||||
|
path: ctx.request.path,
|
||||||
|
method: ctx.request.method,
|
||||||
|
type: resource.options.type,
|
||||||
|
}, {
|
||||||
|
prefix,
|
||||||
|
accessors,
|
||||||
|
});
|
||||||
|
if (!params) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(resource);
|
||||||
|
// action 需要 clone 之后再赋给 ctx
|
||||||
|
ctx.action = resourcer.getAction(resourceName, params.actionName).clone();
|
||||||
|
console.log(ctx.action);
|
||||||
|
ctx.action.setContext(ctx);
|
||||||
|
// 自带 query 处理的不太给力,需要用 qs 转一下
|
||||||
|
const query = qs.parse(qs.stringify(ctx.query));
|
||||||
|
// filter 支持 json string
|
||||||
|
if (typeof query.filter === 'string') {
|
||||||
|
query.filter = JSON.parse(query.filter);
|
||||||
|
}
|
||||||
|
// 兼容 ctx.params 的处理,之后的版本里会去掉
|
||||||
|
ctx[paramsKey] = {
|
||||||
|
table: params.resourceName,
|
||||||
|
tableKey: params.resourceKey,
|
||||||
|
relatedTable: params.associatedName,
|
||||||
|
relatedKey: params.resourceKey,
|
||||||
|
action: params.actionName,
|
||||||
|
};
|
||||||
|
if (pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}').test(ctx.request.path)) {
|
||||||
|
await ctx.action.mergeParams({
|
||||||
|
...query,
|
||||||
|
...params,
|
||||||
|
...ctx.request.body,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await ctx.action.mergeParams({
|
||||||
|
...query,
|
||||||
|
...params,
|
||||||
|
values: ctx.request.body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return compose(ctx.action.getHandlers())(ctx, next);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user