mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-12-01 19:58:15 +08:00
feat: improve code
This commit is contained in:
parent
09c42abdcc
commit
b7eb720eb4
@ -7,6 +7,7 @@
|
||||
"scripts": {
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"clean": "lerna clean",
|
||||
"examples": "ts-node-dev -r dotenv/config ./examples",
|
||||
"start": "cd packages/app && npm start",
|
||||
"start-client": "cd packages/app && npm run start-client",
|
||||
"start-server": "nodemon",
|
||||
|
@ -6,6 +6,7 @@ import Database, { DatabaseOptions, TableOptions } from '@nocobase/database';
|
||||
import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
|
||||
import { dataWrapping, table2resource } from './middlewares';
|
||||
import { PluginType, Plugin, PluginOptions } from './plugin';
|
||||
import { registerActions } from '@nocobase/actions';
|
||||
|
||||
export interface ResourcerOptions {
|
||||
prefix?: string;
|
||||
@ -20,6 +21,7 @@ export interface ApplicationOptions {
|
||||
}
|
||||
|
||||
export class Application extends Koa {
|
||||
|
||||
public readonly db: Database;
|
||||
|
||||
public readonly resourcer: Resourcer;
|
||||
@ -31,7 +33,12 @@ export class Application extends Koa {
|
||||
constructor(options: ApplicationOptions) {
|
||||
super();
|
||||
|
||||
this.db = new Database(options.database);
|
||||
if (options.database instanceof Database) {
|
||||
this.db = options.database;
|
||||
} else {
|
||||
this.db = new Database(options.database);
|
||||
}
|
||||
|
||||
this.resourcer = new Resourcer({ ...options.resourcer });
|
||||
this.cli = new Command();
|
||||
|
||||
@ -55,28 +62,30 @@ export class Application extends Koa {
|
||||
});
|
||||
|
||||
if (options.dataWrapping !== false) {
|
||||
this.use(dataWrapping);
|
||||
this.use(dataWrapping());
|
||||
}
|
||||
|
||||
this.use(table2resource);
|
||||
this.use(table2resource());
|
||||
this.use(this.resourcer.restApiMiddleware());
|
||||
|
||||
registerActions(this);
|
||||
|
||||
this.cli
|
||||
.command('db sync')
|
||||
.option('-f, --force')
|
||||
.action(async (...args) => {
|
||||
console.log('db sync');
|
||||
console.log('db sync...');
|
||||
const cli = args.pop();
|
||||
const force = cli.opts()?.force;
|
||||
await this.load();
|
||||
await this.db.sync(
|
||||
force
|
||||
? {
|
||||
force: true,
|
||||
alter: {
|
||||
drop: true,
|
||||
},
|
||||
}
|
||||
force: true,
|
||||
alter: {
|
||||
drop: true,
|
||||
},
|
||||
}
|
||||
: {},
|
||||
);
|
||||
await this.destroy();
|
||||
@ -120,6 +129,10 @@ export class Application extends Koa {
|
||||
return this.resourcer.define(options);
|
||||
}
|
||||
|
||||
actions(handlers: any) {
|
||||
return this.resourcer.registerActions(handlers);
|
||||
}
|
||||
|
||||
command(nameAndArgs: string, opts?: CommandOptions) {
|
||||
return this.cli.command(nameAndArgs, opts);
|
||||
}
|
||||
@ -216,58 +229,13 @@ export class Application extends Koa {
|
||||
return true;
|
||||
}
|
||||
|
||||
// registerPlugin(key: string | object, plugin?: any) {
|
||||
// if (typeof key === 'object') {
|
||||
// Object.keys(key).forEach((k) => {
|
||||
// this.registerPlugin(k, key[k]);
|
||||
// });
|
||||
// } else {
|
||||
// const config = {};
|
||||
// if (Array.isArray(plugin)) {
|
||||
// const [entry, options = {}] = plugin;
|
||||
// Object.assign(config, { entry, options });
|
||||
// } else {
|
||||
// Object.assign(config, { entry: plugin, options: {} });
|
||||
// }
|
||||
// this.plugins.set(key, config);
|
||||
// }
|
||||
// }
|
||||
|
||||
// async loadPlugins() {
|
||||
// await this.emitAsync('plugins.beforeLoad');
|
||||
// const allPlugins = this.plugins.values();
|
||||
// for (const plugin of allPlugins) {
|
||||
// plugin.instance = await this.loadPlugin(plugin);
|
||||
// }
|
||||
// await this.emitAsync('plugins.afterLoad');
|
||||
// }
|
||||
|
||||
async start(argv = process.argv) {
|
||||
async parse(argv = process.argv) {
|
||||
return this.cli.parseAsync(argv);
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
await this.db.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`;
|
||||
// main = require(pathname).default;
|
||||
// }
|
||||
// return main && (await main.call(this, options));
|
||||
// }
|
||||
}
|
||||
|
||||
export default Application;
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { Context, Next } from '@nocobase/actions';
|
||||
|
||||
export function actionParams(options: any = {}) {
|
||||
return async (ctx: Context, next: Next) => {
|
||||
const { actionName, resourceField, resourceName, fields = {} } = ctx.action.params;
|
||||
const table = ctx.db.getTable(resourceField ? resourceField.options.target : resourceName);
|
||||
// ctx.state.developerMode = {[Op.not]: null};
|
||||
ctx.state.developerMode = false;
|
||||
if (table && table.hasField('developerMode') && ctx.state.developerMode === false) {
|
||||
ctx.action.mergeParams({ filter: { "developerMode.$isFalsy": true } }, { filter: 'and' });
|
||||
}
|
||||
if (table && ['get', 'list'].includes(actionName)) {
|
||||
const except = [];
|
||||
const appends = [];
|
||||
for (const [name, field] of table.getFields()) {
|
||||
if (field.options.hidden) {
|
||||
except.push(field.options.name);
|
||||
}
|
||||
// if (field.options.appends) {
|
||||
// appends.push(field.options.name);
|
||||
// }
|
||||
}
|
||||
ctx.action.mergeParams({ fields: {
|
||||
except,
|
||||
appends
|
||||
} }, { fields: 'append' });
|
||||
// console.log('ctx.action.params.fields', ctx.action.params.fields, except, appends);
|
||||
}
|
||||
await next();
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import path from 'path';
|
||||
import send from 'koa-send';
|
||||
import serve from 'koa-static';
|
||||
import { Context, Next } from '@nocobase/actions';
|
||||
|
||||
export interface AppDistServeOptions {
|
||||
root?: string;
|
||||
useStaticServer?: boolean;
|
||||
}
|
||||
|
||||
export function appDistServe(options: AppDistServeOptions = {}) {
|
||||
return async (ctx: Context, next: Next) => {
|
||||
if (!options.root || !options.useStaticServer) {
|
||||
return next();
|
||||
}
|
||||
let root = options.root;
|
||||
if (!root.startsWith('/')) {
|
||||
root = path.resolve(process.cwd(), root);
|
||||
}
|
||||
await serve(root)(ctx, next);
|
||||
if (ctx.status == 404) {
|
||||
return send(ctx, 'index.html', { root });
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +1,31 @@
|
||||
import { Context, Next } from '@nocobase/actions';
|
||||
|
||||
export async function dataWrapping(ctx: Context, next: 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 function dataWrapping() {
|
||||
return async function dataWrapping(ctx: Context, next: 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { Context, Next } from '@nocobase/actions';
|
||||
|
||||
export interface DemoBlackListedActionsOptions {
|
||||
emails?: string[];
|
||||
blacklist?: string[];
|
||||
}
|
||||
|
||||
const defaultBlacklist = ['create', 'update', 'destroy', 'sort', 'upload'];
|
||||
|
||||
export function demoBlacklistedActions(options: DemoBlackListedActionsOptions = {}) {
|
||||
const { emails, blacklist = defaultBlacklist } = options;
|
||||
return async (ctx: Context, next: Next) => {
|
||||
const currentUser = ctx.state.currentUser;
|
||||
if (currentUser && emails.includes(currentUser.email)) {
|
||||
return next();
|
||||
}
|
||||
const { actionName } = ctx.action.params;
|
||||
if (blacklist.includes(actionName)) {
|
||||
ctx.body = {
|
||||
data: {},
|
||||
meta: {},
|
||||
};
|
||||
return;
|
||||
}
|
||||
await next();
|
||||
}
|
||||
}
|
@ -1,5 +1,2 @@
|
||||
export * from './action-params';
|
||||
export * from './app-dist-serve';
|
||||
export * from './demo-blacklisted-actions';
|
||||
export * from './table2resource';
|
||||
export * from './data-wrapping';
|
||||
|
@ -6,67 +6,69 @@ import {
|
||||
} 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') || {};
|
||||
export function table2resource() {
|
||||
return async function table2resource(
|
||||
ctx: ResourcerContext,
|
||||
next: () => Promise<any>,
|
||||
) {
|
||||
const resourcer = ctx.resourcer;
|
||||
const database = ctx.db;
|
||||
let params = parseRequest(
|
||||
{
|
||||
path: ctx.request.path,
|
||||
method: ctx.request.method,
|
||||
},
|
||||
{
|
||||
prefix: resourcer.options.prefix,
|
||||
accessors: resourcer.options.accessors,
|
||||
},
|
||||
);
|
||||
if (!params) {
|
||||
return next();
|
||||
}
|
||||
resourcer.define({
|
||||
type: resourceType,
|
||||
name: resourceName,
|
||||
actions,
|
||||
});
|
||||
}
|
||||
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;
|
||||
|
@ -28,7 +28,7 @@ describe('mock server', () => {
|
||||
});
|
||||
|
||||
it('resource', async () => {
|
||||
const response = await api.resource('test').list();
|
||||
const response = await api.agent().resource('test').list();
|
||||
expect(response.body).toEqual([1, 2]);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import qs from 'qs';
|
||||
import supertest from 'supertest';
|
||||
import supertest, { SuperAgentTest } from 'supertest';
|
||||
import Application, { ApplicationOptions } from '@nocobase/server';
|
||||
import { getConfig } from './mockDatabase';
|
||||
|
||||
@ -46,59 +46,61 @@ interface Resource {
|
||||
}
|
||||
|
||||
export class MockServer extends Application {
|
||||
|
||||
protected agentInstance: supertest.SuperAgentTest;
|
||||
|
||||
agent() {
|
||||
if (!this.agentInstance) {
|
||||
this.agentInstance = supertest.agent(this.callback());
|
||||
}
|
||||
return this.agentInstance;
|
||||
}
|
||||
|
||||
resource(name: string) {
|
||||
const agent = this.agent();
|
||||
const keys = name.split('.');
|
||||
agent(): SuperAgentTest & { resource: (name: string) => Resource } {
|
||||
const agent = supertest.agent(this.callback());
|
||||
const prefix = this.resourcer.options.prefix;
|
||||
const proxy = new Proxy({}, {
|
||||
get(target, method: string, receiver) {
|
||||
return (params: ActionParams = {}) => {
|
||||
const {
|
||||
associatedKey,
|
||||
resourceKey,
|
||||
values = {},
|
||||
file,
|
||||
...restParams
|
||||
} = params;
|
||||
let url = prefix;
|
||||
if (keys.length > 1) {
|
||||
url = `/${keys[0]}/${associatedKey}/${keys[1]}`
|
||||
} else {
|
||||
url = `/${name}`;
|
||||
}
|
||||
url += `:${method as string}`;
|
||||
if (resourceKey) {
|
||||
url += `/${resourceKey}`;
|
||||
}
|
||||
console.log('request url: ' + url);
|
||||
switch (method) {
|
||||
case 'upload':
|
||||
return agent
|
||||
.post(`${url}?${qs.stringify(restParams)}`)
|
||||
.attach('file', file)
|
||||
.field(values);
|
||||
case 'list':
|
||||
case 'get':
|
||||
return agent.get(`${url}?${qs.stringify(restParams)}`);
|
||||
default:
|
||||
return agent
|
||||
.post(`${url}?${qs.stringify(restParams)}`)
|
||||
.send(values);
|
||||
if (method === 'resource') {
|
||||
return (name: string) => {
|
||||
const keys = name.split('.');
|
||||
const proxy = new Proxy({}, {
|
||||
get(target, method: string, receiver) {
|
||||
return (params: ActionParams = {}) => {
|
||||
const {
|
||||
associatedKey,
|
||||
resourceKey,
|
||||
values = {},
|
||||
file,
|
||||
...restParams
|
||||
} = params;
|
||||
let url = prefix;
|
||||
if (keys.length > 1) {
|
||||
url = `/${keys[0]}/${associatedKey}/${keys[1]}`
|
||||
} else {
|
||||
url = `/${name}`;
|
||||
}
|
||||
url += `:${method as string}`;
|
||||
if (resourceKey) {
|
||||
url += `/${resourceKey}`;
|
||||
}
|
||||
console.log('request url: ' + url);
|
||||
switch (method) {
|
||||
case 'upload':
|
||||
return agent
|
||||
.post(`${url}?${qs.stringify(restParams)}`)
|
||||
.attach('file', file)
|
||||
.field(values);
|
||||
case 'list':
|
||||
case 'get':
|
||||
return agent.get(`${url}?${qs.stringify(restParams)}`);
|
||||
default:
|
||||
return agent
|
||||
.post(`${url}?${qs.stringify(restParams)}`)
|
||||
.send(values);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
return (...args: any[]) => {
|
||||
return agent[method](...args);
|
||||
};
|
||||
},
|
||||
}
|
||||
});
|
||||
return proxy as Resource;
|
||||
return proxy as any;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@nocobase/*": [
|
||||
"./packages/*"
|
||||
"./packages/*/src"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user