kegani 0.1.1

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! Database migrations for Kegani
//!
//! Provides migration management using sqlx-cli.

use std::path::Path;
use anyhow::Result;

/// Migration direction
#[derive(Debug, Clone, Copy)]
pub enum MigrationDirection {
    Up,
    Down,
}

/// Migration status
#[derive(Debug, Clone)]
pub struct MigrationResult {
    pub applied: usize,
    pub reverted: usize,
    pub errors: Vec<String>,
}

/// Database migrator
pub struct Migrator {
    migrations_path: String,
}

impl Migrator {
    /// Create a new migrator with migrations directory
    pub fn new(migrations_path: &str) -> Self {
        Self {
            migrations_path: migrations_path.to_string(),
        }
    }

    /// Check if migrations directory exists
    pub fn has_migrations(&self) -> bool {
        Path::new(&self.migrations_path).exists()
    }

    /// Get the migrations path
    pub fn migrations_path(&self) -> &str {
        &self.migrations_path
    }

    /// Run migrations using sqlx-cli
    pub async fn run(&self) -> Result<MigrationResult> {
        use std::process::Command;

        let output = Command::new("sqlx")
            .args(["migrate", "run"])
            .current_dir(&self.migrations_path)
            .output()?;

        let stderr = String::from_utf8_lossy(&output.stderr);
        let _stdout = String::from_utf8_lossy(&output.stdout);

        let mut errors = Vec::new();
        if !output.status.success() {
            errors.push(stderr.to_string());
        }

        Ok(MigrationResult {
            applied: if output.status.success() { 1 } else { 0 },
            reverted: 0,
            errors,
        })
    }

    /// Revert last migration
    pub async fn revert(&self) -> Result<MigrationResult> {
        use std::process::Command;

        let output = Command::new("sqlx")
            .args(["migrate", "revert"])
            .output()?;

        let stderr = String::from_utf8_lossy(&output.stderr);

        let mut errors = Vec::new();
        if !output.status.success() {
            errors.push(stderr.to_string());
        }

        Ok(MigrationResult {
            applied: 0,
            reverted: if output.status.success() { 1 } else { 0 },
            errors,
        })
    }

    /// Show migration status
    pub async fn status(&self) -> Result<Vec<MigrationInfo>> {
        use std::process::Command;

        let output = Command::new("sqlx")
            .args(["migrate", "status"])
            .output()?;

        // Parse output (simplified)
        let stdout = String::from_utf8_lossy(&output.stdout);
        let mut migrations = Vec::new();

        for line in stdout.lines() {
            if line.contains("migration") || line.starts_with('0') || line.starts_with('1') {
                migrations.push(MigrationInfo {
                    version: line.split_whitespace().next().unwrap_or("").to_string(),
                    name: line.to_string(),
                    applied: line.starts_with('1'),
                });
            }
        }

        Ok(migrations)
    }

    /// Reset all migrations
    pub async fn reset(&self) -> Result<MigrationResult> {
        use std::process::Command;

        let output = Command::new("sqlx")
            .args(["migrate", "reset"])
            .output()?;

        Ok(MigrationResult {
            applied: 0,
            reverted: if output.status.success() { 1 } else { 0 },
            errors: if output.status.success() {
                vec![]
            } else {
                vec![String::from_utf8_lossy(&output.stderr).to_string()]
            },
        })
    }
}

/// Migration information
#[derive(Debug, Clone)]
pub struct MigrationInfo {
    pub version: String,
    pub name: String,
    pub applied: bool,
}