niledatabase
Version:
Command line interface for Nile databases
383 lines (377 loc) • 18.9 kB
JavaScript
;
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];
}