UNPKG

niledatabase

Version:

Command line interface for Nile databases

383 lines (377 loc) 18.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.UsersCommand = void 0; exports.createUsersCommand = createUsersCommand; 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 UsersCommand { constructor(program, getGlobalOptions) { const users = program .command('users') .description('Manage users in your database') .addHelpText('after', ` Examples: # List users ${(0, colors_1.formatCommand)('nile users list')} List all users ${(0, colors_1.formatCommand)('nile users list', '--workspace myworkspace')} List users in specific workspace ${(0, colors_1.formatCommand)('nile users list', '--db mydb')} List users in specific database ${(0, colors_1.formatCommand)('nile users list', '--format json')} Output in JSON format ${(0, colors_1.formatCommand)('nile users list', '--format csv')} Output in CSV format # Create users ${(0, colors_1.formatCommand)('nile users create', '--email "user@example.com" --password "password123"')} Create user with email and password ${(0, colors_1.formatCommand)('nile users create', '--email "user@example.com" --password "password123" --tenant tenant-123')} Create user in specific tenant # Update users ${(0, colors_1.formatCommand)('nile users update', '--id user-123 --new_email "new@example.com"')} Update user email ${(0, colors_1.formatCommand)('nile users update', '--id user-123 --new_password "newpassword123"')} Update user password # Delete users ${(0, colors_1.formatCommand)('nile users delete', '--id user-123')} Delete user by ID ${(0, colors_1.formatCommand)('nile users delete', '--id 550e8400-e29b-41d4-a716-446655440000')} Delete user by UUID # Using with different output formats ${(0, colors_1.formatCommand)('nile users list', '--format json')} List users in JSON format ${(0, colors_1.formatCommand)('nile users list', '--format csv')} List users in CSV format ${(0, colors_1.formatCommand)('nile users create', '--email "user@example.com" --password "password123" --format json')} Create user with JSON output ${(0, globalOptions_1.getGlobalOptionsHelp)()}`); const listCmd = new commander_1.Command('list') .description('List all users in the database') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile users list')} List all users ${(0, colors_1.formatCommand)('nile users list', '--workspace myworkspace')} List users in specific workspace ${(0, colors_1.formatCommand)('nile users list', '--db mydb')} List users 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 users...')); const result = await client.query('SELECT * FROM users'); const users = result.rows; if (users.length === 0) { console.log('No users found'); return; } console.log('\nUsers:'); const usersTable = new cli_table3_1.default({ head: [ colors_1.theme.header('ID'), colors_1.theme.header('EMAIL'), colors_1.theme.header('TENANT ID') ], 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': '│' } }); users.forEach(user => { usersTable.push([ colors_1.theme.primary(user.id), colors_1.theme.info(user.email), colors_1.theme.secondary(user.tenant_id || '(none)') ]); }); console.log(usersTable.toString()); } catch (error) { if (axios_1.default.isAxiosError(error) && (error.response?.status === 401 || error.message === 'Token is required')) { await (0, errorHandling_1.handleUserError)(error, 'list users', 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 user') .requiredOption('--email <email>', 'Email address of the user') .requiredOption('--password <password>', 'Password for the user') .option('--tenant <tenant>', 'Tenant ID to associate with the user') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile users create', '--email "user@example.com" --password "password123"')} Create a user with email and password ${(0, colors_1.formatCommand)('nile users create', '--email "user@example.com" --password "password123" --tenant tenant-123')} Create user in specific tenant `) .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 user with email: ${cmdOptions.email}`)); const result = await client.query('INSERT INTO users (email, password, tenant_id) VALUES ($1, $2, $3) RETURNING *', [cmdOptions.email, cmdOptions.password, cmdOptions.tenant]); const user = result.rows[0]; console.log('\nUser 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(user.id)], [colors_1.theme.secondary('Email:'), colors_1.theme.info(user.email)], [colors_1.theme.secondary('Tenant ID:'), colors_1.theme.secondary(user.tenant_id || '(none)')]); 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.handleUserError)(error, 'create user', new config_1.ConfigManager(getGlobalOptions())); } else { throw error; } } finally { if (client) { await client.end(); } } }); const deleteCmd = new commander_1.Command('delete') .description('Delete a user') .requiredOption('--id <id>', 'ID of the user to delete') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile users delete', '--id user-123')} Delete a user by ID ${(0, colors_1.formatCommand)('nile users delete', '--id 550e8400-e29b-41d4-a716-446655440000')} Delete user 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 user exists client = await getPostgresClient(api, workspaceSlug, databaseName, options); const checkResult = await client.query('SELECT id, email FROM users WHERE id = $1', [cmdOptions.id]); if (checkResult.rowCount === 0) { throw new Error(`User with ID '${cmdOptions.id}' not found`); } const user = checkResult.rows[0]; console.log(colors_1.theme.dim(`\nDeleting user '${user.email}' (${user.id})...`)); // Delete the user await client.query('DELETE FROM users WHERE id = $1', [cmdOptions.id]); console.log(colors_1.theme.success(`\nUser '${colors_1.theme.bold(user.email)}' deleted successfully.`)); } catch (error) { if (axios_1.default.isAxiosError(error) && (error.response?.status === 401 || error.message === 'Token is required')) { await (0, errorHandling_1.handleUserError)(error, 'delete user', new config_1.ConfigManager(getGlobalOptions())); } else { throw error; } } finally { if (client) { await client.end(); } } }); const updateCmd = new commander_1.Command('update') .description('Update a user') .requiredOption('--id <id>', 'ID of the user to update') .option('--new_email <email>', 'New email address for the user') .option('--new_password <password>', 'New password for the user') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile users update', '--id user-123 --new_email "new@example.com"')} Update user email ${(0, colors_1.formatCommand)('nile users update', '--id user-123 --new_password "newpassword123"')} Update user password `) .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 user...')); client = await getPostgresClient(api, workspaceSlug, databaseName, options); // Build update query based on provided options const updates = []; const values = []; let paramCount = 1; if (cmdOptions.new_email) { updates.push(`email = $${paramCount}`); values.push(cmdOptions.new_email); paramCount++; } if (cmdOptions.new_password) { updates.push(`password = $${paramCount}`); values.push(cmdOptions.new_password); paramCount++; } if (updates.length === 0) { throw new Error('No update fields provided. Use --new_email or --new_password'); } values.push(cmdOptions.id); const query = `UPDATE users SET ${updates.join(', ')} WHERE id = $${paramCount} RETURNING *`; const result = await client.query(query, values); if (result.rowCount === 0) { throw new Error(`User with ID '${cmdOptions.id}' not found`); } const user = result.rows[0]; console.log(colors_1.theme.success(`\nUser '${colors_1.theme.bold(user.id)}' updated successfully.`)); console.log(colors_1.theme.primary('\nUpdated user details:')); console.log(`${colors_1.theme.secondary('ID:')} ${colors_1.theme.primary(user.id)}`); console.log(`${colors_1.theme.secondary('Email:')} ${colors_1.theme.info(user.email)}`); console.log(`${colors_1.theme.secondary('Tenant ID:')} ${colors_1.theme.secondary(user.tenant_id || '(none)')}`); } catch (error) { if (axios_1.default.isAxiosError(error) && (error.response?.status === 401 || error.message === 'Token is required')) { await (0, errorHandling_1.handleUserError)(error, 'update user', new config_1.ConfigManager(getGlobalOptions())); } else { throw error; } } finally { if (client) { await client.end(); } } }); users.addCommand(listCmd); users.addCommand(createCmd); users.addCommand(deleteCmd); users.addCommand(updateCmd); } } exports.UsersCommand = UsersCommand; function createUsersCommand(getGlobalOptions) { const program = new commander_1.Command(); new UsersCommand(program, getGlobalOptions); return program.commands[0]; }