systemprompt_cli/commands/cloud/sync/admin_user/
sync.rs1use 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}