259 lines
8.1 KiB
Rust
259 lines
8.1 KiB
Rust
//! Portal Server CLI
|
|
//!
|
|
//! Command-line interface for running the portal server with configurable options.
|
|
|
|
use clap::Parser;
|
|
use portal_server::{PortalServerBuilder, ServerConfig};
|
|
use tracing::{info, error};
|
|
use anyhow::Result;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "portal-server")]
|
|
#[command(about = "Portal Server for KYC verification and payment processing")]
|
|
#[command(version = "0.1.0")]
|
|
struct Cli {
|
|
/// Server host address
|
|
#[arg(long, default_value = "127.0.0.1")]
|
|
host: String,
|
|
|
|
/// Server port
|
|
#[arg(short, long, default_value = "3001")]
|
|
port: u16,
|
|
|
|
/// Stripe secret key
|
|
#[arg(long, env)]
|
|
stripe_secret_key: Option<String>,
|
|
|
|
/// Stripe publishable key
|
|
#[arg(long, env)]
|
|
stripe_publishable_key: Option<String>,
|
|
|
|
/// Stripe webhook secret
|
|
#[arg(long, env)]
|
|
stripe_webhook_secret: Option<String>,
|
|
|
|
/// Identify API key for KYC verification
|
|
#[arg(long, env)]
|
|
identify_api_key: Option<String>,
|
|
|
|
/// Identify webhook secret for signature verification
|
|
#[arg(long, env)]
|
|
identify_webhook_secret: Option<String>,
|
|
|
|
/// API keys for authentication (comma-separated)
|
|
#[arg(long, env)]
|
|
api_keys: Option<String>,
|
|
|
|
/// Identify API URL
|
|
#[arg(long, env, default_value = "https://api.identify.com")]
|
|
identify_api_url: String,
|
|
|
|
/// CORS allowed origins (comma-separated)
|
|
#[arg(long, env, default_value = "*")]
|
|
cors_origins: String,
|
|
|
|
/// Directory to serve static files from
|
|
#[arg(long)]
|
|
static_dir: Option<String>,
|
|
|
|
/// Load configuration from environment variables
|
|
#[arg(long)]
|
|
from_env: bool,
|
|
|
|
/// Path to .env file (defaults to .env in current directory)
|
|
#[arg(long)]
|
|
env_file: Option<String>,
|
|
|
|
/// Enable verbose logging
|
|
#[arg(short, long)]
|
|
verbose: bool,
|
|
}
|
|
|
|
fn load_env_file(cli: &Cli) -> Result<()> {
|
|
use std::path::Path;
|
|
|
|
if let Some(env_file_path) = &cli.env_file {
|
|
// Use the specified .env file path
|
|
info!("Loading .env file from: {}", env_file_path);
|
|
if Path::new(env_file_path).exists() {
|
|
dotenv::from_path(env_file_path)
|
|
.map_err(|e| anyhow::anyhow!("Failed to load .env file from {}: {}", env_file_path, e))?;
|
|
info!("Successfully loaded .env file from: {}", env_file_path);
|
|
} else {
|
|
return Err(anyhow::anyhow!("Specified .env file not found: {}", env_file_path));
|
|
}
|
|
} else {
|
|
// Try default locations in order of preference
|
|
let default_paths = [
|
|
".env", // Current directory
|
|
"portal-server/.env", // portal-server subdirectory
|
|
];
|
|
|
|
let mut loaded = false;
|
|
for path in &default_paths {
|
|
if Path::new(path).exists() {
|
|
info!("Loading .env file from: {}", path);
|
|
dotenv::from_path(path)
|
|
.map_err(|e| anyhow::anyhow!("Failed to load .env file from {}: {}", path, e))?;
|
|
info!("Successfully loaded .env file from: {}", path);
|
|
loaded = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !loaded {
|
|
info!("No .env file found in default locations. Using environment variables and CLI arguments only.");
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
|
|
// Initialize tracing
|
|
if cli.verbose {
|
|
tracing_subscriber::fmt()
|
|
.with_max_level(tracing::Level::DEBUG)
|
|
.init();
|
|
} else {
|
|
tracing_subscriber::fmt()
|
|
.with_max_level(tracing::Level::INFO)
|
|
.init();
|
|
}
|
|
|
|
info!("Starting Portal Server...");
|
|
|
|
// Load .env file if specified or use default locations
|
|
load_env_file(&cli)?;
|
|
|
|
// Build configuration
|
|
let config = if cli.from_env {
|
|
info!("Loading configuration from environment variables");
|
|
ServerConfig::from_env()?
|
|
} else {
|
|
info!("Using configuration from command line arguments");
|
|
build_config_from_cli(&cli)?
|
|
};
|
|
|
|
// Log configuration (without sensitive data)
|
|
info!("Server configuration:");
|
|
info!(" Host: {}", config.host);
|
|
info!(" Port: {}", config.port);
|
|
info!(" Identify API URL: {}", config.identify_api_url);
|
|
info!(" CORS Origins: {:?}", config.cors_origins);
|
|
info!(" Stripe configured: {}", !config.stripe_secret_key.is_empty());
|
|
info!(" Identify configured: {}", !config.identify_api_key.is_empty());
|
|
|
|
// Build server
|
|
let mut builder = PortalServerBuilder::new(config);
|
|
|
|
// Add static file serving if specified
|
|
if let Some(static_dir) = cli.static_dir {
|
|
builder = builder.with_static_dir(static_dir);
|
|
}
|
|
|
|
let server = builder.build().await?;
|
|
|
|
// Run server
|
|
if let Err(e) = server.run().await {
|
|
error!("Server error: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn build_config_from_cli(cli: &Cli) -> Result<ServerConfig> {
|
|
let stripe_secret_key = cli.stripe_secret_key
|
|
.clone()
|
|
.or_else(|| std::env::var("STRIPE_SECRET_KEY").ok())
|
|
.ok_or_else(|| anyhow::anyhow!("Stripe secret key is required. Use --stripe-secret-key or set STRIPE_SECRET_KEY environment variable"))?;
|
|
|
|
let stripe_publishable_key = cli.stripe_publishable_key
|
|
.clone()
|
|
.or_else(|| std::env::var("STRIPE_PUBLISHABLE_KEY").ok())
|
|
.ok_or_else(|| anyhow::anyhow!("Stripe publishable key is required. Use --stripe-publishable-key or set STRIPE_PUBLISHABLE_KEY environment variable"))?;
|
|
|
|
let identify_api_key = cli.identify_api_key
|
|
.clone()
|
|
.or_else(|| std::env::var("IDENTIFY_API_KEY").ok())
|
|
.ok_or_else(|| anyhow::anyhow!("Identify API key is required. Use --identify-api-key or set IDENTIFY_API_KEY environment variable"))?;
|
|
|
|
let cors_origins = cli.cors_origins
|
|
.split(',')
|
|
.map(|s| s.trim().to_string())
|
|
.collect();
|
|
|
|
let api_keys = cli.api_keys
|
|
.clone()
|
|
.or_else(|| std::env::var("API_KEYS").ok())
|
|
.map(|keys| keys.split(',').map(|s| s.trim().to_string()).collect())
|
|
.unwrap_or_default();
|
|
|
|
Ok(ServerConfig {
|
|
host: cli.host.clone(),
|
|
port: cli.port,
|
|
stripe_secret_key,
|
|
stripe_publishable_key,
|
|
stripe_webhook_secret: cli.stripe_webhook_secret.clone(),
|
|
identify_api_key,
|
|
identify_webhook_secret: cli.identify_webhook_secret.clone(),
|
|
identify_api_url: cli.identify_api_url.clone(),
|
|
cors_origins,
|
|
api_keys,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_cli_parsing() {
|
|
let cli = Cli::parse_from(&[
|
|
"portal-server",
|
|
"--host", "0.0.0.0",
|
|
"--port", "8080",
|
|
"--stripe-secret-key", "sk_test_123",
|
|
"--stripe-publishable-key", "pk_test_123",
|
|
"--identify-api-key", "identify_123",
|
|
"--verbose",
|
|
]);
|
|
|
|
assert_eq!(cli.host, "0.0.0.0");
|
|
assert_eq!(cli.port, 8080);
|
|
assert_eq!(cli.stripe_secret_key, Some("sk_test_123".to_string()));
|
|
assert_eq!(cli.stripe_publishable_key, Some("pk_test_123".to_string()));
|
|
assert_eq!(cli.identify_api_key, Some("identify_123".to_string()));
|
|
assert!(cli.verbose);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_from_cli() {
|
|
let cli = Cli {
|
|
host: "localhost".to_string(),
|
|
port: 3000,
|
|
stripe_secret_key: Some("sk_test_123".to_string()),
|
|
stripe_publishable_key: Some("pk_test_123".to_string()),
|
|
stripe_webhook_secret: None,
|
|
identify_api_key: Some("identify_123".to_string()),
|
|
identify_webhook_secret: None,
|
|
api_keys: None,
|
|
identify_api_url: "https://api.identify.com".to_string(),
|
|
cors_origins: "*".to_string(),
|
|
static_dir: None,
|
|
from_env: false,
|
|
env_file: None,
|
|
verbose: false,
|
|
};
|
|
|
|
let config = build_config_from_cli(&cli).unwrap();
|
|
assert_eq!(config.host, "localhost");
|
|
assert_eq!(config.port, 3000);
|
|
assert_eq!(config.stripe_secret_key, "sk_test_123");
|
|
assert_eq!(config.identify_api_key, "identify_123");
|
|
}
|
|
} |