feat: initializing resources from the database

This commit is contained in:
chen 2020-11-11 18:16:18 +08:00
parent b00c24d5c6
commit 812cd0766d
6 changed files with 250 additions and 52 deletions

View File

@ -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);

View File

@ -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);
} }

View 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]);
});
});

View 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;

View File

@ -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;

View 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();
}
}
}