db_migrate/commands/
status.rs1use 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 #[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 output.push(format!("{} Migration Status", "📊".cyan()));
27 output.push("═".repeat(50));
28 output.push(String::new());
29
30 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 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 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}