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 Koa from 'koa';
|
||||||
|
import cors from '@koa/cors';
|
||||||
|
import bodyParser from 'koa-bodyparser';
|
||||||
|
import { Command } from 'commander';
|
||||||
import Database, { DatabaseOptions } from '@nocobase/database';
|
import Database, { DatabaseOptions } from '@nocobase/database';
|
||||||
import Resourcer from '@nocobase/resourcer';
|
import Resourcer from '@nocobase/resourcer';
|
||||||
import { Command } from 'commander';
|
import { dataWrapping, table2resource } from './middlewares';
|
||||||
import { actions, middlewares as m } from '@nocobase/actions';
|
|
||||||
import cors from '@koa/cors';
|
export interface ResourcerOptions {
|
||||||
import { dbResourceRouter } from './middlewares';
|
prefix?: string;
|
||||||
import bodyParser from 'koa-bodyparser';
|
}
|
||||||
|
|
||||||
export interface ApplicationOptions {
|
export interface ApplicationOptions {
|
||||||
database: DatabaseOptions;
|
database?: DatabaseOptions;
|
||||||
resourcer?: any;
|
resourcer?: ResourcerOptions;
|
||||||
|
bodyParser?: any;
|
||||||
|
cors?: any;
|
||||||
|
dataWrapping?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Application extends Koa {
|
export class Application extends Koa {
|
||||||
|
|
||||||
public readonly database: Database;
|
public readonly database: Database;
|
||||||
|
|
||||||
public readonly resourcer: Resourcer;
|
public readonly resourcer: Resourcer;
|
||||||
@ -24,31 +29,37 @@ export class Application extends Koa {
|
|||||||
|
|
||||||
constructor(options: ApplicationOptions) {
|
constructor(options: ApplicationOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.database = new Database(options.database);
|
this.database = new Database(options.database);
|
||||||
this.resourcer = new Resourcer();
|
this.resourcer = new Resourcer({ ...options.resourcer });
|
||||||
this.cli = new Command();
|
this.cli = new Command();
|
||||||
|
|
||||||
this.use(bodyParser());
|
this.use(
|
||||||
this.use(cors({
|
bodyParser({
|
||||||
exposeHeaders: ['content-disposition'],
|
...options.bodyParser,
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.resourcer.registerActionHandlers({ ...actions.common, ...actions.associate });
|
this.use(
|
||||||
|
cors({
|
||||||
|
exposeHeaders: ['content-disposition'],
|
||||||
|
...options.cors,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.use(async (ctx, next) => {
|
this.use(async (ctx, next) => {
|
||||||
ctx.db = this.database;
|
ctx.db = this.database;
|
||||||
ctx.database = this.database;
|
ctx.database = this.database;
|
||||||
|
ctx.resourcer = this.resourcer;
|
||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resourcer.use(m.associated);
|
if (options.dataWrapping !== false) {
|
||||||
this.use(m.dataWrapping);
|
this.use(dataWrapping);
|
||||||
|
}
|
||||||
|
|
||||||
this.use(dbResourceRouter({
|
this.use(table2resource);
|
||||||
database: this.database,
|
this.use(this.resourcer.restApiMiddleware());
|
||||||
resourcer: this.resourcer,
|
|
||||||
...(options.resourcer || {}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.cli
|
this.cli
|
||||||
.command('db sync')
|
.command('db sync')
|
||||||
@ -65,7 +76,7 @@ export class Application extends Koa {
|
|||||||
.action(async (...args) => {
|
.action(async (...args) => {
|
||||||
const cli = args.pop();
|
const cli = args.pop();
|
||||||
await this.emitAsync('db.init');
|
await this.emitAsync('db.init');
|
||||||
await this.database.close();
|
await this.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cli
|
this.cli
|
||||||
@ -110,12 +121,7 @@ export class Application extends Koa {
|
|||||||
cb = cb.apply(this, args);
|
cb = cb.apply(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (cb && (cb instanceof Promise || typeof cb.then === 'function')) {
|
||||||
cb && (
|
|
||||||
cb instanceof Promise ||
|
|
||||||
typeof cb.then === 'function'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return cb;
|
return cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +134,9 @@ export class Application extends Koa {
|
|||||||
callbacks = callbacks.slice().filter(Boolean);
|
callbacks = callbacks.slice().filter(Boolean);
|
||||||
await callbacks.reduce((prev, next) => {
|
await callbacks.reduce((prev, next) => {
|
||||||
return prev.then((res) => {
|
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([]));
|
}, 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() {
|
async loadPlugins() {
|
||||||
await this.emitAsync('plugins.beforeLoad');
|
await this.emitAsync('plugins.beforeLoad');
|
||||||
const allPlugins = this.plugins.values();
|
const allPlugins = this.plugins.values();
|
||||||
@ -171,15 +174,26 @@ export class Application extends Koa {
|
|||||||
return this.cli.parseAsync(argv);
|
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;
|
let main: any;
|
||||||
if (typeof entry === 'function') {
|
if (typeof entry === 'function') {
|
||||||
main = entry;
|
main = entry;
|
||||||
} else if (typeof entry === 'string') {
|
} 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;
|
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 './action-params';
|
||||||
export * from './app-dist-serve';
|
export * from './app-dist-serve';
|
||||||
export * from './db-resource-router';
|
|
||||||
export * from './demo-blacklisted-actions';
|
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