2023-03-10 19:16:00 +08:00
|
|
|
import Database, { IDatabaseOptions } from '@nocobase/database';
|
2023-02-14 15:30:58 +08:00
|
|
|
import Application, { AppManager, InstallOptions, Plugin } from '@nocobase/server';
|
2023-03-10 19:16:00 +08:00
|
|
|
import lodash from 'lodash';
|
2023-02-14 15:30:58 +08:00
|
|
|
import * as path from 'path';
|
2023-03-10 19:16:00 +08:00
|
|
|
import { resolve } from 'path';
|
2022-03-28 22:01:10 +08:00
|
|
|
import { ApplicationModel } from './models/application';
|
2023-02-14 15:30:58 +08:00
|
|
|
|
|
|
|
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',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|
2022-03-28 22:01:10 +08:00
|
|
|
|
2022-04-24 20:22:50 +08:00
|
|
|
export class PluginMultiAppManager extends Plugin {
|
2023-02-14 15:30:58 +08:00
|
|
|
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']));
|
|
|
|
}
|
|
|
|
|
2022-04-30 23:41:01 +08:00
|
|
|
async install(options?: InstallOptions) {
|
2023-03-10 19:16:00 +08:00
|
|
|
// const repo = this.db.getRepository<any>('collections');
|
|
|
|
// if (repo) {
|
|
|
|
// await repo.db2cm('applications');
|
|
|
|
// }
|
2022-04-30 23:41:01 +08:00
|
|
|
}
|
|
|
|
|
2023-03-10 19:16:00 +08:00
|
|
|
beforeLoad() {
|
|
|
|
this.db.registerModels({
|
|
|
|
ApplicationModel,
|
2023-02-14 15:30:58 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-28 22:01:10 +08:00
|
|
|
async load() {
|
2023-03-10 19:16:00 +08:00
|
|
|
this.app.appManager.setAppSelector(async (req) => {
|
|
|
|
if (req.headers['x-app']) {
|
|
|
|
return req.headers['x-app'];
|
|
|
|
}
|
|
|
|
if (req.headers['x-hostname']) {
|
|
|
|
const appInstance = await this.db.getRepository('applications').findOne({
|
|
|
|
filter: {
|
|
|
|
cname: req.headers['x-hostname'],
|
|
|
|
},
|
|
|
|
});
|
|
|
|
if (appInstance) {
|
|
|
|
return appInstance.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
2022-03-28 22:01:10 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
await this.db.import({
|
|
|
|
directory: resolve(__dirname, 'collections'),
|
|
|
|
});
|
|
|
|
|
|
|
|
this.db.on('applications.afterCreateWithAssociations', async (model: ApplicationModel, options) => {
|
|
|
|
const { transaction } = options;
|
2022-04-29 20:00:50 +08:00
|
|
|
|
2023-02-14 15:30:58 +08:00
|
|
|
await model.registerToMainApp(this.app, {
|
|
|
|
transaction,
|
|
|
|
dbCreator: this.appDbCreator,
|
|
|
|
appOptionsFactory: this.appOptionsFactory,
|
|
|
|
});
|
2022-03-28 22:01:10 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
this.db.on('applications.afterDestroy', async (model: ApplicationModel) => {
|
|
|
|
await this.app.appManager.removeApplication(model.get('name') as string);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.app.appManager.on(
|
|
|
|
'beforeGetApplication',
|
2023-02-14 15:30:58 +08:00
|
|
|
async ({ appManager, name }: { appManager: AppManager; name: string }) => {
|
2022-03-28 22:01:10 +08:00
|
|
|
if (!appManager.applications.has(name)) {
|
|
|
|
const existsApplication = (await this.app.db.getRepository('applications').findOne({
|
|
|
|
filter: {
|
|
|
|
name,
|
|
|
|
},
|
|
|
|
})) as ApplicationModel | null;
|
|
|
|
|
|
|
|
if (existsApplication) {
|
2023-02-14 15:30:58 +08:00
|
|
|
await existsApplication.registerToMainApp(this.app, {
|
|
|
|
dbCreator: this.appDbCreator,
|
|
|
|
appOptionsFactory: this.appOptionsFactory,
|
|
|
|
});
|
2022-03-28 22:01:10 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2023-01-09 07:35:48 +08:00
|
|
|
|
2023-03-10 19:16:00 +08:00
|
|
|
this.app.resourcer.registerActionHandlers({
|
|
|
|
'applications:listPinned': async (ctx, next) => {
|
|
|
|
const items = await this.db.getRepository('applications').find({
|
|
|
|
filter: {
|
|
|
|
pinned: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
ctx.body = items;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
this.app.acl.allow('applications', 'listPinned', 'loggedIn');
|
|
|
|
|
2023-01-09 07:35:48 +08:00
|
|
|
this.app.acl.registerSnippet({
|
|
|
|
name: `pm.${this.name}.applications`,
|
2023-02-14 15:30:58 +08:00
|
|
|
actions: ['applications:*'],
|
2023-01-09 07:35:48 +08:00
|
|
|
});
|
2022-03-28 22:01:10 +08:00
|
|
|
}
|
|
|
|
}
|