use dioxus::prelude::*; use serde::{Deserialize, Serialize}; use compliance_core::models::TrackedRepository; #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct RepositoryListResponse { pub data: Vec, pub total: Option, pub page: Option, } #[server] pub async fn fetch_repositories(page: u64) -> Result { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!( "{}/api/v1/repositories?page={page}&limit=20", state.agent_api_url ); let resp = reqwest::get(&url) .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: RepositoryListResponse = resp .json() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(body) } #[server] #[allow(clippy::too_many_arguments)] pub async fn add_repository( name: String, git_url: String, default_branch: String, auth_token: Option, auth_username: Option, tracker_type: Option, tracker_owner: Option, tracker_repo: Option, tracker_token: Option, ) -> Result<(), ServerFnError> { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/repositories", state.agent_api_url); let mut body = serde_json::json!({ "name": name, "git_url": git_url, "default_branch": default_branch, }); if let Some(token) = auth_token.filter(|t| !t.is_empty()) { body["auth_token"] = serde_json::Value::String(token); } if let Some(username) = auth_username.filter(|u| !u.is_empty()) { body["auth_username"] = serde_json::Value::String(username); } if let Some(tt) = tracker_type.filter(|t| !t.is_empty()) { body["tracker_type"] = serde_json::Value::String(tt); } if let Some(to) = tracker_owner.filter(|t| !t.is_empty()) { body["tracker_owner"] = serde_json::Value::String(to); } if let Some(tr) = tracker_repo.filter(|t| !t.is_empty()) { body["tracker_repo"] = serde_json::Value::String(tr); } if let Some(tk) = tracker_token.filter(|t| !t.is_empty()) { body["tracker_token"] = serde_json::Value::String(tk); } let client = reqwest::Client::new(); let resp = client .post(&url) .json(&body) .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; if !resp.status().is_success() { let body = resp.text().await.unwrap_or_default(); return Err(ServerFnError::new(format!( "Failed to add repository: {body}" ))); } Ok(()) } #[server] pub async fn update_repository( repo_id: String, name: Option, default_branch: Option, auth_token: Option, auth_username: Option, tracker_type: Option, tracker_owner: Option, tracker_repo: Option, tracker_token: Option, scan_schedule: Option, ) -> Result<(), ServerFnError> { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/repositories/{repo_id}", state.agent_api_url); let mut body = serde_json::Map::new(); if let Some(v) = name.filter(|s| !s.is_empty()) { body.insert("name".into(), serde_json::Value::String(v)); } if let Some(v) = default_branch.filter(|s| !s.is_empty()) { body.insert("default_branch".into(), serde_json::Value::String(v)); } if let Some(v) = auth_token { body.insert("auth_token".into(), serde_json::Value::String(v)); } if let Some(v) = auth_username { body.insert("auth_username".into(), serde_json::Value::String(v)); } if let Some(v) = tracker_type { body.insert("tracker_type".into(), serde_json::Value::String(v)); } if let Some(v) = tracker_owner { body.insert("tracker_owner".into(), serde_json::Value::String(v)); } if let Some(v) = tracker_repo { body.insert("tracker_repo".into(), serde_json::Value::String(v)); } if let Some(v) = tracker_token { body.insert("tracker_token".into(), serde_json::Value::String(v)); } if let Some(v) = scan_schedule { body.insert("scan_schedule".into(), serde_json::Value::String(v)); } let client = reqwest::Client::new(); let resp = client .patch(&url) .json(&body) .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; if !resp.status().is_success() { let text = resp.text().await.unwrap_or_default(); return Err(ServerFnError::new(format!( "Failed to update repository: {text}" ))); } Ok(()) } #[server] pub async fn fetch_ssh_public_key() -> Result { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/settings/ssh-public-key", state.agent_api_url); let resp = reqwest::get(&url) .await .map_err(|e| ServerFnError::new(e.to_string()))?; if !resp.status().is_success() { return Err(ServerFnError::new("SSH key not available".to_string())); } let body: serde_json::Value = resp .json() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(body .get("public_key") .and_then(|v| v.as_str()) .unwrap_or("") .to_string()) } #[server] pub async fn delete_repository(repo_id: String) -> Result<(), ServerFnError> { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/repositories/{repo_id}", state.agent_api_url); let client = reqwest::Client::new(); let resp = client .delete(&url) .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; if !resp.status().is_success() { let body = resp.text().await.unwrap_or_default(); return Err(ServerFnError::new(format!( "Failed to delete repository: {body}" ))); } Ok(()) } #[server] pub async fn trigger_repo_scan(repo_id: String) -> Result<(), ServerFnError> { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/repositories/{repo_id}/scan", state.agent_api_url); let client = reqwest::Client::new(); client .post(&url) .send() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(()) } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct WebhookConfigResponse { pub webhook_secret: Option, pub tracker_type: String, } #[server] pub async fn fetch_webhook_config(repo_id: String) -> Result { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!( "{}/api/v1/repositories/{repo_id}/webhook-config", state.agent_api_url ); let resp = reqwest::get(&url) .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: WebhookConfigResponse = resp .json() .await .map_err(|e| ServerFnError::new(e.to_string()))?; Ok(body) } /// Check if a repository has any running scans #[server] pub async fn check_repo_scanning(repo_id: String) -> Result { let state: super::server_state::ServerState = dioxus_fullstack::FullstackContext::extract().await?; let url = format!("{}/api/v1/scan-runs?page=1&limit=1", state.agent_api_url); let resp = reqwest::get(&url) .await .map_err(|e| ServerFnError::new(e.to_string()))?; let body: serde_json::Value = resp .json() .await .map_err(|e| ServerFnError::new(e.to_string()))?; // Check if the most recent scan for this repo is still running if let Some(scans) = body.get("data").and_then(|d| d.as_array()) { for scan in scans { let scan_repo = scan.get("repo_id").and_then(|v| v.as_str()).unwrap_or(""); let status = scan.get("status").and_then(|v| v.as_str()).unwrap_or(""); if scan_repo == repo_id && status == "running" { return Ok(true); } } } Ok(false) }