UNPKG

niledatabase

Version:

Command line interface for Nile databases

369 lines (363 loc) 18.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TenantsCommand = void 0; exports.createTenantsCommand = createTenantsCommand; const commander_1 = require("commander"); const pg_1 = require("pg"); const config_1 = require("../lib/config"); const api_1 = require("../lib/api"); const colors_1 = require("../lib/colors"); const globalOptions_1 = require("../lib/globalOptions"); const errorHandling_1 = require("../lib/errorHandling"); const cli_table3_1 = __importDefault(require("cli-table3")); const axios_1 = __importDefault(require("axios")); async function getWorkspaceAndDatabase(options) { const configManager = new config_1.ConfigManager(options); const workspaceSlug = configManager.getWorkspace(); if (!workspaceSlug) { throw new Error('No workspace specified. Use one of:\n' + '1. --workspace flag\n' + '2. nile config --workspace <n>\n' + '3. NILE_WORKSPACE environment variable'); } const databaseName = configManager.getDatabase(); if (!databaseName) { throw new Error('No database specified. Use one of:\n' + '1. --db flag\n' + '2. nile config --db <n>\n' + '3. NILE_DB environment variable'); } return { workspaceSlug, databaseName }; } async function getPostgresClient(api, workspaceSlug, databaseName, options) { // Get database credentials from control plane console.log(colors_1.theme.dim('\nFetching database credentials...')); const credentials = await api.createDatabaseCredentials(workspaceSlug, databaseName); if (!credentials.id || !credentials.password) { throw new Error('Invalid credentials received from server'); } // Create postgres connection URL const region = credentials.database.region.toLowerCase(); const regionParts = region.split('_'); const regionPrefix = `${regionParts[1]}-${regionParts[2]}-${regionParts[3]}`; // e.g., us-west-2 // Use custom host if provided, otherwise use default with region prefix const dbHost = options.dbHost ? `${regionPrefix}.${options.dbHost}` : `${regionPrefix}.db.thenile.dev`; // Create postgres client const client = new pg_1.Client({ host: dbHost, port: 5432, database: databaseName, user: credentials.id, password: credentials.password, ssl: { rejectUnauthorized: false } }); if (options.debug) { console.log(colors_1.theme.dim('\nConnecting to PostgreSQL:')); console.log(colors_1.theme.dim('Host:'), dbHost); console.log(colors_1.theme.dim('Database:'), databaseName); console.log(colors_1.theme.dim('User:'), credentials.id); console.log(); } // Connect to the database await client.connect(); return client; } class TenantsCommand { constructor(program, getGlobalOptions) { const tenants = program .command('tenants') .description('Manage tenants in your database') .addHelpText('after', ` Examples: # List tenants ${(0, colors_1.formatCommand)('nile tenants list')} List all tenants ${(0, colors_1.formatCommand)('nile tenants list', '--workspace myworkspace')} List tenants in specific workspace ${(0, colors_1.formatCommand)('nile tenants list', '--db mydb')} List tenants in specific database ${(0, colors_1.formatCommand)('nile tenants list', '--format json')} Output in JSON format ${(0, colors_1.formatCommand)('nile tenants list', '--format csv')} Output in CSV format # Create tenants ${(0, colors_1.formatCommand)('nile tenants create', '--name "My Tenant"')} Create tenant with auto-generated ID ${(0, colors_1.formatCommand)('nile tenants create', '--name "My Tenant" --id custom-id')} Create tenant with custom ID ${(0, colors_1.formatCommand)('nile tenants create', '--name "Customer A"')} Create tenant for a customer ${(0, colors_1.formatCommand)('nile tenants create', '--name "Organization B" --id org-b')} Create tenant with organization ID # Update tenants ${(0, colors_1.formatCommand)('nile tenants update', '--id tenant-123 --new_name "New Name"')} Update tenant name ${(0, colors_1.formatCommand)('nile tenants update', '--id org-b --new_name "Organization B Ltd"')} Update organization name ${(0, colors_1.formatCommand)('nile tenants update', '--id custom-id --new_name "Updated Name"')} Update custom tenant name # Delete tenants ${(0, colors_1.formatCommand)('nile tenants delete', '--id tenant-123')} Delete tenant by ID ${(0, colors_1.formatCommand)('nile tenants delete', '--id org-b')} Delete organization tenant ${(0, colors_1.formatCommand)('nile tenants delete', '--id 550e8400-e29b-41d4-a716-446655440000')} Delete tenant by UUID # Using with different output formats ${(0, colors_1.formatCommand)('nile tenants list', '--format json')} List tenants in JSON format ${(0, colors_1.formatCommand)('nile tenants list', '--format csv')} List tenants in CSV format ${(0, colors_1.formatCommand)('nile tenants create', '--name "New Tenant" --format json')} Create tenant with JSON output ${(0, globalOptions_1.getGlobalOptionsHelp)()}`); const listCmd = new commander_1.Command('list') .description('List all tenants in the database') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile tenants list')} List all tenants ${(0, colors_1.formatCommand)('nile tenants list', '--workspace myworkspace')} List tenants in specific workspace ${(0, colors_1.formatCommand)('nile tenants list', '--db mydb')} List tenants in specific database `) .action(async () => { let client; try { const options = getGlobalOptions(); const configManager = new config_1.ConfigManager(options); let token = configManager.getToken(); if (!token) { await (0, errorHandling_1.forceRelogin)(configManager); token = configManager.getToken(); if (!token) { throw new Error('Failed to get token after re-login'); } } const api = new api_1.NileAPI({ token, dbHost: configManager.getDbHost(), controlPlaneUrl: configManager.getGlobalHost(), debug: options.debug }); const { workspaceSlug, databaseName } = await getWorkspaceAndDatabase(options); client = await getPostgresClient(api, workspaceSlug, databaseName, options); console.log(colors_1.theme.dim('\nFetching tenants...')); const result = await client.query('SELECT * FROM tenants'); const tenants = result.rows; if (tenants.length === 0) { console.log('No tenants found'); return; } console.log('\nTenants:'); const tenantsTable = new cli_table3_1.default({ head: [ colors_1.theme.header('ID'), colors_1.theme.header('NAME') ], style: { head: [], border: [], }, chars: { 'top': '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐', 'bottom': '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘', 'left': '│', 'left-mid': '├', 'mid': '─', 'mid-mid': '┼', 'right': '│', 'right-mid': '┤', 'middle': '│' } }); tenants.forEach(tenant => { tenantsTable.push([ colors_1.theme.primary(tenant.id), colors_1.theme.info(tenant.name || '(unnamed)') ]); }); console.log(tenantsTable.toString()); } catch (error) { if (axios_1.default.isAxiosError(error) && (error.response?.status === 401 || error.message === 'Token is required')) { await (0, errorHandling_1.handleTenantError)(error, 'list tenants', new config_1.ConfigManager(getGlobalOptions())); } else { throw error; } } finally { if (client) { await client.end(); } } }); const createCmd = new commander_1.Command('create') .description('Create a new tenant') .requiredOption('--name <name>', 'Name of the tenant') .option('--id <id>', 'Optional UUID for the tenant') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile tenants create', '--name "My Tenant"')} Create a tenant with auto-generated ID ${(0, colors_1.formatCommand)('nile tenants create', '--name "My Tenant" --id custom-id')} Create tenant with custom ID `) .action(async (cmdOptions) => { let client; try { const options = getGlobalOptions(); const configManager = new config_1.ConfigManager(options); const api = new api_1.NileAPI({ token: configManager.getToken(), dbHost: configManager.getDbHost(), controlPlaneUrl: configManager.getGlobalHost(), }); const { workspaceSlug, databaseName } = await getWorkspaceAndDatabase(options); client = await getPostgresClient(api, workspaceSlug, databaseName, options); console.log(colors_1.theme.dim(`\nCreating tenant...with name: ${cmdOptions.name} and id: ${cmdOptions.id}`)); let result; if (cmdOptions.id) { result = await client.query('INSERT INTO tenants (id, name) VALUES ($1, $2) RETURNING *', [cmdOptions.id, cmdOptions.name]); } else { result = await client.query('INSERT INTO tenants (name) VALUES ($1) RETURNING *', [cmdOptions.name]); } const tenant = result.rows[0]; console.log('\nTenant created:'); const detailsTable = new cli_table3_1.default({ style: { head: [], border: [], }, chars: { 'top': '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐', 'bottom': '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘', 'left': '│', 'left-mid': '├', 'mid': '─', 'mid-mid': '┼', 'right': '│', 'right-mid': '┤', 'middle': '│' } }); detailsTable.push([colors_1.theme.secondary('ID:'), colors_1.theme.primary(tenant.id)], [colors_1.theme.secondary('Name:'), colors_1.theme.info(tenant.name)]); console.log(detailsTable.toString()); } catch (error) { if (axios_1.default.isAxiosError(error) && (error.response?.status === 401 || error.message === 'Token is required')) { await (0, errorHandling_1.handleTenantError)(error, 'create tenant', new config_1.ConfigManager(getGlobalOptions())); } else { throw error; } } finally { if (client) { await client.end(); } } }); const deleteCmd = new commander_1.Command('delete') .description('Delete a tenant') .requiredOption('--id <id>', 'ID of the tenant to delete') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile tenants delete', '--id tenant-123')} Delete a tenant by ID ${(0, colors_1.formatCommand)('nile tenants delete', '--id 550e8400-e29b-41d4-a716-446655440000')} Delete tenant by UUID `) .action(async (cmdOptions) => { let client; try { const options = getGlobalOptions(); const configManager = new config_1.ConfigManager(options); const token = await configManager.getToken(); const api = new api_1.NileAPI({ token, dbHost: configManager.getDbHost(), controlPlaneUrl: configManager.getGlobalHost(), }); const { workspaceSlug, databaseName } = await getWorkspaceAndDatabase(options); // First verify the tenant exists client = await getPostgresClient(api, workspaceSlug, databaseName, options); const checkResult = await client.query('SELECT id, name FROM tenants WHERE id = $1', [cmdOptions.id]); if (checkResult.rowCount === 0) { throw new Error(`Tenant with ID '${cmdOptions.id}' not found`); } const tenant = checkResult.rows[0]; console.log(colors_1.theme.dim(`\nDeleting tenant '${tenant.name}' (${tenant.id})...`)); // Delete the tenant await client.query('DELETE FROM tenants WHERE id = $1', [cmdOptions.id]); console.log(colors_1.theme.success(`\nTenant '${colors_1.theme.bold(tenant.name)}' deleted successfully.`)); } catch (error) { if (axios_1.default.isAxiosError(error) && (error.response?.status === 401 || error.message === 'Token is required')) { await (0, errorHandling_1.handleTenantError)(error, 'delete tenant', new config_1.ConfigManager(getGlobalOptions())); } else { throw error; } } finally { if (client) { await client.end(); } } }); const updateCmd = new commander_1.Command('update') .description('Update a tenant') .requiredOption('--id <id>', 'ID of the tenant to update') .requiredOption('--new_name <name>', 'New name for the tenant') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile tenants update', '--id tenant-123 --new_name "New Name"')} Update tenant name ${(0, colors_1.formatCommand)('nile tenants update', '--id 550e8400-e29b-41d4-a716-446655440000 --new_name "New Name"')} Update tenant with UUID `) .action(async (cmdOptions) => { let client; try { const options = getGlobalOptions(); const configManager = new config_1.ConfigManager(options); const api = new api_1.NileAPI({ token: configManager.getToken(), dbHost: configManager.getDbHost(), controlPlaneUrl: configManager.getGlobalHost(), }); const { workspaceSlug, databaseName } = await getWorkspaceAndDatabase(options); console.log(colors_1.theme.dim('\nUpdating tenant...')); client = await getPostgresClient(api, workspaceSlug, databaseName, options); const result = await client.query('UPDATE tenants SET name = $1 WHERE id = $2 RETURNING *', [cmdOptions.new_name, cmdOptions.id]); if (result.rowCount === 0) { throw new Error(`Tenant with ID '${cmdOptions.id}' not found`); } const tenant = result.rows[0]; console.log(colors_1.theme.success(`\nTenant '${colors_1.theme.bold(tenant.id)}' updated successfully.`)); console.log(colors_1.theme.primary('\nUpdated tenant details:')); console.log(`${colors_1.theme.secondary('ID:')} ${colors_1.theme.primary(tenant.id)}`); console.log(`${colors_1.theme.secondary('Name:')} ${colors_1.theme.info(tenant.name)}`); } catch (error) { if (axios_1.default.isAxiosError(error) && (error.response?.status === 401 || error.message === 'Token is required')) { await (0, errorHandling_1.handleTenantError)(error, 'update tenant', new config_1.ConfigManager(getGlobalOptions())); } else { throw error; } } finally { if (client) { await client.end(); } } }); tenants.addCommand(listCmd); tenants.addCommand(createCmd); tenants.addCommand(deleteCmd); tenants.addCommand(updateCmd); } } exports.TenantsCommand = TenantsCommand; function createTenantsCommand(getGlobalOptions) { const program = new commander_1.Command(); new TenantsCommand(program, getGlobalOptions); return program.commands[0]; }