From e91225d71d7d0017732cfa2c984ec911e74e0b00 Mon Sep 17 00:00:00 2001 From: thibaud-leclere Date: Mon, 13 Apr 2026 14:38:20 +0200 Subject: [PATCH] feat: Tauri commands for tracker CRUD, Tuleap fields, and manual polling Co-Authored-By: Claude Opus 4.6 (1M context) --- src-tauri/src/commands/mod.rs | 2 + src-tauri/src/commands/poller.rs | 98 +++++++++++++++++++++++ src-tauri/src/commands/tracker.rs | 126 ++++++++++++++++++++++++++++++ src-tauri/src/lib.rs | 8 ++ 4 files changed, 234 insertions(+) create mode 100644 src-tauri/src/commands/poller.rs create mode 100644 src-tauri/src/commands/tracker.rs diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 29988c6..e404626 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -1,2 +1,4 @@ pub mod credential; +pub mod poller; pub mod project; +pub mod tracker; diff --git a/src-tauri/src/commands/poller.rs b/src-tauri/src/commands/poller.rs new file mode 100644 index 0000000..6290e3b --- /dev/null +++ b/src-tauri/src/commands/poller.rs @@ -0,0 +1,98 @@ +use crate::error::AppError; +use crate::models::credential::TuleapCredentials; +use crate::models::ticket::ProcessedTicket; +use crate::models::tracker::WatchedTracker; +use crate::services::{crypto, filter_engine}; +use crate::services::tuleap_client::TuleapClient; +use crate::AppState; +use tauri::State; + +#[tauri::command] +pub async fn manual_poll( + state: State<'_, AppState>, + tracker_id: String, +) -> Result, AppError> { + let (tracker, client) = { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + let tracker = WatchedTracker::get_by_id(&db, &tracker_id)?; + + let cred = TuleapCredentials::get(&db)? + .ok_or_else(|| AppError::from("No Tuleap credentials configured".to_string()))?; + + let password = crypto::decrypt(&state.encryption_key, &cred.password_encrypted) + .map_err(AppError::from)?; + + let client = TuleapClient::new( + &state.http_client, + &cred.tuleap_url, + &cred.username, + &password, + ); + + (tracker, client) + }; // lock dropped here + + let artifacts = client + .get_artifacts(tracker.tracker_id) + .await + .map_err(AppError::from)?; + + let filtered = filter_engine::apply_filters(&artifacts, &tracker.filters); + + let mut newly_inserted: Vec = Vec::new(); + + { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + for artifact in &filtered { + let artifact_id = artifact + .get("id") + .and_then(|v| v.as_i64()) + .unwrap_or(0) as i32; + + let artifact_title = artifact + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + + let artifact_data = serde_json::to_string(artifact) + .unwrap_or_else(|_| "{}".to_string()); + + if let Some(ticket) = ProcessedTicket::insert_if_new( + &db, + &tracker.id, + artifact_id, + &artifact_title, + &artifact_data, + )? { + newly_inserted.push(ticket); + } + } + + WatchedTracker::update_last_polled(&db, &tracker.id)?; + } + + Ok(newly_inserted) +} + +#[tauri::command] +pub fn get_queue_status( + state: State<'_, AppState>, + project_id: String, +) -> Result, AppError> { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + let tickets = ProcessedTicket::list_by_project(&db, &project_id)?; + Ok(tickets) +} diff --git a/src-tauri/src/commands/tracker.rs b/src-tauri/src/commands/tracker.rs new file mode 100644 index 0000000..3784062 --- /dev/null +++ b/src-tauri/src/commands/tracker.rs @@ -0,0 +1,126 @@ +use crate::error::AppError; +use crate::models::credential::TuleapCredentials; +use crate::models::ticket::ProcessedTicket; +use crate::models::tracker::{AgentConfig, FilterGroup, WatchedTracker}; +use crate::services::crypto; +use crate::services::tuleap_client::TuleapClient; +use crate::AppState; +use tauri::State; + +fn build_tuleap_client(state: &State) -> Result { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + let cred = TuleapCredentials::get(&db)? + .ok_or_else(|| AppError::from("No Tuleap credentials configured".to_string()))?; + + let password = crypto::decrypt(&state.encryption_key, &cred.password_encrypted) + .map_err(AppError::from)?; + + Ok(TuleapClient::new( + &state.http_client, + &cred.tuleap_url, + &cred.username, + &password, + )) +} + +#[tauri::command] +pub fn add_tracker( + state: State<'_, AppState>, + project_id: String, + tracker_id: i32, + tracker_label: String, + polling_interval: i32, + agent_config: AgentConfig, + filters: Vec, +) -> Result { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + let tracker = WatchedTracker::insert( + &db, + &project_id, + tracker_id, + &tracker_label, + polling_interval, + agent_config, + filters, + )?; + + Ok(tracker) +} + +#[tauri::command] +pub fn list_trackers( + state: State<'_, AppState>, + project_id: String, +) -> Result, AppError> { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + let trackers = WatchedTracker::list_by_project(&db, &project_id)?; + Ok(trackers) +} + +#[tauri::command] +pub fn update_tracker( + state: State<'_, AppState>, + id: String, + polling_interval: i32, + agent_config: AgentConfig, + filters: Vec, + enabled: bool, +) -> Result<(), AppError> { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + WatchedTracker::update(&db, &id, polling_interval, agent_config, filters, enabled)?; + Ok(()) +} + +#[tauri::command] +pub fn remove_tracker(state: State<'_, AppState>, id: String) -> Result<(), AppError> { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + WatchedTracker::delete(&db, &id)?; + Ok(()) +} + +#[tauri::command] +pub async fn get_tracker_fields( + state: State<'_, AppState>, + tracker_id: i32, +) -> Result, AppError> { + let client = build_tuleap_client(&state)?; + let fields = client + .get_tracker_fields(tracker_id) + .await + .map_err(AppError::from)?; + Ok(fields) +} + +#[tauri::command] +pub fn list_processed_tickets( + state: State<'_, AppState>, + project_id: String, +) -> Result, AppError> { + let db = state + .db + .lock() + .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; + + let tickets = ProcessedTicket::list_by_project(&db, &project_id)?; + Ok(tickets) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 95d0909..6a9db99 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -46,6 +46,14 @@ pub fn run() { commands::credential::get_tuleap_credentials, commands::credential::delete_tuleap_credentials, commands::credential::test_tuleap_connection, + commands::tracker::add_tracker, + commands::tracker::list_trackers, + commands::tracker::update_tracker, + commands::tracker::remove_tracker, + commands::tracker::get_tracker_fields, + commands::tracker::list_processed_tickets, + commands::poller::manual_poll, + commands::poller::get_queue_status, ]) .run(tauri::generate_context!()) .expect("error while running tauri application");