tachybase_todo/packages/plugins/multi-app-manager/src/server.ts
chenos 05740672a0
feat: multiple apps admin (#1431)
* fix: dynamic routerBase

* fix: start sub app with empty options

* chore: sync options

* fix: sub app create database

* fix: test

* fix: deps

* feat: register app db creator

* feat: default db creator

* feat: app options factory

* chore: api name

* fix: test

* fix: running sub app

* fix: beforeGetApplication hook

* fix: mysql test

* fix: appManager get selector

* chore: callback

---------

Co-authored-by: Chareice <chareice@live.com>
2023-02-14 15:30:58 +08:00

151 lines
4.3 KiB
TypeScript

import Application, { AppManager, InstallOptions, Plugin } from '@nocobase/server';
import { resolve } from 'path';
import * as path from 'path';
import { ApplicationModel } from './models/application';
import Database, { IDatabaseOptions, Model, Transactionable } from '@nocobase/database';
import lodash from 'lodash';
export type AppDbCreator = (app: Application) => Promise<void>;
export type AppOptionsFactory = (appName: string, mainApp: Application) => any;
const defaultDbCreator = async (app: Application) => {
const databaseOptions = app.options.database as any;
const { host, port, username, password, dialect, database } = databaseOptions;
if (dialect === 'mysql') {
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection({ host, port, user: username, password });
await connection.query(`CREATE DATABASE IF NOT EXISTS \`${database}\`;`);
await connection.close();
}
if (dialect === 'postgres') {
const { Client } = require('pg');
const client = new Client({
host,
port,
user: username,
password,
database: 'postgres',
});
await client.connect();
try {
await client.query(`CREATE DATABASE "${database}"`);
} catch (e) {}
await client.end();
}
};
const defaultAppOptionsFactory = (appName: string, mainApp: Application) => {
const rawDatabaseOptions = PluginMultiAppManager.getDatabaseConfig(mainApp);
if (rawDatabaseOptions.dialect === 'sqlite') {
const mainAppStorage = rawDatabaseOptions.storage;
if (mainAppStorage !== ':memory:') {
const mainStorageDir = path.dirname(mainAppStorage);
rawDatabaseOptions.storage = path.join(mainStorageDir, `${appName}.sqlite`);
}
} else {
rawDatabaseOptions.database = appName;
}
return {
database: {
...rawDatabaseOptions,
tablePrefix: '',
},
plugins: ['nocobase'],
resourcer: {
prefix: '/api',
},
};
};
export class PluginMultiAppManager extends Plugin {
appDbCreator: AppDbCreator = defaultDbCreator;
appOptionsFactory: AppOptionsFactory = defaultAppOptionsFactory;
setAppOptionsFactory(factory: AppOptionsFactory) {
this.appOptionsFactory = factory;
}
setAppDbCreator(appDbCreator: AppDbCreator) {
this.appDbCreator = appDbCreator;
}
static getDatabaseConfig(app: Application): IDatabaseOptions {
const oldConfig =
app.options.database instanceof Database
? (app.options.database as Database).options
: (app.options.database as IDatabaseOptions);
return lodash.cloneDeep(lodash.omit(oldConfig, ['migrator']));
}
async install(options?: InstallOptions) {
const repo = this.db.getRepository<any>('collections');
if (repo) {
await repo.db2cm('applications');
}
}
beforeLoad(): void {
this.app.appManager.setAppSelector((req) => {
return (req.headers['x-app'] || null) as any;
});
}
async load() {
this.db.registerModels({
ApplicationModel,
});
await this.db.import({
directory: resolve(__dirname, 'collections'),
});
this.db.on('applications.afterCreateWithAssociations', async (model: ApplicationModel, options) => {
const { transaction } = options;
await model.registerToMainApp(this.app, {
transaction,
dbCreator: this.appDbCreator,
appOptionsFactory: this.appOptionsFactory,
});
});
this.db.on('applications.afterDestroy', async (model: ApplicationModel) => {
await this.app.appManager.removeApplication(model.get('name') as string);
});
this.app.appManager.on(
'beforeGetApplication',
async ({ appManager, name }: { appManager: AppManager; name: string }) => {
if (!appManager.applications.has(name)) {
const existsApplication = (await this.app.db.getRepository('applications').findOne({
filter: {
name,
},
})) as ApplicationModel | null;
if (existsApplication) {
await existsApplication.registerToMainApp(this.app, {
dbCreator: this.appDbCreator,
appOptionsFactory: this.appOptionsFactory,
});
}
}
},
);
this.app.acl.registerSnippet({
name: `pm.${this.name}.applications`,
actions: ['applications:*'],
});
}
}