systemprompt_cli/commands/cloud/profile/
create_setup.rs1use anyhow::Result;
2use dialoguer::Confirm;
3use dialoguer::theme::ColorfulTheme;
4use std::path::Path;
5use systemprompt_cloud::{DockerCli, ProjectContext};
6use systemprompt_logging::CliService;
7
8use super::templates::{run_migrations_cmd, validate_connection};
9use crate::cloud::tenant::wait_for_postgres_healthy;
10
11pub async fn handle_local_tenant_setup(
12 cloud_user: &crate::cloud::sync::admin_user::CloudUser,
13 db_url: &str,
14 tenant_name: &str,
15 profile_path: &Path,
16) -> Result<()> {
17 let spinner = CliService::spinner("Validating PostgreSQL connection...");
18 let mut connection_valid = validate_connection(db_url).await;
19 spinner.finish_and_clear();
20
21 if !connection_valid {
22 let ctx = ProjectContext::discover();
23 let compose_path = ctx.docker_dir().join(format!("{}.yaml", tenant_name));
24
25 if compose_path.exists() {
26 let start_docker = Confirm::with_theme(&ColorfulTheme::default())
27 .with_prompt("PostgreSQL not running. Start Docker container?")
28 .default(true)
29 .interact()?;
30
31 if start_docker {
32 connection_valid = start_postgres_container(&compose_path).await?;
33 }
34 } else {
35 CliService::warning("Could not connect to PostgreSQL.");
36 CliService::info("Ensure PostgreSQL is running before starting services.");
37 }
38 }
39
40 if connection_valid {
41 CliService::success("PostgreSQL connection verified");
42
43 let run_migrations = Confirm::with_theme(&ColorfulTheme::default())
44 .with_prompt("Run database migrations?")
45 .default(true)
46 .interact()?;
47
48 let migrations_succeeded = if run_migrations {
49 match run_migrations_cmd(profile_path) {
50 Ok(()) => true,
51 Err(e) => {
52 CliService::warning(&format!("Migration failed: {}", e));
53 false
54 },
55 }
56 } else {
57 false
58 };
59
60 if migrations_succeeded {
61 let result = crate::cloud::sync::admin_user::sync_admin_to_database(
62 cloud_user,
63 db_url,
64 tenant_name,
65 )
66 .await;
67
68 match &result {
69 crate::cloud::sync::admin_user::SyncResult::Created { email, .. } => {
70 CliService::success(&format!("Created admin user: {}", email));
71 },
72 crate::cloud::sync::admin_user::SyncResult::Promoted { email, .. } => {
73 CliService::success(&format!("Promoted user to admin: {}", email));
74 },
75 crate::cloud::sync::admin_user::SyncResult::AlreadyAdmin { email, .. } => {
76 CliService::info(&format!("User '{}' is already admin", email));
77 },
78 crate::cloud::sync::admin_user::SyncResult::ConnectionFailed { error, .. } => {
79 CliService::warning(&format!("Could not sync admin user: {}", error));
80 },
81 crate::cloud::sync::admin_user::SyncResult::Failed { error, .. } => {
82 CliService::warning(&format!("Admin user sync failed: {}", error));
83 },
84 }
85 }
86 }
87
88 Ok(())
89}
90
91pub fn get_cloud_user() -> Result<crate::cloud::sync::admin_user::CloudUser> {
92 crate::cloud::sync::admin_user::CloudUser::from_credentials()?.ok_or_else(|| {
93 anyhow::anyhow!("Cloud credentials required. Run 'systemprompt cloud login' first.")
94 })
95}
96
97async fn start_postgres_container(compose_path: &Path) -> Result<bool> {
98 CliService::info("Starting PostgreSQL container...");
99
100 let compose_path_str = compose_path
101 .to_str()
102 .ok_or_else(|| anyhow::anyhow!("Invalid compose path"))?;
103
104 let status = DockerCli::new()
105 .status(&["compose", "-f", compose_path_str, "up", "-d"])
106 .map_err(|_e| anyhow::anyhow!("Failed to execute docker compose. Is Docker running?"))?;
107
108 if !status.success() {
109 CliService::warning("Failed to start PostgreSQL container. Is Docker running?");
110 return Ok(false);
111 }
112
113 let spinner = CliService::spinner("Waiting for PostgreSQL to be ready...");
114 match wait_for_postgres_healthy(compose_path, 60).await {
115 Ok(()) => {
116 spinner.finish_and_clear();
117 Ok(true)
118 },
119 Err(e) => {
120 spinner.finish_and_clear();
121 CliService::warning(&format!("PostgreSQL failed to become healthy: {}", e));
122 Ok(false)
123 },
124 }
125}