Skip to main content

systemprompt_cli/commands/cloud/sync/admin_user/
sync.rs

1use std::sync::Arc;
2use systemprompt_database::Database;
3use systemprompt_logging::CliService;
4use systemprompt_users::{PromoteResult, UserAdminService, UserService};
5
6use super::discovery::{discover_profiles, print_discovery_summary};
7use super::types::{CloudUser, SyncResult};
8
9async fn promote_existing_user(
10    admin_service: &UserAdminService,
11    email: &str,
12    profile_name: &str,
13) -> SyncResult {
14    match admin_service.promote_to_admin(email).await {
15        Ok(PromoteResult::Promoted(_, _)) => SyncResult::Promoted {
16            email: email.to_string(),
17            profile: profile_name.to_string(),
18        },
19        Ok(PromoteResult::AlreadyAdmin(_)) => SyncResult::AlreadyAdmin {
20            email: email.to_string(),
21            profile: profile_name.to_string(),
22        },
23        Ok(PromoteResult::UserNotFound) => SyncResult::Failed {
24            profile: profile_name.to_string(),
25            error: "User not found after existence check".to_string(),
26        },
27        Err(e) => SyncResult::Failed {
28            profile: profile_name.to_string(),
29            error: format!("Promotion failed: {}", e),
30        },
31    }
32}
33
34async fn create_and_promote_user(
35    user_service: &UserService,
36    admin_service: &UserAdminService,
37    user: &CloudUser,
38    profile_name: &str,
39) -> SyncResult {
40    let username = user.username();
41    let display_name = user.name.as_deref();
42
43    match user_service
44        .create(&username, &user.email, display_name, display_name)
45        .await
46    {
47        Ok(_) => match admin_service.promote_to_admin(&user.email).await {
48            Ok(_) => SyncResult::Created {
49                email: user.email.clone(),
50                profile: profile_name.to_string(),
51            },
52            Err(e) => SyncResult::Failed {
53                profile: profile_name.to_string(),
54                error: format!("Created user but promotion failed: {}", e),
55            },
56        },
57        Err(e) => SyncResult::Failed {
58            profile: profile_name.to_string(),
59            error: format!("User creation failed: {}", e),
60        },
61    }
62}
63
64pub async fn sync_admin_to_database(
65    user: &CloudUser,
66    database_url: &str,
67    profile_name: &str,
68) -> SyncResult {
69    let db = match tokio::time::timeout(
70        std::time::Duration::from_secs(5),
71        Database::new_postgres(database_url),
72    )
73    .await
74    {
75        Ok(Ok(db)) => Arc::new(db),
76        Ok(Err(e)) => {
77            return SyncResult::ConnectionFailed {
78                profile: profile_name.to_string(),
79                error: e.to_string(),
80            };
81        },
82        Err(e) => {
83            tracing::warn!(profile = %profile_name, error = %e, "Database connection timed out");
84            return SyncResult::ConnectionFailed {
85                profile: profile_name.to_string(),
86                error: "Connection timed out (5s)".to_string(),
87            };
88        },
89    };
90
91    let user_service = match UserService::new(&db) {
92        Ok(s) => s,
93        Err(e) => {
94            return SyncResult::Failed {
95                profile: profile_name.to_string(),
96                error: format!("Failed to create user service: {}", e),
97            };
98        },
99    };
100
101    let admin_service = UserAdminService::new(user_service.clone());
102
103    match user_service.find_by_email(&user.email).await {
104        Ok(Some(_)) => promote_existing_user(&admin_service, &user.email, profile_name).await,
105        Ok(None) => {
106            create_and_promote_user(&user_service, &admin_service, user, profile_name).await
107        },
108        Err(e) => SyncResult::Failed {
109            profile: profile_name.to_string(),
110            error: format!("Failed to check existing user: {}", e),
111        },
112    }
113}
114
115pub async fn sync_admin_to_all_profiles(user: &CloudUser, verbose: bool) -> Vec<SyncResult> {
116    let discovery = match discover_profiles() {
117        Ok(d) => d,
118        Err(e) => {
119            CliService::warning(&format!("Failed to discover profiles: {}", e));
120            return Vec::new();
121        },
122    };
123
124    print_discovery_summary(&discovery, verbose);
125
126    if discovery.profiles.is_empty() {
127        if discovery.skipped.is_empty() {
128            CliService::info("No profiles found to sync admin user.");
129        } else {
130            CliService::warning(
131                "No profiles available for sync (all skipped due to configuration issues).",
132            );
133        }
134        return Vec::new();
135    }
136
137    let mut results = Vec::new();
138
139    for profile in discovery.profiles {
140        let result = sync_admin_to_database(user, &profile.database_url, &profile.name).await;
141        results.push(result);
142    }
143
144    results
145}
146
147pub fn print_sync_results(results: &[SyncResult]) {
148    for result in results {
149        match result {
150            SyncResult::Created { email, profile } => {
151                CliService::success(&format!(
152                    "Created admin user '{}' in profile '{}'",
153                    email, profile
154                ));
155            },
156            SyncResult::Promoted { email, profile } => {
157                CliService::success(&format!(
158                    "Promoted existing user '{}' to admin in profile '{}'",
159                    email, profile
160                ));
161            },
162            SyncResult::AlreadyAdmin { email, profile } => {
163                CliService::info(&format!(
164                    "User '{}' is already admin in profile '{}'",
165                    email, profile
166                ));
167            },
168            SyncResult::ConnectionFailed { profile, error } => {
169                CliService::warning(&format!(
170                    "Could not connect to profile '{}': {}",
171                    profile, error
172                ));
173            },
174            SyncResult::Failed { profile, error } => {
175                CliService::warning(&format!(
176                    "Failed to sync admin to profile '{}': {}",
177                    profile, error
178                ));
179            },
180        }
181    }
182}