diff --git a/packages/resourcer/src/__tests__/resourcer.test.ts b/packages/resourcer/src/__tests__/resourcer.test.ts index 10c510badc..929ad53797 100644 --- a/packages/resourcer/src/__tests__/resourcer.test.ts +++ b/packages/resourcer/src/__tests__/resourcer.test.ts @@ -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); diff --git a/packages/resourcer/src/resourcer.ts b/packages/resourcer/src/resourcer.ts index 66ac924fcc..d45fb175ff 100644 --- a/packages/resourcer/src/resourcer.ts +++ b/packages/resourcer/src/resourcer.ts @@ -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); } diff --git a/packages/server/src/__tests__/middleware.test.ts b/packages/server/src/__tests__/middleware.test.ts new file mode 100644 index 0000000000..f417cfc9c9 --- /dev/null +++ b/packages/server/src/__tests__/middleware.test.ts @@ -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]); + }); +}); diff --git a/packages/server/src/applicatiion.ts b/packages/server/src/applicatiion.ts new file mode 100644 index 0000000000..f8b6272e8a --- /dev/null +++ b/packages/server/src/applicatiion.ts @@ -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; diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 46bacb7662..b50145b21c 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -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; diff --git a/packages/server/src/middleware.ts b/packages/server/src/middleware.ts new file mode 100644 index 0000000000..e85e3b8aed --- /dev/null +++ b/packages/server/src/middleware.ts @@ -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) => { + 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(); + } + } +} \ No newline at end of file