diff --git a/package.json b/package.json index 0534ee81c..7e676b417 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/server/src/application.ts b/packages/server/src/application.ts index 81dd9ab4b..aae902680 100644 --- a/packages/server/src/application.ts +++ b/packages/server/src/application.ts @@ -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; diff --git a/packages/server/src/middlewares/action-params.ts b/packages/server/src/middlewares/action-params.ts deleted file mode 100644 index 08aac41a7..000000000 --- a/packages/server/src/middlewares/action-params.ts +++ /dev/null @@ -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(); - } -} diff --git a/packages/server/src/middlewares/app-dist-serve.ts b/packages/server/src/middlewares/app-dist-serve.ts deleted file mode 100644 index 230751660..000000000 --- a/packages/server/src/middlewares/app-dist-serve.ts +++ /dev/null @@ -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 }); - } - } -} diff --git a/packages/server/src/middlewares/data-wrapping.ts b/packages/server/src/middlewares/data-wrapping.ts index 6f5f3e64c..3ccb82d9c 100644 --- a/packages/server/src/middlewares/data-wrapping.ts +++ b/packages/server/src/middlewares/data-wrapping.ts @@ -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, + }; + } } } diff --git a/packages/server/src/middlewares/demo-blacklisted-actions.ts b/packages/server/src/middlewares/demo-blacklisted-actions.ts deleted file mode 100644 index ecd8da0d3..000000000 --- a/packages/server/src/middlewares/demo-blacklisted-actions.ts +++ /dev/null @@ -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(); - } -} diff --git a/packages/server/src/middlewares/index.ts b/packages/server/src/middlewares/index.ts index 499f85c85..2b99c68a2 100644 --- a/packages/server/src/middlewares/index.ts +++ b/packages/server/src/middlewares/index.ts @@ -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'; diff --git a/packages/server/src/middlewares/table2resource.ts b/packages/server/src/middlewares/table2resource.ts index 7706b1331..fe696629d 100644 --- a/packages/server/src/middlewares/table2resource.ts +++ b/packages/server/src/middlewares/table2resource.ts @@ -6,67 +6,69 @@ import { } from '@nocobase/resourcer'; import { BELONGSTO, BELONGSTOMANY, HASMANY, HASONE } from '@nocobase/database'; -export async function table2resource( - ctx: ResourcerContext, - next: () => Promise, -) { - 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, + ) { + 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; diff --git a/packages/test/src/__tests__/mockServer.test.ts b/packages/test/src/__tests__/mockServer.test.ts index 8d9e7a224..062060fcf 100644 --- a/packages/test/src/__tests__/mockServer.test.ts +++ b/packages/test/src/__tests__/mockServer.test.ts @@ -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]); }); }); diff --git a/packages/test/src/mockServer.ts b/packages/test/src/mockServer.ts index 3531677a6..330d1cc7b 100644 --- a/packages/test/src/mockServer.ts +++ b/packages/test/src/mockServer.ts @@ -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; } } diff --git a/tsconfig.json b/tsconfig.json index b6a4b413c..0055699ef 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,7 @@ "baseUrl": ".", "paths": { "@nocobase/*": [ - "./packages/*" + "./packages/*/src" ] } }