Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ graph TB
subgraph External["External Systems"]
Metabase[Metabase API]
FileSystem[Config Files]
EnvVar[MBR_API_KEY]
EnvVar[MBR_API_KEY / MBR_URL]
end

CLI --> Services
Expand Down Expand Up @@ -193,13 +193,15 @@ flowchart LR
**Configuration File:** `~/.config/mbr-cli/config.toml`

```toml
[profiles.default]
url = "https://metabase.example.com"

[profiles.production]
url = "https://metabase.prod.example.com"
```

**Priority Order:**
1. CLI `--api-key` argument
2. `MBR_API_KEY` environment variable
3. `MBR_URL` environment variable (for URL)
4. `config.toml` file (for URL)

## Query Execution Flow

```mermaid
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Rust-based CLI/TUI tool for Metabase API interaction.
## Features

- **API Key Authentication**: Simple authentication via `MBR_API_KEY` environment variable
- **Multi-Profile Configuration**: TOML-based configuration with profile support
- **Simple Configuration**: TOML-based configuration with URL setting
- **CLI Interface**: Command-line tool for scripting and quick operations
- **Rich TUI Experience**: Interactive terminal UI with keyboard navigation (`mbr-tui`)
- **Hierarchical Error Handling**: Comprehensive error system with troubleshooting hints
Expand Down Expand Up @@ -89,7 +89,6 @@ mbr-cli query 123 --param date=2024-01-01 # Execute with parameters

### Global Options
- `--verbose, -v` - Enable verbose output
- `--profile, -p <PROFILE>` - Use specific profile (defaults to 'default')
- `--config-dir <DIR>` - Override default config directory
- `--api-key <KEY>` - Set API key (also via `MBR_API_KEY` environment variable)

Expand All @@ -108,6 +107,7 @@ mbr-cli query 123 --param date=2024-01-01 # Execute with parameters
| Variable | Description | Required |
|----------|-------------|----------|
| `MBR_API_KEY` | Metabase API key for authentication | Yes |
| `MBR_URL` | Metabase server URL (alternative to config file) | No |

## Development

Expand Down
57 changes: 15 additions & 42 deletions crates/mbr-cli/src/cli/command_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ impl ConfigHandler {
"Attempting config show command using ConfigService",
);

let profiles = config_service.list_profiles();

// Show the current configuration
println!("Current Configuration:");
println!("=====================");

println!("Default Profile: {}", config_service.get_default_profile());
// Show URL status
if let Some(url) = config_service.get_url() {
println!("URL: {}", url);
} else {
println!("URL: ❌ Not configured");
}

// Show API key status
if has_api_key() {
Expand All @@ -47,58 +50,28 @@ impl ConfigHandler {
println!("API Key: ❌ Not set");
}

println!("\nProfiles:");
if profiles.is_empty() {
println!(" No profiles configured");
} else {
for (name, profile) in profiles {
println!(" [{}]", name);
println!(" URL: {}", profile.url);
if let Some(email) = &profile.email {
println!(" Email: {}", email);
}
}
}

Ok(())
}
ConfigCommands::Set {
profile,
url,
email,
} => {
ConfigCommands::Set { url } => {
print_verbose(
verbose,
&format!(
"Attempting config set using ConfigService - profile: {}, url: {:?}, email: {:?}",
profile, url, email
),
&format!("Attempting config set using ConfigService - url: {:?}", url),
);

let mut updated_fields = Vec::new();

// Handle URL setting
if let Some(url_value) = url {
mbr_core::utils::validation::validate_url(&url_value)?;
config_service.set_profile_field(&profile, "url", &url_value)?;
updated_fields.push(format!("URL to: {}", url_value));
}

// Handle email setting
if let Some(email_value) = email {
config_service.set_profile_field(&profile, "email", &email_value)?;
updated_fields.push(format!("email to: {}", email_value));
}

if updated_fields.is_empty() {
config_service.set_url(url_value.clone());
config_service.save_config(None)?;
println!("✅ URL set to: {}", url_value);
println!("Configuration saved successfully.");
} else {
return Err(AppError::Cli(CliError::InvalidArguments(
"No configuration values provided. Use --url and/or --email, or set MBR_URL/MBR_USERNAME environment variables".to_string(),
"No URL provided. Use --url or set MBR_URL environment variable"
.to_string(),
)));
}

println!("✅ Set profile '{}' {}", profile, updated_fields.join(", "));
config_service.save_config(None)?;
println!("Configuration saved successfully.");
Ok(())
}
ConfigCommands::Validate => {
Expand Down
74 changes: 23 additions & 51 deletions crates/mbr-cli/src/cli/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ use crate::cli::main_types::Commands;
use mbr_core::api::client::MetabaseClient;
use mbr_core::core::services::config_service::ConfigService;
use mbr_core::error::{AppError, CliError};
use mbr_core::storage::config::{Config, Profile};
use mbr_core::storage::config::Config;
use mbr_core::storage::credentials::get_api_key;
use mbr_core::utils::logging::print_verbose;

pub struct Dispatcher {
config: Config,
profile_name: String,
verbose: bool,
api_key: Option<String>,
}
Expand All @@ -21,37 +20,9 @@ impl Dispatcher {
print_verbose(self.verbose, msg);
}

pub fn new(
mut config: Config,
profile_name: String,
verbose: bool,
api_key: Option<String>,
config_path: Option<std::path::PathBuf>,
) -> Self {
// Create default profile if not exists
if config.get_profile(&profile_name).is_none() {
print_verbose(
verbose,
&format!("Creating default profile: {}", profile_name),
);

let default_profile = Profile {
url: "http://localhost:3000".to_string(),
email: None,
};

config.set_profile(profile_name.clone(), default_profile);

if let Err(err) = config.save(config_path) {
print_verbose(verbose, &format!("Warning: Failed to save config: {}", err));
} else {
print_verbose(verbose, "Successfully saved config file");
}
}

pub fn new(config: Config, verbose: bool, api_key: Option<String>) -> Self {
Self {
config,
profile_name,
verbose,
api_key,
}
Expand All @@ -69,26 +40,24 @@ impl Dispatcher {
get_api_key()
}

// Helper method to get profile for the current credentials
fn get_current_profile(&self) -> Result<&Profile, AppError> {
self.config.get_profile(&self.profile_name).ok_or_else(|| {
AppError::Cli(CliError::AuthRequired {
message: format!("Profile '{}' not found", self.profile_name),
hint: "Use 'mbr-cli config set --profile <name> --url <url>' to create a profile"
.to_string(),
available_profiles: self.config.profiles.keys().cloned().collect(),
})
// Get URL from config or environment
fn get_url(&self) -> Result<String, AppError> {
self.config.get_url().ok_or_else(|| {
AppError::Cli(CliError::InvalidArguments(
"Metabase URL is not configured. Use 'mbr-cli config set --url <url>' or set MBR_URL environment variable".to_string(),
))
})
}

// Helper method to create MetabaseClient with API key
fn create_client(&self, profile: &Profile) -> Result<MetabaseClient, AppError> {
fn create_client(&self) -> Result<MetabaseClient, AppError> {
let url = self.get_url()?;
if let Some(api_key) = self.get_effective_api_key() {
self.log_verbose("Creating client with API key");
Ok(MetabaseClient::with_api_key(profile.url.clone(), api_key)?)
Ok(MetabaseClient::with_api_key(url, api_key)?)
} else {
self.log_verbose("Creating client without API key");
Ok(MetabaseClient::new(profile.url.clone())?)
Ok(MetabaseClient::new(url)?)
}
}

Expand All @@ -102,28 +71,31 @@ impl Dispatcher {
Commands::Config { command } => {
let handler = ConfigHandler::new();
let mut config_service = self.create_config_service();
let profile = self.get_current_profile()?;
let client = self.create_client(profile)?;
// For config commands, we may not have a URL yet
let client = match self.create_client() {
Ok(c) => c,
Err(_) => {
// Create a dummy client for config show/set (URL not needed)
MetabaseClient::new("http://localhost:3000".to_string())?
}
};
handler
.handle(command, &mut config_service, client, self.verbose)
.await
}
Commands::Query(args) => {
let handler = QueryHandler::new();
let profile = self.get_current_profile()?;
let client = self.create_client(profile)?;
let client = self.create_client()?;
handler.handle(args, client, self.verbose).await
}
Commands::Collection { command } => {
let handler = CollectionHandler::new();
let profile = self.get_current_profile()?;
let client = self.create_client(profile)?;
let client = self.create_client()?;
handler.handle(command, client, self.verbose).await
}
Commands::Database { command } => {
let handler = DatabaseHandler::new();
let profile = self.get_current_profile()?;
let client = self.create_client(profile)?;
let client = self.create_client()?;
handler.handle(command, client, self.verbose).await
}
}
Expand Down
15 changes: 2 additions & 13 deletions crates/mbr-cli/src/cli/main_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ pub struct Cli {
#[arg(short, long, global = true)]
pub verbose: bool,

/// Profile name to use (default: 'default')
#[arg(short, long, global = true)]
pub profile: Option<String>,

/// Custom configuration directory path
#[arg(long, global = true)]
pub config_dir: Option<String>,
Expand Down Expand Up @@ -63,21 +59,14 @@ pub enum Commands {
pub enum ConfigCommands {
/// Show the current configuration
Show,
/// Set configuration values for a profile
/// Set configuration values
#[command(after_help = "Examples:
mbr-cli config set --url http://localhost:3000
mbr-cli config set --profile prod --url https://metabase.example.com
mbr-cli config set --email user@example.com")]
mbr-cli config set --url https://metabase.example.com")]
Set {
/// Profile name to configure
#[arg(long, default_value = "default")]
profile: String,
/// Metabase server URL
#[arg(long, env = "MBR_URL")]
url: Option<String>,
/// Email address for this profile
#[arg(long, env = "MBR_USERNAME")]
email: Option<String>,
},
/// Validate API key and test connection to Metabase server
#[command(after_help = "Examples:
Expand Down
6 changes: 1 addition & 5 deletions crates/mbr-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
};

// Determine the profile to use (default to "default" if not specified)
let profile_name = cli.profile.clone().unwrap_or_else(|| "default".to_string());

if cli.verbose {
println!("Verbose mode is enabled");
println!("Using profile: {}", profile_name);

if let Some(config_dir) = &cli.config_dir {
println!("Using config directory: {}", config_dir);
Expand All @@ -42,7 +38,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}

// Create dispatcher
let dispatcher = Dispatcher::new(config, profile_name, cli.verbose, cli.api_key, config_path);
let dispatcher = Dispatcher::new(config, cli.verbose, cli.api_key);

// Execute the command
if let Err(e) = dispatcher.dispatch(cli.command).await {
Expand Down
Loading