1use clap::{Parser, Subcommand};
2use dbctl_core::db::{Database, MariaDB, Postgres, Redis};
3use dbctl_core::docker::DockerEngine;
4use std::path::PathBuf;
5
6#[derive(Parser)]
7#[command(author, version, about, long_about = None)]
8pub struct Cli {
9 #[command(subcommand)]
10 command: Option<Commands>,
11}
12
13#[derive(Subcommand)]
14pub enum Commands {
15 Create {
17 #[arg(value_enum)]
18 db_type: String,
19 #[arg(short, long)]
20 name: Option<String>,
21 #[arg(short, long)]
22 user: Option<String>,
23 #[arg(short = 'P', long)]
24 password: Option<String>,
25 #[arg(short = 'p', long)]
26 port: Option<u16>,
27 #[arg(short, long)]
28 db_name: Option<String>,
29 #[arg(short, long)]
30 from_file: Option<PathBuf>,
31 },
32 List {},
34 Logs { container_id: String },
36 Remove { container_id: String },
38}
39
40async fn create_postgres(
41 name: Option<String>,
42 user: Option<String>,
43 password: Option<String>,
44 port: Option<u16>,
45 db_name: Option<String>,
46) -> anyhow::Result<()> {
47 let mut pg = Postgres::default();
48 if let Some(name) = name {
49 pg.name = name;
50 }
51 if let Some(user) = user {
52 pg.user = user;
53 }
54 if let Some(password) = password {
55 pg.password = password;
56 }
57 if let Some(port) = port {
58 pg.port = port;
59 }
60 if let Some(db_name) = db_name {
61 pg.db_name = db_name;
62 }
63
64 let engine = DockerEngine::new().await;
65 let container_id = engine.start_container(pg.clone()).await?;
66
67 println!("✅ Database '{}' started in Docker", pg.name);
68 println!("🔗 URL: {}", pg.connection_url());
69 println!("🆔 Container ID: {}", &container_id[..12]);
70
71 Ok(())
72}
73
74async fn create_redis(
75 name: Option<String>,
76 password: Option<String>,
77 port: Option<u16>,
78) -> anyhow::Result<()> {
79 let mut redis = Redis::default();
80 if let Some(name) = name {
81 redis.name = name;
82 }
83 if let Some(password) = password {
84 redis.password = Some(password);
85 }
86 if let Some(port) = port {
87 redis.port = port;
88 }
89
90 let engine = DockerEngine::new().await;
91 let container_id = engine.start_container(redis.clone()).await?;
92
93 println!("✅ Redis '{}' started in Docker", redis.name);
94 println!("🔗 URL: {}", redis.connection_url());
95 println!("🆔 Container ID: {}", &container_id[..12]);
96
97 Ok(())
98}
99
100async fn create_mariadb(
101 name: Option<String>,
102 user: Option<String>,
103 password: Option<String>,
104 port: Option<u16>,
105 db_name: Option<String>,
106) -> anyhow::Result<()> {
107 let mut mariadb = MariaDB::default();
108 if let Some(name) = name {
109 mariadb.name = name;
110 }
111 if let Some(user) = user {
112 mariadb.user = user;
113 }
114 if let Some(password) = password {
115 mariadb.password = password;
116 }
117 if let Some(port) = port {
118 mariadb.port = port;
119 }
120 if let Some(db_name) = db_name {
121 mariadb.db_name = db_name;
122 }
123
124 let engine = DockerEngine::new().await;
125 let container_id = engine.start_container(mariadb.clone()).await?;
126
127 println!("✅ MariaDB '{}' started in Docker", mariadb.name);
128 println!("🔗 URL: {}", mariadb.connection_url());
129 println!("🆔 Container ID: {}", &container_id[..12]);
130
131 Ok(())
132}
133
134async fn list_containers() -> anyhow::Result<()> {
135 let engine = DockerEngine::new().await;
136 let containers = engine.docker.list_containers::<String>(None).await?;
137
138 println!("🐳 Running Database Containers:");
139 println!(
140 "{:<15} {:<20} {:<15} {:<10}",
141 "CONTAINER ID", "NAME", "IMAGE", "STATUS"
142 );
143
144 for container in containers {
145 if let (Some(id), Some(names), Some(image), Some(status)) = (
146 container.id,
147 container.names,
148 container.image,
149 container.status,
150 ) {
151 if names.iter().any(|name| name.contains("dbctl")) {
152 let name = names.first().unwrap_or(&String::new()).replace("/", "");
153 println!(
154 "{:<15} {:<20} {:<15} {:<10}",
155 &id[..12],
156 name,
157 image,
158 status
159 );
160 }
161 }
162 }
163
164 Ok(())
165}
166
167async fn view_logs(container_id: &str) -> anyhow::Result<()> {
168 let engine = DockerEngine::new().await;
169 let logs = engine.container_logs(container_id).await?;
170
171 println!("📋 Logs for container {}:", container_id);
172 for line in logs {
173 println!("{}", line);
174 }
175
176 Ok(())
177}
178
179async fn remove_container(container_id: &str) -> anyhow::Result<()> {
180 let engine = DockerEngine::new().await;
181 engine.stop_container(container_id).await?;
182
183 println!("✅ Container {} stopped and removed", container_id);
184
185 Ok(())
186}
187
188pub async fn run_cli_async() -> anyhow::Result<()> {
189 let cli = Cli::parse();
190
191 match &cli.command {
192 Some(Commands::Create {
193 db_type,
194 name,
195 user,
196 password,
197 port,
198 db_name,
199 from_file,
200 }) => {
201 if let Some(_path) = from_file {
202 println!("Loading from file not yet implemented");
203 return Ok(());
204 }
205
206 match db_type.to_lowercase().as_str() {
207 "postgres" => {
208 create_postgres(
209 name.clone(),
210 user.clone(),
211 password.clone(),
212 *port,
213 db_name.clone(),
214 )
215 .await?
216 }
217 "redis" => create_redis(name.clone(), password.clone(), *port).await?,
218 "mariadb" => {
219 create_mariadb(
220 name.clone(),
221 user.clone(),
222 password.clone(),
223 *port,
224 db_name.clone(),
225 )
226 .await?
227 }
228 _ => {
229 println!("Unsupported database type: {}", db_type);
230 }
231 }
232 }
233 Some(Commands::List {}) => list_containers().await?,
234 Some(Commands::Logs { container_id }) => view_logs(&container_id).await?,
235 Some(Commands::Remove { container_id }) => remove_container(&container_id).await?,
236 None => {
237 println!("No command specified. Use --help for available commands.");
238 }
239 }
240
241 Ok(())
242}