feat(server): bigcapital cli commands

This commit is contained in:
a.bouhuolia
2023-02-09 23:38:13 +02:00
parent d0e84fb51a
commit 7e7ee24109
4 changed files with 356 additions and 23 deletions

View File

@@ -0,0 +1,282 @@
#!/usr/bin/env node
const commander = require('commander');
const color = require('colorette');
const argv = require('getopts');
const Knex = require('knex');
const { knexSnakeCaseMappers } = require('objection');
const config = require('../src/config');
function initSystemKnex() {
return Knex({
client: config.system.db_client,
connection: {
host: config.system.db_host,
user: config.system.db_user,
password: config.system.db_password,
database: config.system.db_name,
charset: 'utf8',
},
migrations: {
directory: config.system.migrations_dir,
},
seeds: {
directory: config.system.seeds_dir,
},
pool: { min: 0, max: 7 },
...knexSnakeCaseMappers({ upperCase: true }),
});
}
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);
}
function getAllSystemTenants(knex) {
return knex('tenants');
}
// module.exports = {
// log,
// success,
// exit,
// initSystemKnex,
// };
// - 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
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('Migrate 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 <name>')
.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: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:make <name>')
.description('Created a named migration file to the tenants database.')
.action(async (name) => {
const sysKnex = await initTenantKnex();
sysKnex.migrate
.make(name)
.then((name) => {
success(color.green(`Created Migration: ${name}`));
})
.catch(exit);
});
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
.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.parse();

View File

@@ -0,0 +1,51 @@
const Knex = require('knex');
const { knexSnakeCaseMappers } = require('objection');
const color = require('colorette');
const config = require('./config');
function initSystemKnex() {
return Knex({
client: config.system.db_client,
connection: {
host: config.system.db_host,
user: config.system.db_user,
password: config.system.db_password,
database: config.system.db_name,
charset: 'utf8',
},
migrations: {
directory: config.system.migrations_dir,
},
seeds: {
directory: config.system.seeds_dir,
},
pool: { min: 0, max: 7 },
...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);
}
module.exports = {
log,
success,
exit,
initSystemKnex,
};

View File

@@ -1,4 +1,4 @@
import dotenv from 'dotenv';
const dotenv = require('dotenv');
// Set the NODE_ENV to 'development' by default
// process.env.NODE_ENV = process.env.NODE_ENV || 'development';
@@ -9,7 +9,7 @@ if (envFound.error) {
throw new Error("⚠️ Couldn't find .env file ⚠️");
}
export default {
module.exports = {
/**
* Your favorite port
*/

View File

@@ -1144,6 +1144,11 @@
"@babel/plugin-transform-typescript": "^7.9.0"
}
},
"@babel/regjsgen": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz",
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
},
"@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
@@ -3516,9 +3521,9 @@
}
},
"async-each": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.5.tgz",
"integrity": "sha512-5QzqtU3BlagehwmdoqwaS2FBQF2P5eL6vFqXwNsb5jwoEsmtfAXg1ocFvW7I6/gGLFhBMKwcMwZuy7uv/Bo9jA=="
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz",
"integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg=="
},
"async-foreach": {
"version": "0.1.3",
@@ -4681,9 +4686,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001450",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz",
"integrity": "sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew=="
"version": "1.0.30001451",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz",
"integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w=="
},
"capture-exit": {
"version": "2.0.0",
@@ -6015,9 +6020,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"electron-to-chromium": {
"version": "1.4.285",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.285.tgz",
"integrity": "sha512-47o4PPgxfU1KMNejz+Dgaodf7YTcg48uOfV1oM6cs3adrl2+7R+dHkt3Jpxqo0LRCbGJEzTKMUt0RdvByb/leg=="
"version": "1.4.289",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.289.tgz",
"integrity": "sha512-relLdMfPBxqGCxy7Gyfm1HcbRPcFUJdlgnCPVgQ23sr1TvUrRJz0/QPoGP0+x41wOVSTN/Wi3w6YDgHiHJGOzg=="
},
"elliptic": {
"version": "6.5.4",
@@ -10984,9 +10989,9 @@
}
},
"node-releases": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.9.tgz",
"integrity": "sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA=="
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
},
"node-sass": {
"version": "4.14.1",
@@ -14129,23 +14134,18 @@
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg=="
},
"regexpu-core": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz",
"integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.0.tgz",
"integrity": "sha512-ZdhUQlng0RoscyW7jADnUZ25F5eVtHdMyXSb2PiwafvteRAOJUjFoUPEYZSIfP99fBIs3maLIRfpEddT78wAAQ==",
"requires": {
"@babel/regjsgen": "^0.8.0",
"regenerate": "^1.4.2",
"regenerate-unicode-properties": "^10.1.0",
"regjsgen": "^0.7.1",
"regjsparser": "^0.9.1",
"unicode-match-property-ecmascript": "^2.0.0",
"unicode-match-property-value-ecmascript": "^2.1.0"
}
},
"regjsgen": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz",
"integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA=="
},
"regjsparser": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz",