oxidite-cli 2.3.2

CLI tool for the Oxidite web framework
Documentation
use super::sql_script::{load_database_url, split_sql_statements};
use std::fs;
use std::path::Path;

pub fn create_seeder(name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let seeds_dir = Path::new("seeds");

    // Create seeds directory if it doesn't exist
    if !seeds_dir.exists() {
        fs::create_dir(seeds_dir)?;
    }

    let timestamp = chrono::Utc::now().format("%Y%m%d%H%M%S");
    let filename = format!("{}_{}.sql", timestamp, name.to_lowercase());
    let filepath = seeds_dir.join(&filename);

    let template = format!(
        r#"-- Seed: {}
-- Created at: {}
-- Generated by `oxidite generate seeder`.
-- Add deterministic seed data here so it can be rerun safely in development.
-- Replace the example insert below with the records your app actually needs.

INSERT INTO users (username, email) VALUES ('admin', 'admin@example.com');
"#,
        name,
        chrono::Utc::now().to_rfc3339()
    );

    fs::write(&filepath, template)?;

    println!("✅ Created seeder: {}", filepath.display());
    println!("\nEdit the file to add your seed data.");

    Ok(())
}

pub async fn run_seeders() -> Result<(), Box<dyn std::error::Error>> {
    use oxidite_db::{Database, DbPool};

    let db_url = load_database_url()?;

    let db = DbPool::connect(&db_url).await?;

    let seeds_dir = Path::new("seeds");

    if !seeds_dir.exists() {
        println!("No seeds directory found.");
        return Ok(());
    }

    // Get all seed files
    let mut seed_files: Vec<_> = fs::read_dir(seeds_dir)?
        .filter_map(|entry| entry.ok())
        .filter(|entry| {
            entry
                .path()
                .extension()
                .and_then(|s| s.to_str())
                .map(|s| s == "sql")
                .unwrap_or(false)
        })
        .collect();

    // Sort by filename (timestamp)
    seed_files.sort_by_key(|entry| entry.file_name());

    if seed_files.is_empty() {
        println!("No seed files found.");
        return Ok(());
    }

    println!("Running {} seeders...\n", seed_files.len());

    for entry in seed_files {
        let path = entry.path();
        let filename = path.file_name().unwrap().to_string_lossy();

        println!("🌱 Seeding: {}", filename);

        let sql = fs::read_to_string(&path)?;

        if !sql.trim().is_empty() {
            for statement in split_sql_statements(&sql) {
                db.execute(&statement).await?;
            }
            println!("   ✅ Done");
        } else {
            println!("   ⚠️  Empty seeder");
        }
    }

    println!("\n✅ All seeders run successfully!");

    Ok(())
}

#[cfg(test)]
mod tests {
    use crate::commands::sql_script::split_sql_statements;

    #[test]
    fn sql_split_ignores_comments() {
        let sql = r#"
            -- start
            INSERT INTO users (id) VALUES (1);
            -- another
            INSERT INTO users (id) VALUES (2);
        "#;

        let statements = split_sql_statements(sql);
        assert_eq!(statements.len(), 2);
        assert!(statements[0].contains("VALUES (1)"));
        assert!(statements[1].contains("VALUES (2)"));
    }
}