use std::sync::Mutex;
use indicatif::ProgressBar;
use systemprompt_cloud::tenants::{ProvisioningProgress, ProvisioningProgressEvent};
use systemprompt_logging::CliService;
use url::Url;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DatabaseConnectionInfo {
pub host: String,
pub port: u16,
pub database: String,
pub username: String,
pub password: String,
}
impl DatabaseConnectionInfo {
#[must_use]
pub fn parse(url: &str) -> Option<Self> {
let parsed = Url::parse(url).ok()?;
Some(Self {
host: parsed.host_str().unwrap_or("unknown").to_owned(),
port: parsed.port().unwrap_or(5432),
database: parsed.path().trim_start_matches('/').to_owned(),
username: parsed.username().to_owned(),
password: parsed.password().unwrap_or("********").to_owned(),
})
}
#[must_use]
pub fn psql_command(&self) -> String {
format!(
"PGPASSWORD='{}' psql -h {} -p {} -U {} -d {}",
self.password, self.host, self.port, self.username, self.database
)
}
}
pub(super) struct CliProvisioningProgress {
spinner: Mutex<Option<ProgressBar>>,
}
impl CliProvisioningProgress {
pub(super) const fn new() -> Self {
Self {
spinner: Mutex::new(None),
}
}
fn start_spinner(&self, message: &str) {
if let Ok(mut slot) = self.spinner.lock() {
*slot = Some(CliService::spinner(message));
}
}
fn clear_spinner(&self) {
if let Ok(mut slot) = self.spinner.lock()
&& let Some(spinner) = slot.take()
{
spinner.finish_and_clear();
}
}
}
impl ProvisioningProgress for CliProvisioningProgress {
fn event(&self, event: &ProvisioningProgressEvent<'_>) {
if !matches!(event, ProvisioningProgressEvent::ProvisioningUpdate { .. }) {
self.clear_spinner();
}
match event {
ProvisioningProgressEvent::CheckoutSessionStarted => {
self.start_spinner("Creating checkout session...");
},
ProvisioningProgressEvent::CheckoutComplete { tenant_id } => {
CliService::success(&format!(
"Checkout complete! Tenant ID: {}",
tenant_id.as_str()
));
},
ProvisioningProgressEvent::ProvisioningStarted => {
self.start_spinner("Waiting for infrastructure provisioning...");
},
ProvisioningProgressEvent::ProvisioningUpdate { message } => {
CliService::info(message);
},
ProvisioningProgressEvent::Provisioned => {
CliService::success("Tenant provisioned successfully");
},
ProvisioningProgressEvent::CredentialsFetchStarted => {
self.start_spinner("Fetching database credentials...");
},
ProvisioningProgressEvent::CredentialsFetched => {
CliService::success("Database credentials retrieved");
},
ProvisioningProgressEvent::ExternalAccessStarted => {
self.start_spinner("Enabling external database access...");
},
ProvisioningProgressEvent::ExternalAccessEnabled { database_url } => {
CliService::success("External database access enabled");
print_database_connection_info(database_url);
},
ProvisioningProgressEvent::ExternalAccessFailed { error } => {
CliService::warning(&format!("Failed to enable external access: {}", error));
CliService::info("You can enable it later with 'systemprompt cloud tenant edit'");
},
ProvisioningProgressEvent::TenantSyncStarted => {
self.start_spinner("Syncing new tenant...");
},
ProvisioningProgressEvent::CheckoutSessionCreated
| ProvisioningProgressEvent::TenantSynced => {},
}
}
}
fn print_database_connection_info(url: &str) {
let Some(info) = DatabaseConnectionInfo::parse(url) else {
return;
};
CliService::section("Database Connection");
CliService::key_value("Host", &info.host);
CliService::key_value("Port", &info.port.to_string());
CliService::key_value("Database", &info.database);
CliService::key_value("User", &info.username);
CliService::key_value("Password", &info.password);
CliService::key_value("SSL", "required");
CliService::info("");
CliService::key_value("Connection URL", url);
CliService::info("");
CliService::info(&format!("Connect with psql:\n {}", info.psql_command()));
}