db_migrate/commands/
status.rs

1use crate::{migration::MigrationManager, CommandOutput, utils::format_timestamp};
2use anyhow::Result;
3use clap::Args;
4use colored::*;
5use std::collections::HashSet;
6
7#[derive(Args)]
8pub struct StatusCommand {
9    /// Show detailed information about each migration
10    #[arg(short, long)]
11    verbose: bool,
12}
13
14impl StatusCommand {
15    pub async fn execute(&self, manager: &MigrationManager) -> Result<CommandOutput> {
16        let applied_migrations = manager.get_applied_migrations().await?;
17        let all_files = manager.get_migration_files().await?;
18        let pending_migrations = manager.get_pending_migrations().await?;
19
20        let applied_versions: HashSet<String> =
21            applied_migrations.iter().map(|m| m.version.clone()).collect();
22
23        let mut output = Vec::new();
24
25        // Header
26        output.push(format!("{} Migration Status", "📊".cyan()));
27        output.push("═".repeat(50));
28        output.push(String::new());
29
30        // Current state summary
31        let current_version = applied_migrations
32            .last()
33            .map(|m| m.version.as_str())
34            .unwrap_or("None");
35
36        output.push(format!(
37            "{}: {}",
38            "Current schema version".bold(),
39            if current_version == "None" {
40                "None (no migrations applied)".dimmed().to_string()
41            } else {
42                current_version.bright_cyan().to_string()
43            }
44        ));
45
46        output.push(format!(
47            "{}: {}",
48            "Applied migrations".bold(),
49            if applied_migrations.is_empty() {
50                "0".dimmed().to_string()
51            } else {
52                applied_migrations.len().to_string().bright_green().to_string()
53            }
54        ));
55
56        output.push(format!(
57            "{}: {}",
58            "Pending migrations".bold(),
59            if pending_migrations.is_empty() {
60                "0 ✅".bright_green().to_string()
61            } else {
62                format!("{} ⚠️", pending_migrations.len()).bright_yellow().to_string()
63            }
64        ));
65
66        output.push(format!(
67            "{}: {}",
68            "Total migration files".bold(),
69            all_files.len().to_string().bright_blue()
70        ));
71
72        if self.verbose {
73            output.push(String::new());
74            output.push("Applied Migrations:".bold().to_string());
75            output.push("─".repeat(30));
76
77            if applied_migrations.is_empty() {
78                output.push("  No migrations applied yet".dimmed().to_string());
79            } else {
80                for migration in &applied_migrations {
81                    output.push(format!(
82                        "  {} {} - {} {}",
83                        "✅".green(),
84                        migration.version.bright_cyan(),
85                        migration.description,
86                        format!("({})", format_timestamp(migration.applied_at)).dimmed()
87                    ));
88                }
89            }
90
91            output.push(String::new());
92            output.push("Pending Migrations:".bold().to_string());
93            output.push("─".repeat(30));
94
95            if pending_migrations.is_empty() {
96                output.push("  No pending migrations".dimmed().to_string());
97            } else {
98                for migration in &pending_migrations {
99                    output.push(format!(
100                        "  {} {} - {}",
101                        "⏳".yellow(),
102                        migration.version.bright_cyan(),
103                        migration.description
104                    ));
105                }
106            }
107
108            // Show files without valid migration format
109            let invalid_files: Vec<_> = all_files
110                .iter()
111                .filter(|f| !applied_versions.contains(&f.version) &&
112                    !pending_migrations.iter().any(|p| p.version == f.version))
113                .collect();
114
115            if !invalid_files.is_empty() {
116                output.push(String::new());
117                output.push("Invalid Migration Files:".bold().to_string());
118                output.push("─".repeat(30));
119
120                for file in invalid_files {
121                    output.push(format!(
122                        "  {} {} - {}",
123                        "❌".red(),
124                        file.file_path.file_name().unwrap_or_default().to_string_lossy(),
125                        "Invalid format or duplicate version".red()
126                    ));
127                }
128            }
129        }
130
131        // Status summary
132        output.push(String::new());
133        let status_message = if pending_migrations.is_empty() {
134            format!("{} Schema is up to date", "✅".green())
135        } else {
136            format!(
137                "{} {} migration(s) pending. Run 'db-migrate up' to apply them.",
138                "⚠️ ".yellow(),
139                pending_migrations.len()
140            )
141        };
142        output.push(status_message);
143
144        Ok(CommandOutput::success_with_data(
145            output.join("\n"),
146            serde_json::json!({
147                "current_version": current_version,
148                "applied_count": applied_migrations.len(),
149                "pending_count": pending_migrations.len(),
150                "total_files": all_files.len(),
151                "up_to_date": pending_migrations.is_empty(),
152                "applied_migrations": applied_migrations.iter().map(|m| {
153                    serde_json::json!({
154                        "version": m.version,
155                        "description": m.description,
156                        "applied_at": m.applied_at,
157                        "checksum": m.checksum
158                    })
159                }).collect::<Vec<_>>(),
160                "pending_migrations": pending_migrations.iter().map(|m| {
161                    serde_json::json!({
162                        "version": m.version,
163                        "description": m.description,
164                        "checksum": m.checksum
165                    })
166                }).collect::<Vec<_>>()
167            })
168        ))
169    }
170}