use dsn::DSN; use miette::miette; use colored::Colorize; use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table, presets::UTF8_FULL}; use miette::Result; use crate::{ cli::ConnectionFormat, config::{PGDConfig, Project}, consts::{DATABASE, USERNAME}, controller::{docker::DockerController, reconciler::Reconciler}, state::{InstanceState, StateManager}, }; mod docker; mod utils; pub mod reconciler; pub struct Context { docker: DockerController, project: Option, instance: Option, state: StateManager, } impl Context { pub fn require_instance(&self) -> Result<&InstanceState> { self.instance.as_ref().ok_or(miette!("This command requires instance. Either initiliaze a project, or pass -I with instance name")) } pub fn require_project(&self) -> Result<&Project> { self.project.as_ref().ok_or(miette!( "This command requires project. Please, initiliaze a project." )) } pub async fn new(instance_override: Option) -> Result { let project = Project::load()?; let state = StateManager::new()?; let instance = match (project.as_ref(), instance_override) { (None, None) => None, // prioritizing provided instance name (_, Some(instance)) => state.get(&instance), (Some(project), None) => state.get(&project.name), }; Ok(Self { docker: DockerController::new().await?, project, instance, state, }) } } /// Main CLI command dispatcher pub struct Controller { ctx: Context, } impl Controller { pub fn new(ctx: Context) -> Self { Self { ctx } } pub async fn show_connection(&self, format: ConnectionFormat) -> Result<()> { let project = self.ctx.require_project()?; let reconciler = Reconciler { ctx: &self.ctx }; reconciler.reconcile(project).await?; match format { ConnectionFormat::DSN => { let dsn = DSN::builder() .driver("postgres") .username(USERNAME) .password(project.config.password.clone()) .host("127.0.0.1") .port(project.config.port) .database(DATABASE) .build(); println!("{}", dsn.to_string()); } ConnectionFormat::Human => { let mut table = create_ui_table("Instance"); table.add_row(vec![ Cell::new("Project").fg(Color::White), Cell::new(&project.name).add_attribute(Attribute::Bold), ]); table.add_row(vec![ Cell::new("PostgreSQL Version").fg(Color::White), Cell::new(project.config.version.to_string()).add_attribute(Attribute::Bold), ]); table.add_row(vec![ Cell::new("Host").fg(Color::White), Cell::new("127.0.0.1").add_attribute(Attribute::Bold), ]); table.add_row(vec![ Cell::new("Port").fg(Color::White), Cell::new(project.config.port.to_string()).add_attribute(Attribute::Bold), ]); table.add_row(vec![ Cell::new("Username").fg(Color::White), Cell::new(USERNAME).add_attribute(Attribute::Bold), ]); table.add_row(vec![ Cell::new("Password").fg(Color::White), Cell::new(project.config.password.clone()).fg(Color::DarkGrey), ]); println!("{}", table); } } Ok(()) } pub async fn init_project(&self) -> Result<()> { let reconciler = Reconciler { ctx: &self.ctx }; if let Some(project) = &self.ctx.project { return reconciler.reconcile(project).await; } println!("{}", "Initializing new pgd project...".cyan()); let mut versions = self.ctx.docker.available_versions().await?; versions.sort(); let latest_version = versions .last() .ok_or(miette!("expected to have at least one version"))?; let config = PGDConfig { version: *latest_version, password: utils::generate_password(), port: utils::find_available_port()?, }; let project = Project::new(config)?; println!( "\n{} {}\n", "Created pgd.toml in", project.path.display().to_string().bright_white().bold() ); let mut table = create_ui_table("Project Configuration"); table.add_row(vec![ Cell::new("Project").fg(Color::White), Cell::new(&project.name).add_attribute(Attribute::Bold), ]); table.add_row(vec![ Cell::new("PostgreSQL Version").fg(Color::White), Cell::new(project.config.version.to_string()).add_attribute(Attribute::Bold), ]); table.add_row(vec![ Cell::new("Port").fg(Color::White), Cell::new(project.config.port.to_string()).add_attribute(Attribute::Bold), ]); table.add_row(vec![ Cell::new("Password").fg(Color::White), Cell::new("*".repeat(project.config.password.len())).fg(Color::DarkGrey), ]); println!("{table}"); reconciler.reconcile(&project).await?; println!("\n{}", "✓ Project initialized successfully!".green().bold()); Ok(()) } } fn create_ui_table(header: &'static str) -> Table { let mut table = Table::new(); table .load_preset(UTF8_FULL) .set_content_arrangement(ContentArrangement::Dynamic) .set_style(comfy_table::TableComponent::MiddleIntersections, ' ') .set_header(vec![Cell::new(header).add_attribute(Attribute::Bold)]); use comfy_table::TableComponent::*; table.set_style(TopLeftCorner, '╭'); table.set_style(TopRightCorner, '╮'); table.set_style(BottomLeftCorner, '╰'); table.set_style(BottomRightCorner, '╯'); table }