From ab21f439e2b6a295b794560b26aed95d90452e1c Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 25 May 2020 06:14:44 +0200 Subject: [PATCH] feat: bigcapital CLI. --- client/.prettierrc | 10 ++ server/bin/bigcapital.js | 250 +++++++++++++++++++++++++++++++++++++++ server/cli/tenants.js | 13 -- server/package.json | 5 + 4 files changed, 265 insertions(+), 13 deletions(-) create mode 100644 client/.prettierrc create mode 100644 server/bin/bigcapital.js delete mode 100644 server/cli/tenants.js diff --git a/client/.prettierrc b/client/.prettierrc new file mode 100644 index 000000000..168c3f294 --- /dev/null +++ b/client/.prettierrc @@ -0,0 +1,10 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "printWidth": 80, + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 2, + "useTabs": false, +} diff --git a/server/bin/bigcapital.js b/server/bin/bigcapital.js new file mode 100644 index 000000000..0109aba7f --- /dev/null +++ b/server/bin/bigcapital.js @@ -0,0 +1,250 @@ +const commander = require('commander'); +const { knexSnakeCaseMappers } = require('objection'); +const Knex = require('knex'); +const color = require('colorette'); +const argv = require('getopts')(process.argv.slice(2)); +const systemConfig = require('../config/systemKnexfile'); +const config = require('../config/config'); + +// - bigcapital system:migrate:latest +// - bigcapital system:migrate:rollback +// - bigcapital tenants:migrate:latest +// - bigcapital tenants:migrate:latest --tenant_id=XXX +// - bigcapital tenants:migrate:rollback +// - bigcapital tenants:migrate:rollback --tenant_id=XXX +// - bigcapital tenants:migrate:make +// - bigcapital system:migrate:make +// - bigcapital tenants:list + +function initSystemKnex() { + return Knex({ + ...systemConfig['production'], + ...knexSnakeCaseMappers({ upperCase: true }), + }); +} + +function getAllSystemTenants(knex) { + return knex('tenants'); +} + +function initTenantKnex(organizationId) { + return Knex({ + client: config.tenant.db_client, + connection: { + host: config.tenant.db_host, + user: config.tenant.db_user, + password: config.tenant.db_password, + database: `${config.tenant.db_name_prefix}${organizationId}`, + charset: config.tenant.charset, + }, + migrations: { + directory: config.tenant.migrations_dir, + }, + seeds: { + directory: config.tenant.seeds_dir, + }, + pool: { min: 0, max: 5 }, + ...knexSnakeCaseMappers({ upperCase: true }), + }) +} + +function exit(text) { + if (text instanceof Error) { + console.error( + color.red(`${text.detail ? `${text.detail}\n` : ''}${text.stack}`) + ); + } else { + console.error(color.red(text)); + } + process.exit(1); +} + +function success(text) { + console.log(text); + process.exit(0); +} + +function log(text) { + console.log(text); +} + +commander + .command('system:migrate:rollback') + .description('Migrate the system database of the application.') + .action(async () => { + try { + const sysKnex = await initSystemKnex(); + const [batchNo, _log] = await sysKnex.migrate.rollback(); + + if (_log.length === 0) { + success(color.cyan('Already at the base migration')); + } + success( + color.green( + `Batch ${batchNo} rolled back: ${_log.length} migrations` + ) + (argv.verbose ? `\n${color.cyan(_log.join('\n'))}` : '') + ); + } catch(error) { + exit(error); + } + }); + +commander + .command('system:migrate:latest') + .description('Rollback latest mgiration of the system database.') + .action(async () => { + try { + const sysKnex = await initSystemKnex(); + const [batchNo, log] = await sysKnex.migrate.latest(); + + if (log.length === 0) { + success(color.cyan('Already up to date')); + } + success( + color.green(`Batch ${batchNo} run: ${log.length} migrations`) + + (argv.verbose ? `\n${color.cyan(log.join('\n'))}` : '') + ); + } catch(error) { + exit(error); + } + }); + +commander + .command('system:migrate:make ') + .description('Created a named migration file to the system database.') + .action(async (name) => { + const sysKnex = await initSystemKnex(); + + sysKnex.migrate.make(name).then((name) => { + success(color.green(`Created Migration: ${name}`)); + }).catch(exit) + }); + +commander + .command('tenants:migrate:make ') + .description('Created a name migration file to the tenants databases.') + .action(async (name) => { + const sysKnex = await initTenantKnex(); + + sysKnex.migrate.make(name).then((name) => { + success(color.green(`Created Migration: ${name}`)); + }).catch(exit) + }); + +commander + .command('tenants:list') + .description('Retrieve a list of all system tenants databases.') + .action(async (cmd) => { + try{ + const sysKnex = await initSystemKnex(); + const tenants = await getAllSystemTenants(sysKnex); + + tenants.forEach((tenant) => { + const dbName = `${config.tenant.db_name_prefix}${tenant.organizationId}`; + console.log(`ID: ${tenant.id} | Organization ID: ${tenant.organizationId} | DB Name: ${dbName}`); + }); + } catch(error) { exit(error); }; + success('---'); + }); + +commander + .command('tenants:migrate:rollback') + .description('Rollback the last batch of tenants migrations.') + .option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.') + .action(async (cmd) => { + try { + const sysKnex = await initSystemKnex(); + const tenants = await getAllSystemTenants(sysKnex); + const tenantsOrgsIds = tenants.map((tenant) => tenant.organizationId); + + if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) { + exit(`The given tenant id ${cmd.tenant_id} is not exists.`); + } + + const migrateOpers = []; + const migrateTenant = async (organizationId) => { + try { + const tenantKnex = await initTenantKnex(organizationId); + const [batchNo, _log] = await tenantKnex.migrate.rollback(); + const tenantDb = `${config.tenant.db_name_prefix}${organizationId}`; + + if (_log.length === 0) { + log(color.cyan('Already at the base migration')); + } + log( + color.green( + `Tenant: ${tenantDb} > Batch ${batchNo} rolled back: ${_log.length} migrations` + ) + (argv.verbose ? `\n${color.cyan(_log.join('\n'))}` : '') + ); + log('---------------'); + } catch (error) { exit(error); } + }; + + if (!cmd.tenant_id) { + tenants.forEach((tenant) => { + const oper = migrateTenant(tenant.organizationId); + migrateOpers.push(oper); + }); + } else { + const oper = migrateTenant(cmd.tenant_id); + migrateOpers.push(oper); + } + Promise.all(migrateOpers).then(() => { + success('All tenants are rollbacked.'); + }); + } catch (error) { exit(error); } + }); + +commander + .command('tenants:migrate:latest') + .description('Migrate all tenants or the given tenant id.') + .option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.') + .action(async (cmd) => { + try { + const sysKnex = await initSystemKnex(); + const tenants = await getAllSystemTenants(sysKnex); + const tenantsOrgsIds = tenants.map(tenant => tenant.organizationId); + + if (cmd.tenant_id && tenantsOrgsIds.indexOf(cmd.tenant_id) === -1) { + exit(`The given tenant id ${cmd.tenant_id} is not exists.`); + } + // Validate the tenant id exist first of all. + const migrateOpers = []; + const migrateTenant = async (organizationId) => { + try { + const tenantKnex = await initTenantKnex(organizationId); + const [batchNo, _log] = await tenantKnex.migrate.latest(); + + const tenantDb = `${config.tenant.db_name_prefix}${organizationId}`; + + if (_log.length === 0) { + log(color.cyan('Already up to date')); + } + log( + color.green(`Tenant ${tenantDb} > Batch ${batchNo} run: ${_log.length} migrations`) + + (argv.verbose ? `\n${color.cyan(log.join('\n'))}` : '') + ); + log('-------------------'); + } catch (error) { + log(error); + } + } + if (!cmd.tenant_id) { + tenants.forEach((tenant) => { + const oper = migrateTenant(tenant.organizationId); + migrateOpers.push(oper); + }); + } else { + const oper = migrateTenant(cmd.tenant_id); + migrateOpers.push(oper); + } + + Promise.all(migrateOpers).then(() => { + success('All tenants are migrated.'); + }); + } catch (error) { + exit(error); + } + }); + +commander.parse(process.argv); \ No newline at end of file diff --git a/server/cli/tenants.js b/server/cli/tenants.js deleted file mode 100644 index b601e432d..000000000 --- a/server/cli/tenants.js +++ /dev/null @@ -1,13 +0,0 @@ -import { program } from 'commander'; - -program - .version('0.0.1') - .description('An application for pizzas ordering') - .command('tenants:migrate') - .description('Migrate all tenants or the given tenant id.') - .option('-t, --tenant_id [tenant_id]', 'Which tenant id do you migrate.') - .action(async () => { - - }); - -program.parse(process.argv); \ No newline at end of file diff --git a/server/package.json b/server/package.json index ebacb4880..d98472f3a 100644 --- a/server/package.json +++ b/server/package.json @@ -13,6 +13,9 @@ }, "author": "Ahmed Bouhuolia, ", "license": "ISC", + "bin": { + "bigcapital": "./bin/bigcapital.js" + }, "dependencies": { "@hapi/boom": "^7.4.3", "app-root-path": "^3.0.0", @@ -61,6 +64,7 @@ "chai": "^4.2.0", "chai-http": "^4.3.0", "chai-things": "^0.2.0", + "colorette": "^1.2.0", "commander": "^5.0.0", "cross-env": "^5.2.0", "eslint": "^6.2.1", @@ -70,6 +74,7 @@ "eslint-loader": "^2.2.1", "eslint-plugin-import": "^2.19.1", "faker": "^4.1.0", + "getopts": "^2.2.5", "knex-factory": "0.0.6", "mocha": "^5.2.0", "mocha-webpack": "^2.0.0-beta.0",