refactor: improve server application
This commit is contained in:
parent
edbe1ecb67
commit
3fa9e59093
123
packages/server/src/__tests__/application.test.ts
Normal file
123
packages/server/src/__tests__/application.test.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import supertest from 'supertest';
|
||||
import { Application } from '../application';
|
||||
|
||||
describe('application', () => {
|
||||
let app: Application;
|
||||
let agent;
|
||||
|
||||
beforeEach(() => {
|
||||
app = new Application({
|
||||
database: {
|
||||
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,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
},
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
dataWrapping: false,
|
||||
});
|
||||
app.resourcer.registerActionHandlers({
|
||||
list: async (ctx, next) => {
|
||||
ctx.body = [1, 2];
|
||||
await next();
|
||||
},
|
||||
get: async (ctx, next) => {
|
||||
ctx.body = [3, 4];
|
||||
await next();
|
||||
},
|
||||
'foo2s.bar2s:list': async (ctx, next) => {
|
||||
ctx.body = [5, 6];
|
||||
await next();
|
||||
},
|
||||
});
|
||||
agent = supertest.agent(app.callback());
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
return app.database.close();
|
||||
});
|
||||
|
||||
it('resourcer.define', async () => {
|
||||
app.resourcer.define({
|
||||
name: 'test',
|
||||
});
|
||||
const response = await agent.get('/api/test');
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('resourcer.define', async () => {
|
||||
app.resourcer.define({
|
||||
type: 'hasMany',
|
||||
name: 'test.abc',
|
||||
});
|
||||
const response = await agent.get('/api/test/1/abc');
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('db.table', async () => {
|
||||
app.database.table({
|
||||
name: 'tests',
|
||||
});
|
||||
const response = await agent.get('/api/tests');
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('db.association', async () => {
|
||||
app.database.table({
|
||||
name: 'bars',
|
||||
});
|
||||
app.database.table({
|
||||
name: 'foos',
|
||||
fields: [
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'bars',
|
||||
}
|
||||
],
|
||||
});
|
||||
const response = await agent.get('/api/foos/1/bars');
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('db.middleware', async () => {
|
||||
const index = app.middleware.findIndex(m => m.name === 'table2resource');
|
||||
app.middleware.splice(index, 0, async (ctx, next) => {
|
||||
app.database.table({
|
||||
name: 'tests',
|
||||
});
|
||||
await next();
|
||||
});
|
||||
const response = await agent.get('/api/tests');
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('db.middleware', async () => {
|
||||
const index = app.middleware.findIndex(m => m.name === 'table2resource');
|
||||
app.middleware.splice(index, 0, async (ctx, next) => {
|
||||
app.database.table({
|
||||
name: 'bars',
|
||||
});
|
||||
app.database.table({
|
||||
name: 'foos',
|
||||
fields: [
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'bars',
|
||||
}
|
||||
],
|
||||
});
|
||||
await next();
|
||||
});
|
||||
console.log(app.middleware);
|
||||
const response = await agent.get('/api/foos/1/bars');
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
});
|
||||
});
|
57
packages/server/src/__tests__/dataWrapping.test.ts
Normal file
57
packages/server/src/__tests__/dataWrapping.test.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import supertest from 'supertest';
|
||||
import { Application } from '../application';
|
||||
|
||||
describe('application', () => {
|
||||
let app: Application;
|
||||
let agent;
|
||||
|
||||
beforeEach(() => {
|
||||
app = new Application({
|
||||
database: {
|
||||
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,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
},
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
dataWrapping: true,
|
||||
});
|
||||
app.resourcer.registerActionHandlers({
|
||||
list: async (ctx, next) => {
|
||||
ctx.body = [1, 2];
|
||||
await next();
|
||||
},
|
||||
get: async (ctx, next) => {
|
||||
ctx.body = [3, 4];
|
||||
await next();
|
||||
},
|
||||
'foo2s.bar2s:list': async (ctx, next) => {
|
||||
ctx.body = [5, 6];
|
||||
await next();
|
||||
},
|
||||
});
|
||||
agent = supertest.agent(app.callback());
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
return app.database.close();
|
||||
});
|
||||
|
||||
it('resourcer.define', async () => {
|
||||
app.resourcer.define({
|
||||
name: 'test',
|
||||
});
|
||||
const response = await agent.get('/api/test');
|
||||
expect(response.body).toEqual({
|
||||
data: [1, 2],
|
||||
});
|
||||
});
|
||||
});
|
@ -1,105 +0,0 @@
|
||||
import Koa from 'koa';
|
||||
import supertest 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;
|
||||
let agent;
|
||||
|
||||
beforeAll(() => {
|
||||
app = new Koa();
|
||||
resourcer = new Resourcer();
|
||||
resourcer.registerActionHandlers({
|
||||
list: async (ctx, next) => {
|
||||
ctx.body = [1, 2];
|
||||
await next();
|
||||
},
|
||||
get: async (ctx, next) => {
|
||||
ctx.body = [3, 4];
|
||||
await next();
|
||||
},
|
||||
'foo2s.bar2s:list': async (ctx, next) => {
|
||||
ctx.body = [5, 6];
|
||||
await next();
|
||||
},
|
||||
});
|
||||
database = new Database({
|
||||
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,
|
||||
dialect: process.env.DB_DIALECT as any,
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
},
|
||||
});
|
||||
agent = supertest.agent(app.callback());
|
||||
app.use(middleware({
|
||||
prefix: '/api',
|
||||
database,
|
||||
resourcer,
|
||||
}));
|
||||
});
|
||||
it('shound work', async () => {
|
||||
database.table({
|
||||
name: 'tests',
|
||||
});
|
||||
const response = await agent.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 agent.get('/api/foos/1/bars');
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
response = await agent.get('/api/bars/1/foo');
|
||||
expect(response.body).toEqual([3, 4]);
|
||||
});
|
||||
it('shound work', async () => {
|
||||
database.table({
|
||||
name: 'foo2s',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'bar2s',
|
||||
}
|
||||
]
|
||||
});
|
||||
database.table({
|
||||
name: 'bar2s',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'foo2s',
|
||||
},
|
||||
],
|
||||
});
|
||||
let response = await agent.get('/api/foo2s/1/bar2s');
|
||||
expect(response.body).toEqual([5, 6]);
|
||||
response = await agent.get('/api/bar2s/1/foo2s');
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
});
|
||||
});
|
@ -1,19 +1,24 @@
|
||||
import Koa from 'koa';
|
||||
import cors from '@koa/cors';
|
||||
import bodyParser from 'koa-bodyparser';
|
||||
import { Command } from 'commander';
|
||||
import Database, { DatabaseOptions } from '@nocobase/database';
|
||||
import Resourcer from '@nocobase/resourcer';
|
||||
import { Command } from 'commander';
|
||||
import { actions, middlewares as m } from '@nocobase/actions';
|
||||
import cors from '@koa/cors';
|
||||
import { dbResourceRouter } from './middlewares';
|
||||
import bodyParser from 'koa-bodyparser';
|
||||
import { dataWrapping, table2resource } from './middlewares';
|
||||
|
||||
export interface ResourcerOptions {
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
export interface ApplicationOptions {
|
||||
database: DatabaseOptions;
|
||||
resourcer?: any;
|
||||
database?: DatabaseOptions;
|
||||
resourcer?: ResourcerOptions;
|
||||
bodyParser?: any;
|
||||
cors?: any;
|
||||
dataWrapping?: boolean;
|
||||
}
|
||||
|
||||
export class Application extends Koa {
|
||||
|
||||
public readonly database: Database;
|
||||
|
||||
public readonly resourcer: Resourcer;
|
||||
@ -24,31 +29,37 @@ export class Application extends Koa {
|
||||
|
||||
constructor(options: ApplicationOptions) {
|
||||
super();
|
||||
|
||||
this.database = new Database(options.database);
|
||||
this.resourcer = new Resourcer();
|
||||
this.resourcer = new Resourcer({ ...options.resourcer });
|
||||
this.cli = new Command();
|
||||
|
||||
this.use(bodyParser());
|
||||
this.use(cors({
|
||||
exposeHeaders: ['content-disposition'],
|
||||
}));
|
||||
this.use(
|
||||
bodyParser({
|
||||
...options.bodyParser,
|
||||
}),
|
||||
);
|
||||
|
||||
this.resourcer.registerActionHandlers({ ...actions.common, ...actions.associate });
|
||||
this.use(
|
||||
cors({
|
||||
exposeHeaders: ['content-disposition'],
|
||||
...options.cors,
|
||||
}),
|
||||
);
|
||||
|
||||
this.use(async (ctx, next) => {
|
||||
ctx.db = this.database;
|
||||
ctx.database = this.database;
|
||||
ctx.resourcer = this.resourcer;
|
||||
await next();
|
||||
});
|
||||
|
||||
this.resourcer.use(m.associated);
|
||||
this.use(m.dataWrapping);
|
||||
if (options.dataWrapping !== false) {
|
||||
this.use(dataWrapping);
|
||||
}
|
||||
|
||||
this.use(dbResourceRouter({
|
||||
database: this.database,
|
||||
resourcer: this.resourcer,
|
||||
...(options.resourcer || {}),
|
||||
}));
|
||||
this.use(table2resource);
|
||||
this.use(this.resourcer.restApiMiddleware());
|
||||
|
||||
this.cli
|
||||
.command('db sync')
|
||||
@ -65,7 +76,7 @@ export class Application extends Koa {
|
||||
.action(async (...args) => {
|
||||
const cli = args.pop();
|
||||
await this.emitAsync('db.init');
|
||||
await this.database.close();
|
||||
await this.destroy();
|
||||
});
|
||||
|
||||
this.cli
|
||||
@ -110,12 +121,7 @@ export class Application extends Koa {
|
||||
cb = cb.apply(this, args);
|
||||
}
|
||||
|
||||
if (
|
||||
cb && (
|
||||
cb instanceof Promise ||
|
||||
typeof cb.then === 'function'
|
||||
)
|
||||
) {
|
||||
if (cb && (cb instanceof Promise || typeof cb.then === 'function')) {
|
||||
return cb;
|
||||
}
|
||||
|
||||
@ -128,7 +134,9 @@ export class Application extends Koa {
|
||||
callbacks = callbacks.slice().filter(Boolean);
|
||||
await callbacks.reduce((prev, next) => {
|
||||
return prev.then((res) => {
|
||||
return run(next).then((result) => Promise.resolve(res.concat(result)));
|
||||
return run(next).then((result) =>
|
||||
Promise.resolve(res.concat(result)),
|
||||
);
|
||||
});
|
||||
}, Promise.resolve([]));
|
||||
}
|
||||
@ -153,11 +161,6 @@ export class Application extends Koa {
|
||||
}
|
||||
}
|
||||
|
||||
getPluginInstance(key: string) {
|
||||
const plugin = this.plugins.get(key);
|
||||
return plugin && plugin.instance;
|
||||
}
|
||||
|
||||
async loadPlugins() {
|
||||
await this.emitAsync('plugins.beforeLoad');
|
||||
const allPlugins = this.plugins.values();
|
||||
@ -171,15 +174,26 @@ export class Application extends Koa {
|
||||
return this.cli.parseAsync(argv);
|
||||
}
|
||||
|
||||
protected async loadPlugin({ entry, options = {} }: { entry: string | Function, options: any }) {
|
||||
async destroy() {
|
||||
await this.database.close()
|
||||
}
|
||||
|
||||
protected async loadPlugin({
|
||||
entry,
|
||||
options = {},
|
||||
}: {
|
||||
entry: string | Function;
|
||||
options: any;
|
||||
}) {
|
||||
let main: any;
|
||||
if (typeof entry === 'function') {
|
||||
main = entry;
|
||||
} else if (typeof entry === 'string') {
|
||||
const pathname = `${entry}/${__filename.endsWith('.ts') ? 'src' : 'lib'}/server`;
|
||||
const pathname = `${entry}/${__filename.endsWith('.ts') ? 'src' : 'lib'
|
||||
}/server`;
|
||||
main = require(pathname).default;
|
||||
}
|
||||
return main && await main.call(this, options);
|
||||
return main && (await main.call(this, options));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,135 +0,0 @@
|
||||
import compose from 'koa-compose';
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
import Resourcer, { getNameByParams, KoaMiddlewareOptions, parseRequest, parseQuery, ResourcerContext, ResourceType } from '@nocobase/resourcer';
|
||||
import Database, { BELONGSTO, BELONGSTOMANY, HASMANY, HASONE } from '@nocobase/database';
|
||||
|
||||
interface MiddlewareOptions extends KoaMiddlewareOptions {
|
||||
resourcer?: Resourcer;
|
||||
database?: Database;
|
||||
}
|
||||
/**
|
||||
* database + resourcer 结合的中间件(暂时不知道起什么名好)
|
||||
*
|
||||
* @param options
|
||||
*/
|
||||
export function middleware(options: MiddlewareOptions = {}) {
|
||||
const {
|
||||
prefix,
|
||||
database,
|
||||
resourcer,
|
||||
accessors,
|
||||
paramsKey = 'params',
|
||||
nameRule = getNameByParams,
|
||||
} = options;
|
||||
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)) {
|
||||
const [tableName, fieldName] = resourceName.split('.');
|
||||
const Collection = database.getModel('collections');
|
||||
// 检查资源对应的表名是否已经定义
|
||||
if (!database.isDefined(tableName) && Collection) {
|
||||
// 未定义则尝试通过 collection 表来加载
|
||||
await Collection.load({
|
||||
where: {
|
||||
name: tableName,
|
||||
},
|
||||
});
|
||||
}
|
||||
// 如果经过加载后是已经定义的表
|
||||
if (database.isDefined(tableName)) {
|
||||
const table = database.getTable(tableName);
|
||||
const field = table.getField(fieldName) as BELONGSTO | HASMANY | BELONGSTOMANY | HASONE;
|
||||
if (!fieldName || field) {
|
||||
let resourceType: ResourceType = 'single';
|
||||
let actions = {};
|
||||
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';
|
||||
}
|
||||
if (field.options.actions) {
|
||||
actions = field.options.actions;
|
||||
}
|
||||
} else {
|
||||
const items = table.getOptions('actions') || [];
|
||||
for (const item of (items as any[])) {
|
||||
actions[item.name] = item;
|
||||
}
|
||||
}
|
||||
resourcer.define({
|
||||
type: resourceType,
|
||||
name: resourceName,
|
||||
actions,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
ctx.action.setContext(ctx);
|
||||
// 自带 query 处理的不太给力,需要用 qs 转一下
|
||||
const query = parseQuery(ctx.request.querystring);
|
||||
// 兼容 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default middleware;
|
30
packages/server/src/middlewares/data-wrapping.ts
Normal file
30
packages/server/src/middlewares/data-wrapping.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Action } from '@nocobase/resourcer';
|
||||
|
||||
export async function dataWrapping(ctx, next) {
|
||||
await next();
|
||||
if (ctx.withoutDataWrapping) {
|
||||
return;
|
||||
}
|
||||
if (!ctx?.action?.params) {
|
||||
return;
|
||||
}
|
||||
if (ctx.body instanceof Buffer) {
|
||||
return;
|
||||
}
|
||||
if (!ctx.body) {
|
||||
ctx.body = {};
|
||||
}
|
||||
const { rows, ...meta } = ctx.body;
|
||||
if (rows) {
|
||||
ctx.body = {
|
||||
data: rows,
|
||||
meta,
|
||||
};
|
||||
} else {
|
||||
ctx.body = {
|
||||
data: ctx.body,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default dataWrapping;
|
@ -1,133 +0,0 @@
|
||||
import compose from 'koa-compose';
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
import Resourcer, { getNameByParams, KoaMiddlewareOptions, parseRequest, parseQuery, ResourcerContext, ResourceType } from '@nocobase/resourcer';
|
||||
import Database, { BELONGSTO, BELONGSTOMANY, HASMANY, HASONE } from '@nocobase/database';
|
||||
|
||||
interface MiddlewareOptions extends KoaMiddlewareOptions {
|
||||
resourcer?: Resourcer;
|
||||
database?: Database;
|
||||
}
|
||||
/**
|
||||
* database + resourcer 结合的中间件(暂时不知道起什么名好)
|
||||
*
|
||||
* @param options
|
||||
*/
|
||||
export function dbResourceRouter(options: MiddlewareOptions = {}) {
|
||||
const {
|
||||
prefix,
|
||||
database,
|
||||
resourcer,
|
||||
accessors,
|
||||
paramsKey = 'params',
|
||||
nameRule = getNameByParams,
|
||||
} = options;
|
||||
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)) {
|
||||
const [tableName, fieldName] = resourceName.split('.');
|
||||
const Collection = database.getModel('collections');
|
||||
// 检查资源对应的表名是否已经定义
|
||||
if (!database.isDefined(tableName) && Collection) {
|
||||
// 未定义则尝试通过 collection 表来加载
|
||||
await Collection.load({
|
||||
where: {
|
||||
name: tableName,
|
||||
},
|
||||
});
|
||||
}
|
||||
// 如果经过加载后是已经定义的表
|
||||
if (database.isDefined(tableName)) {
|
||||
const table = database.getTable(tableName);
|
||||
const field = table.getField(fieldName) as BELONGSTO | HASMANY | BELONGSTOMANY | HASONE;
|
||||
if (!fieldName || field) {
|
||||
let resourceType: ResourceType = 'single';
|
||||
let actions = {};
|
||||
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';
|
||||
}
|
||||
if (field.options.actions) {
|
||||
actions = field.options.actions;
|
||||
}
|
||||
} else {
|
||||
const items = table.getOptions('actions') || [];
|
||||
for (const item of (items as any[])) {
|
||||
actions[item.name] = item;
|
||||
}
|
||||
}
|
||||
resourcer.define({
|
||||
type: resourceType,
|
||||
name: resourceName,
|
||||
actions,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
ctx.action.setContext(ctx);
|
||||
// 自带 query 处理的不太给力,需要用 qs 转一下
|
||||
const query = parseQuery(ctx.request.querystring);
|
||||
// 兼容 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
export * from './action-params';
|
||||
export * from './app-dist-serve';
|
||||
export * from './db-resource-router';
|
||||
export * from './demo-blacklisted-actions';
|
||||
export * from './table2resource';
|
||||
export * from './data-wrapping';
|
||||
|
72
packages/server/src/middlewares/table2resource.ts
Normal file
72
packages/server/src/middlewares/table2resource.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import {
|
||||
getNameByParams,
|
||||
parseRequest,
|
||||
ResourcerContext,
|
||||
ResourceType,
|
||||
} from '@nocobase/resourcer';
|
||||
import { BELONGSTO, BELONGSTOMANY, HASMANY, HASONE } from '@nocobase/database';
|
||||
|
||||
export async function table2resource(
|
||||
ctx: ResourcerContext,
|
||||
next: () => Promise<any>,
|
||||
) {
|
||||
const resourcer = ctx.resourcer;
|
||||
const database = ctx.database;
|
||||
let params = parseRequest(
|
||||
{
|
||||
path: ctx.request.path,
|
||||
method: ctx.request.method,
|
||||
},
|
||||
{
|
||||
prefix: resourcer.options.prefix,
|
||||
accessors: resourcer.options.accessors,
|
||||
},
|
||||
);
|
||||
if (!params) {
|
||||
return next();
|
||||
}
|
||||
const resourceName = getNameByParams(params);
|
||||
// 如果资源名称未被定义
|
||||
if (resourcer.isDefined(resourceName)) {
|
||||
return next();
|
||||
}
|
||||
const [tableName, fieldName] = resourceName.split('.');
|
||||
// 如果经过加载后是已经定义的表
|
||||
if (!database.isDefined(tableName)) {
|
||||
return next();
|
||||
}
|
||||
const table = database.getTable(tableName);
|
||||
const field = table.getField(fieldName) as
|
||||
| BELONGSTO
|
||||
| HASMANY
|
||||
| BELONGSTOMANY
|
||||
| HASONE;
|
||||
if (!fieldName || field) {
|
||||
let resourceType: ResourceType = 'single';
|
||||
let actions = {};
|
||||
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';
|
||||
}
|
||||
if (field.options.actions) {
|
||||
actions = field.options.actions || {};
|
||||
}
|
||||
} else {
|
||||
actions = table.getOptions('actions') || {};
|
||||
}
|
||||
resourcer.define({
|
||||
type: resourceType,
|
||||
name: resourceName,
|
||||
actions,
|
||||
});
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
export default table2resource;
|
Loading…
Reference in New Issue
Block a user