freezone/portal-server/cmd/main.rs
2025-06-30 17:01:40 +02:00

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");
}
}