feat(server): improve application (#177)

* feat: getRepository

* getRepository return type

* export action

* refactor(plugin-acl): plugin

* refactor(plugin-action-logs): plugin class

* refactor(plugin-china-region): plugin class

* refactor: plugin class

* fix: cli start command

* feat: pass install-command options into app.install

* fix: cli args

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
ChengLei Shao 2022-02-07 01:14:00 +08:00 committed by GitHub
parent de25f56a79
commit 8e4336cbe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 168 additions and 171 deletions

View File

@ -82,17 +82,13 @@ export default class PluginACL extends Plugin {
});
}
async load() {
this.app.db.registerModels({
RoleResourceActionModel,
RoleResourceModel,
});
async beforeLoad() {
const acl = createACL();
this.acl = acl;
await this.app.db.import({
directory: path.resolve(__dirname, 'collections'),
this.app.db.registerModels({
RoleResourceActionModel,
RoleResourceModel,
});
this.registerAssociationFieldsActions();
@ -100,8 +96,6 @@ export default class PluginACL extends Plugin {
this.app.resourcer.define(availableActionResource);
this.app.resourcer.define(roleCollectionsResource);
this.app.resourcer.use(this.acl.middleware());
this.app.db.on('roles.afterSave', async (model, options) => {
const { transaction } = options;
const roleName = model.get('name');
@ -187,4 +181,12 @@ export default class PluginACL extends Plugin {
}
});
}
async load() {
await this.app.db.import({
directory: path.resolve(__dirname, 'collections'),
});
this.app.resourcer.use(this.acl.middleware());
}
}

View File

@ -1,16 +1,17 @@
import path from 'path';
import { Plugin } from '@nocobase/server';
import { afterCreate, afterUpdate, afterDestroy } from './hooks';
import { Plugin } from '@nocobase/server';
export default class PluginActionLogs extends Plugin {
async beforeLoad() {
this.db.on('afterCreate', afterCreate);
this.db.on('afterUpdate', afterUpdate);
this.db.on('afterDestroy', afterDestroy);
}
export default class extends Plugin {
name: 'action-logs';
async load() {
const database = this.app.db;
await database.import({
await this.db.import({
directory: path.resolve(__dirname, 'collections'),
});
database.on('afterCreate', afterCreate);
database.on('afterUpdate', afterUpdate);
database.on('afterDestroy', afterDestroy);
}
};
}

View File

@ -51,18 +51,17 @@ async function importData(model) {
console.log(`${count} rows of region data imported in ${(Date.now() - timer) / 1000}s`);
}
export default {
name: 'china-region',
async load(this: Plugin) {
const db = this.app.db;
await db.import({
directory: path.resolve(__dirname, 'collections'),
});
this.app.on('db.init', async () => {
const ChinaRegion = db.getCollection('china_regions').model;
export default class PluginChinaRegion extends Plugin {
async beforeLoad() {
this.app.on('installing', async () => {
const ChinaRegion = this.db.getCollection('china_regions').model;
await importData(ChinaRegion);
});
},
};
}
async load() {
await this.db.import({
directory: path.resolve(__dirname, 'collections'),
});
}
}

View File

@ -10,16 +10,12 @@ import { afterCreateForReverseField } from './hooks/afterCreateForReverseField';
export * from './repositories/collection-repository';
export default class CollectionManagerPlugin extends Plugin {
async load() {
async beforeLoad() {
this.app.db.registerModels({
CollectionModel,
FieldModel,
});
await this.app.db.import({
directory: path.resolve(__dirname, './collections'),
});
// 要在 beforeInitOptions 之前处理
this.app.db.on('fields.beforeCreate', beforeCreateForReverseField(this.app.db));
this.app.db.on('fields.beforeCreate', beforeCreateForChildrenCollection(this.app.db));
@ -47,4 +43,10 @@ export default class CollectionManagerPlugin extends Plugin {
}
});
}
async load() {
await this.app.db.import({
directory: path.resolve(__dirname, './collections'),
});
}
}

View File

@ -1,44 +1,41 @@
import path from 'path';
import Database from '@nocobase/database';
import Resourcer from '@nocobase/resourcer';
import { PluginOptions } from '@nocobase/server';
import { Plugin } from '@nocobase/server';
import { action as uploadAction, middleware as uploadMiddleware } from './actions/upload';
import { getStorageConfig } from './storages';
import { STORAGE_TYPE_LOCAL } from './constants';
export default {
name: 'file-manager',
async load() {
const database: Database = this.app.db;
const resourcer: Resourcer = this.app.resourcer;
await database.import({
directory: path.resolve(__dirname, 'collections'),
});
// 暂时中间件只能通过 use 加进来
resourcer.use(uploadMiddleware);
resourcer.registerActionHandler('upload', uploadAction);
const { DEFAULT_STORAGE_TYPE } = process.env;
if (process.env.NOCOBASE_ENV !== 'production') {
await getStorageConfig(STORAGE_TYPE_LOCAL).middleware(this.app);
export default class PluginFileManager extends Plugin {
storageType() {
return process.env.DEFAULT_STORAGE_TYPE;
}
this.app.on('db.init', async () => {
const defaultStorageConfig = getStorageConfig(DEFAULT_STORAGE_TYPE);
async beforeLoad() {
this.app.on('installing', async () => {
const defaultStorageConfig = getStorageConfig(this.storageType());
if (defaultStorageConfig) {
const Storage = database.getCollection('storages');
const Storage = this.db.getCollection('storages');
await Storage.repository.create({
values: {
...defaultStorageConfig.defaults(),
type: DEFAULT_STORAGE_TYPE,
type: this.storageType(),
default: true,
},
});
}
});
},
} as PluginOptions;
}
async load() {
await this.db.import({
directory: path.resolve(__dirname, 'collections'),
});
// 暂时中间件只能通过 use 加进来
this.app.resourcer.use(uploadMiddleware);
this.app.resourcer.registerActionHandler('upload', uploadAction);
if (process.env.NOCOBASE_ENV !== 'production') {
await getStorageConfig(STORAGE_TYPE_LOCAL).middleware(this.app);
}
}
}

View File

@ -1,11 +1,10 @@
import path from 'path';
import { PluginOptions } from '@nocobase/server';
import { Plugin } from '@nocobase/server';
export default {
name: 'notifications',
export default class PluginNotifications extends Plugin {
async load() {
await this.app.db.import({
directory: path.resolve(__dirname, 'collections'),
});
}
} as PluginOptions
}

View File

@ -1,19 +1,10 @@
import path from 'path';
import { PluginOptions } from '@nocobase/server';
import { Plugin, PluginOptions } from '@nocobase/server';
export default {
name: 'system-settings',
async load() {
const database = this.app.db;
await database.import({
directory: path.resolve(__dirname, 'collections'),
});
const SystemSetting = database.getCollection('system_settings');
this.app.on('db.init', async () => {
await SystemSetting.repository.create({
export default class PluginSystemSettings extends Plugin {
async beforeLoad() {
this.app.on('installing', async () => {
await this.db.getRepository('system_settings').create({
values: {
title: 'NocoBase',
logo: {
@ -26,5 +17,11 @@ export default {
},
});
});
},
} as PluginOptions;
}
async load() {
await this.app.db.import({
directory: path.resolve(__dirname, 'collections'),
});
}
}

View File

@ -1,6 +1,6 @@
import { mockServer, MockServer } from '@nocobase/test';
import { Collection, Database } from '@nocobase/database';
import PluginUiSchema, { UiSchemaRepository } from '@nocobase/plugin-ui-schema';
import PluginUiSchema, { UiSchemaRepository } from '..';
describe('ui schema model', () => {
let app: MockServer;

View File

@ -1,7 +1,7 @@
import { ISchema } from '@formily/json-schema';
import { mockServer, MockServer } from '@nocobase/test';
import { Collection, Database } from '@nocobase/database';
import PluginUiSchema, { UiSchemaRepository } from '@nocobase/plugin-ui-schema';
import PluginUiSchema, { UiSchemaRepository } from '..';
describe('ui-schema', () => {
let app: MockServer;

View File

@ -11,17 +11,13 @@ export default class PluginUiSchema extends Plugin {
});
}
async load() {
async beforeLoad() {
const db = this.app.db;
this.app.db.registerModels({ MagicAttributeModel });
this.registerRepository();
await db.import({
directory: path.resolve(__dirname, 'collections'),
});
db.on('ui_schemas.beforeCreate', (model) => {
model.set('uid', model.get('x-uid'));
});
@ -44,23 +40,15 @@ export default class PluginUiSchema extends Plugin {
});
});
await db.getCollection('ui_schemas').sync({
force: false,
alter: {
drop: false,
},
});
await db.getCollection('ui_schema_tree_path').sync({
force: false,
alter: {
drop: false,
},
});
this.app.resourcer.define({
name: 'ui_schemas',
actions: uiSchemaActions,
});
}
async load() {
await this.db.import({
directory: path.resolve(__dirname, 'collections'),
});
}
}

View File

@ -11,7 +11,7 @@ describe('role', () => {
await api.cleanDb();
api.plugin(require('../server').default);
api.plugin(PluginACL);
await api.loadAndSync();
await api.loadAndInstall();
db = api.db;
});

View File

@ -2,16 +2,12 @@ import path from 'path';
import * as actions from './actions/users';
import * as middlewares from './middlewares';
import { Collection } from '@nocobase/database';
import { PluginOptions } from '@nocobase/server';
import { Plugin, PluginOptions } from '@nocobase/server';
export default {
name: 'users',
async load() {
const database = this.app.db;
const resourcer = this.app.resourcer;
this.app.on('db.init', async () => {
const User = database.getCollection('users');
export default class PluginUsers extends Plugin {
async beforeLoad() {
this.app.on('installing', async () => {
const User = this.db.getCollection('users');
await User.model.create({
nickname: 'Super Admin',
email: process.env.ADMIN_EMAIL || 'admin@nocobase.com',
@ -19,7 +15,7 @@ export default {
});
});
database.on('users.afterCreateWithAssociations', async (model, options) => {
this.db.on('users.afterCreateWithAssociations', async (model, options) => {
const { transaction } = options;
const defaultRole = await this.app.db.getRepository('roles').findOne({
@ -34,7 +30,7 @@ export default {
}
});
database.on('afterDefineCollection', (collection: Collection) => {
this.db.on('afterDefineCollection', (collection: Collection) => {
let { createdBy, updatedBy } = collection.options;
if (createdBy === true) {
collection.setField('createdById', {
@ -65,14 +61,16 @@ export default {
}
});
await database.import({
directory: path.resolve(__dirname, 'collections'),
});
for (const [key, action] of Object.entries(actions)) {
resourcer.registerActionHandler(`users:${key}`, action);
this.app.resourcer.registerActionHandler(`users:${key}`, action);
}
resourcer.use(middlewares.parseToken({}));
},
} as PluginOptions;
this.app.resourcer.use(middlewares.parseToken({}));
}
async load() {
await this.db.import({
directory: path.resolve(__dirname, 'collections'),
});
}
}

View File

@ -3,7 +3,7 @@ import { Application } from '../application';
import { Plugin } from '../plugin';
class MyPlugin extends Plugin {
load() {}
async load() {}
}
describe('application', () => {

View File

@ -65,10 +65,12 @@ interface ListenOptions {
}
interface StartOptions {
cliArgs?: any[];
listen?: ListenOptions;
}
interface InstallOptions {
cliArgs?: any[];
clean?: CleanOptions | boolean;
sync?: SyncOptions;
}
@ -174,8 +176,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
await this.emitAsync('afterStart', this, options);
}
async stop() {
await this.emitAsync('beforeStop', this);
async stop(options?: any) {
await this.emitAsync('beforeStop', this, options);
// close database connection
await this.db.close();
@ -188,7 +190,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
if (err) {
return reject(err);
}
this.listenServer = null;
resolve(true);
});
@ -197,13 +198,13 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
await closeServer();
}
await this.emitAsync('afterStop', this);
await this.emitAsync('afterStop', this, options);
}
async destroy() {
await this.emitAsync('beforeDestroy', this);
await this.stop();
await this.emitAsync('afterDestroy', this);
async destroy(options?: any) {
await this.emitAsync('beforeDestroy', this, options);
await this.stop(options);
await this.emitAsync('afterDestroy', this, options);
}
async install(options?: InstallOptions) {
@ -211,7 +212,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
await this.db.clean(isBoolean(options.clean) ? { drop: options.clean } : options.clean);
}
await this.db.sync(options?.sync);
await this.emitAsync('beforeInstall', this, options);
await this.emitAsync('installing', this, options);
await this.emitAsync('afterInstall', this, options);
}

View File

@ -1,13 +1,13 @@
import { DefaultContext, DefaultState } from 'koa';
import bodyParser from 'koa-bodyparser';
import cors from '@koa/cors';
import Database from '@nocobase/database';
import Resourcer from '@nocobase/resourcer';
import { Command } from 'commander';
import i18next from 'i18next';
import { DefaultContext, DefaultState } from 'koa';
import bodyParser from 'koa-bodyparser';
import Application, { ApplicationOptions } from './application';
import { dataWrapping } from './middlewares/data-wrapping';
import { table2resource } from './middlewares/table2resource';
import i18next from 'i18next';
export function createDatabase(options: ApplicationOptions) {
if (options.database instanceof Database) {
@ -37,12 +37,11 @@ export function createCli(app: Application, options: ApplicationOptions) {
cli
.command('db:sync')
.option('-f, --force')
.action(async (...args) => {
.action(async (...cliArgs) => {
const [opts] = cliArgs;
console.log('db sync...');
const cli = args.pop();
const force = cli.opts()?.force;
await app.db.sync(
force
opts.force
? {
force: true,
alter: {
@ -51,44 +50,44 @@ export function createCli(app: Application, options: ApplicationOptions) {
}
: {},
);
await app.destroy();
await app.stop({
cliArgs,
});
});
cli
.command('init')
.command('install')
.option('-f, --force')
.action(async (opts, ...args) => {
if (!opts?.force) {
const tables = await app.db.sequelize.getQueryInterface().showAllTables();
if (tables.includes('collections')) {
console.log('NocoBase is already installed. To reinstall, please execute:');
console.log();
let command = 'yarn nocobase init --force';
for (const [key, value] of Object.entries(opts || {})) {
command += value === true ? ` --${key}` : ` --${key}=${value}`;
}
console.log(command);
console.log();
return;
}
}
await app.db.sync({
force: true,
.action(async (...cliArgs) => {
const [opts] = cliArgs;
await app.install({
cliArgs,
sync: {
force: opts.force,
},
});
await app.stop({
cliArgs,
});
await app.emitAsync('db.init', opts, ...args);
await app.destroy();
});
cli
.command('start')
.option('-p, --port [port]')
.action(async (...args) => {
const cli = args.pop();
const opts = cli.opts();
await app.emitAsync('beforeStart');
await app.start(opts.port || 3000);
console.log(`http://localhost:${opts.port || 3000}/`);
.action(async (...cliArgs) => {
const [opts] = cliArgs;
const port = opts.port || 3000;
await app.start({
cliArgs,
listen: {
port,
},
});
console.log(`http://localhost:${port}/`);
});
return cli;
}

View File

@ -1,5 +1,6 @@
import { Database } from '@nocobase/database';
import { Application } from './application';
import path from 'path';
export interface PluginInterface {
beforeLoad?: () => void;
@ -41,5 +42,16 @@ export abstract class Plugin<O = any> implements PluginInterface {
beforeLoad() {}
abstract load();
async load() {
const collectionPath = this.collectionPath();
if (collectionPath) {
await this.db.import({
directory: collectionPath,
});
}
}
collectionPath() {
return null;
}
}