Add multi-environment support for database configuration
Introduces separate databases and config files for dev, test, and prod environments. The application now defaults to production, with --env flag to specify alternative environments. Changes: - Update config.rs to support env-based loading (config.toml -> config.<env>.toml -> config.example.toml) - Add Env enum (Prod, Dev, Test) with database name mapping - Add --env flag to CLI commands (defaults to prod) - Add 'db setup' command to create database and schema - Split migrations into env-specific database creation and shared schema - Update .gitignore to track config.example.toml but ignore config.toml and config.<env>.toml files - Update config.example.toml as a template with placeholder values - Delete 001_initial_schema.sql, replaced by 002_schema.sql + env-specific files Config loading order: 1. config.toml (local override) 2. config.<env>.toml (environment-specific) 3. config.example.toml (fallback) Database names: - prod: rusty_petroleum - dev: rusty_petroleum_dev - test: rusty_petroleum_test Usage: cargo run -- db setup --env dev # Setup dev database cargo run -- import data.csv --env dev # Import to dev cargo run -- db setup # Setup prod (default) cargo run -- import data.csv # Import to prod (default)
This commit is contained in:
+85
-22
@@ -5,7 +5,7 @@ mod invoice_generator;
|
||||
|
||||
use askama::Template;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use config::Config;
|
||||
use config::{Config, Env};
|
||||
use csv::ReaderBuilder;
|
||||
use db::{create_pool, Repository};
|
||||
use invoice_generator::{group_by_customer, read_csv_file, Customer};
|
||||
@@ -233,10 +233,41 @@ struct CustomerTemplate {
|
||||
generated_date: String,
|
||||
}
|
||||
|
||||
fn parse_env_flag(args: &[String]) -> (Env, usize) {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if arg == "--env" && i + 1 < args.len() {
|
||||
match args[i + 1].parse() {
|
||||
Ok(env) => return (env, i),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(Env::default(), 0)
|
||||
}
|
||||
|
||||
fn remove_env_flags(args: &[String]) -> Vec<String> {
|
||||
let (_, env_idx) = parse_env_flag(args);
|
||||
let mut result = Vec::with_capacity(args.len());
|
||||
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i == env_idx || (i == env_idx + 1 && args.get(env_idx) == Some(&"--env".to_string())) {
|
||||
continue;
|
||||
}
|
||||
result.push(arg.clone());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
let (env, _) = parse_env_flag(&args);
|
||||
|
||||
if args.len() < 2 {
|
||||
print_usage(&args[0]);
|
||||
std::process::exit(1);
|
||||
@@ -244,32 +275,35 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
match args[1].as_str() {
|
||||
"import" => {
|
||||
if args.len() != 3 {
|
||||
eprintln!("Användning: {} import <csv-fil>", args[0]);
|
||||
let clean_args = remove_env_flags(&args);
|
||||
if clean_args.len() != 3 {
|
||||
eprintln!("Usage: {} import <csv-file> [--env <name>]", clean_args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
let csv_path = PathBuf::from(&args[2]);
|
||||
let csv_path = PathBuf::from(&clean_args[2]);
|
||||
if !csv_path.exists() {
|
||||
eprintln!("Fel: Filen hittades inte: {:?}", csv_path);
|
||||
eprintln!("Error: File not found: {:?}", csv_path);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let config = Config::load()?;
|
||||
println!("Environment: {}", env.as_str());
|
||||
let config = Config::load(env)?;
|
||||
let pool = create_pool(&config.database.connection_url()).await?;
|
||||
let repo = Repository::new(pool);
|
||||
|
||||
commands::run_import(&csv_path, &repo).await?;
|
||||
}
|
||||
"generate" => {
|
||||
if args.len() != 3 {
|
||||
eprintln!("Användning: {} generate <csv-fil> <utdatakatalog>", args[0]);
|
||||
let clean_args = remove_env_flags(&args);
|
||||
if clean_args.len() != 4 {
|
||||
eprintln!("Usage: {} generate <csv-file> <output-dir> [--env <name>]", clean_args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
let input_path = Path::new(&args[2]);
|
||||
let base_output_dir = Path::new(&args[3]);
|
||||
let input_path = Path::new(&clean_args[2]);
|
||||
let base_output_dir = Path::new(&clean_args[3]);
|
||||
|
||||
if !input_path.exists() {
|
||||
eprintln!("Fel: Filen hittades inte: {:?}", input_path);
|
||||
eprintln!("Error: File not found: {:?}", input_path);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -279,7 +313,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
|
||||
println!("Konverterar {} till rensat format...", filename);
|
||||
println!("Converting {} to cleaned format...", filename);
|
||||
|
||||
let temp_cleaned_path =
|
||||
base_output_dir.join(format!("{}.temp.csv", filename.trim_end_matches(".txt")));
|
||||
@@ -295,7 +329,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
)?;
|
||||
|
||||
println!(
|
||||
"Konverterade {} transaktioner",
|
||||
"Converted {} transactions",
|
||||
fs::read_to_string(output_dir.join(format!("{}.csv", batch_number)))?
|
||||
.lines()
|
||||
.count()
|
||||
@@ -303,7 +337,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
let batch = read_csv_file(&output_dir.join(format!("{}.csv", batch_number)))?;
|
||||
println!("Laddade {} transaktioner", batch.transactions.len());
|
||||
println!("Loaded {} transactions", batch.transactions.len());
|
||||
|
||||
let first_date = batch.transactions.first().map(|t| t.date).unwrap();
|
||||
let last_date = batch.transactions.last().map(|t| t.date).unwrap();
|
||||
@@ -343,19 +377,45 @@ async fn main() -> anyhow::Result<()> {
|
||||
.unwrap();
|
||||
let filename = format!("customer_{}.html", customer_num);
|
||||
fs::write(output_dir.join(&filename), customer_html)?;
|
||||
println!("Genererade {}", filename);
|
||||
println!("Generated {}", filename);
|
||||
}
|
||||
|
||||
println!(
|
||||
"\nGenererade {} kundfakturor i {:?}",
|
||||
"\nGenerated {} customer invoices in {:?}",
|
||||
customer_count, output_dir
|
||||
);
|
||||
}
|
||||
"db" => {
|
||||
let clean_args = remove_env_flags(&args);
|
||||
if clean_args.len() < 3 {
|
||||
eprintln!("Usage: {} db <subcommand> [--env <name>]", clean_args[0]);
|
||||
eprintln!("Subcommands:");
|
||||
eprintln!(" setup Create database and schema");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
println!("Environment: {}", env.as_str());
|
||||
let config = Config::load(env)?;
|
||||
let pool = create_pool(&config.database.connection_url()).await?;
|
||||
let repo = Repository::new(pool);
|
||||
|
||||
match clean_args[2].as_str() {
|
||||
"setup" => {
|
||||
commands::run_db_setup(&repo, &config).await?;
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Unknown db subcommand: {}", clean_args[2]);
|
||||
eprintln!("Subcommands:");
|
||||
eprintln!(" setup Create database and schema");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
"help" | "--help" | "-h" => {
|
||||
print_usage(&args[0]);
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Okänt kommando: {}", args[1]);
|
||||
eprintln!("Unknown command: {}", args[1]);
|
||||
print_usage(&args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -365,10 +425,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
fn print_usage(program: &str) {
|
||||
eprintln!("Användning: {} <kommando> [argument]", program);
|
||||
eprintln!("Usage: {} <command> [arguments]", program);
|
||||
eprintln!();
|
||||
eprintln!("Kommandon:");
|
||||
eprintln!(" import <csv-fil> Importera CSV-data till databasen");
|
||||
eprintln!(" generate <csv-fil> <dir> Generera HTML-fakturor från CSV");
|
||||
eprintln!(" help Visa denna hjälptext");
|
||||
eprintln!("Commands:");
|
||||
eprintln!(" import <csv-file> [--env <name>] Import CSV data to database (default: prod)");
|
||||
eprintln!(" generate <csv> <dir> Generate HTML invoices from CSV");
|
||||
eprintln!(" db setup [--env <name>] Create database and schema (default: prod)");
|
||||
eprintln!(" help Show this help message");
|
||||
eprintln!();
|
||||
eprintln!("Environments: prod (default), dev, test");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user