mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 05:57: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();
|
||||
|
||||
resourcer.registerHandlers({
|
||||
'test.list': async(ctx, next) => {
|
||||
'test:list': async(ctx, next) => {
|
||||
ctx.arr.push(1);
|
||||
await next();
|
||||
ctx.arr.push(2);
|
||||
|
@ -165,6 +165,10 @@ export class Resourcer {
|
||||
return resource;
|
||||
}
|
||||
|
||||
isDefined(name: string) {
|
||||
return this.resources.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册全局的 action handlers
|
||||
*
|
||||
@ -196,7 +200,7 @@ export class Resourcer {
|
||||
getAction(name: string, action: ActionName): Action {
|
||||
// 支持注册局部 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);
|
||||
}
|
||||
|
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 Resourcer, { Action, ParsedParams } from '@nocobase/resourcer';
|
||||
import Resourcer, { Action } from '@nocobase/resourcer';
|
||||
import actions from '@nocobase/actions';
|
||||
import Router from '@koa/router';
|
||||
import cors from '@koa/cors';
|
||||
import Application from './applicatiion';
|
||||
import bodyParser from 'koa-bodyparser';
|
||||
|
||||
export class Application extends Koa {
|
||||
|
||||
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;
|
||||
}
|
||||
import cors from '@koa/cors';
|
||||
import middleware from './middleware';
|
||||
|
||||
export default {
|
||||
create(options: any): Application {
|
||||
@ -49,14 +12,10 @@ export default {
|
||||
|
||||
const app = new Application();
|
||||
const database = new Database(options.database);
|
||||
const router = new Router();
|
||||
const resourcer = new Resourcer();
|
||||
const uiResourcer = new Resourcer();
|
||||
|
||||
app.database = database;
|
||||
app.router = router;
|
||||
app.resourcer = resourcer;
|
||||
app.uiResourcer = uiResourcer;
|
||||
|
||||
app.use(bodyParser());
|
||||
app.use(cors());
|
||||
@ -89,12 +48,11 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
app.use(resourcer.middleware(options.resourcer || {
|
||||
app.use(middleware({
|
||||
prefix: '/api',
|
||||
}));
|
||||
|
||||
app.use(uiResourcer.middleware(options.uiResourcer || {
|
||||
prefix: '/api/ui',
|
||||
database,
|
||||
resourcer,
|
||||
...(options.resourcer||{}),
|
||||
}));
|
||||
|
||||
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