use rusqlite::Connection; use crate::core::error::{LoreError, Result}; use crate::core::time::iso_to_ms_strict; use crate::gitlab::types::{GitLabLabelEvent, GitLabMilestoneEvent, GitLabStateEvent}; pub fn upsert_state_events( conn: &Connection, project_id: i64, entity_type: &str, entity_local_id: i64, events: &[GitLabStateEvent], ) -> Result { let (issue_id, merge_request_id) = resolve_entity_ids(entity_type, entity_local_id)?; let mut stmt = conn.prepare_cached( "INSERT OR REPLACE INTO resource_state_events (gitlab_id, project_id, issue_id, merge_request_id, state, actor_gitlab_id, actor_username, created_at, source_commit, source_merge_request_iid) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", )?; let mut count = 0; for event in events { let created_at = iso_to_ms_strict(&event.created_at).map_err(LoreError::Other)?; let actor_id = event.user.as_ref().map(|u| u.id); let actor_username = event.user.as_ref().map(|u| u.username.as_str()); let source_mr_iid = event.source_merge_request.as_ref().map(|mr| mr.iid); stmt.execute(rusqlite::params![ event.id, project_id, issue_id, merge_request_id, event.state, actor_id, actor_username, created_at, event.source_commit, source_mr_iid, ])?; count += 1; } Ok(count) } pub fn upsert_label_events( conn: &Connection, project_id: i64, entity_type: &str, entity_local_id: i64, events: &[GitLabLabelEvent], ) -> Result { let (issue_id, merge_request_id) = resolve_entity_ids(entity_type, entity_local_id)?; let mut stmt = conn.prepare_cached( "INSERT OR REPLACE INTO resource_label_events (gitlab_id, project_id, issue_id, merge_request_id, action, label_name, actor_gitlab_id, actor_username, created_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", )?; let mut count = 0; for event in events { let created_at = iso_to_ms_strict(&event.created_at).map_err(LoreError::Other)?; let actor_id = event.user.as_ref().map(|u| u.id); let actor_username = event.user.as_ref().map(|u| u.username.as_str()); stmt.execute(rusqlite::params![ event.id, project_id, issue_id, merge_request_id, event.action, event.label.as_ref().map(|l| l.name.as_str()), actor_id, actor_username, created_at, ])?; count += 1; } Ok(count) } pub fn upsert_milestone_events( conn: &Connection, project_id: i64, entity_type: &str, entity_local_id: i64, events: &[GitLabMilestoneEvent], ) -> Result { let (issue_id, merge_request_id) = resolve_entity_ids(entity_type, entity_local_id)?; let mut stmt = conn.prepare_cached( "INSERT OR REPLACE INTO resource_milestone_events (gitlab_id, project_id, issue_id, merge_request_id, action, milestone_title, milestone_id, actor_gitlab_id, actor_username, created_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", )?; let mut count = 0; for event in events { let created_at = iso_to_ms_strict(&event.created_at).map_err(LoreError::Other)?; let actor_id = event.user.as_ref().map(|u| u.id); let actor_username = event.user.as_ref().map(|u| u.username.as_str()); stmt.execute(rusqlite::params![ event.id, project_id, issue_id, merge_request_id, event.action, event.milestone.as_ref().map(|m| m.title.as_str()), event.milestone.as_ref().map(|m| m.id), actor_id, actor_username, created_at, ])?; count += 1; } Ok(count) } fn resolve_entity_ids( entity_type: &str, entity_local_id: i64, ) -> Result<(Option, Option)> { match entity_type { "issue" => Ok((Some(entity_local_id), None)), "merge_request" => Ok((None, Some(entity_local_id))), _ => Err(LoreError::Other(format!( "Invalid entity type for resource events: {entity_type}" ))), } } pub fn count_events(conn: &Connection) -> Result { let mut counts = EventCounts::default(); let row: (i64, i64) = conn.query_row( "SELECT COUNT(CASE WHEN issue_id IS NOT NULL THEN 1 END), COUNT(CASE WHEN merge_request_id IS NOT NULL THEN 1 END) FROM resource_state_events", [], |row| Ok((row.get(0)?, row.get(1)?)), )?; counts.state_issue = row.0 as usize; counts.state_mr = row.1 as usize; let row: (i64, i64) = conn.query_row( "SELECT COUNT(CASE WHEN issue_id IS NOT NULL THEN 1 END), COUNT(CASE WHEN merge_request_id IS NOT NULL THEN 1 END) FROM resource_label_events", [], |row| Ok((row.get(0)?, row.get(1)?)), )?; counts.label_issue = row.0 as usize; counts.label_mr = row.1 as usize; let row: (i64, i64) = conn.query_row( "SELECT COUNT(CASE WHEN issue_id IS NOT NULL THEN 1 END), COUNT(CASE WHEN merge_request_id IS NOT NULL THEN 1 END) FROM resource_milestone_events", [], |row| Ok((row.get(0)?, row.get(1)?)), )?; counts.milestone_issue = row.0 as usize; counts.milestone_mr = row.1 as usize; Ok(counts) } #[derive(Debug, Default)] pub struct EventCounts { pub state_issue: usize, pub state_mr: usize, pub label_issue: usize, pub label_mr: usize, pub milestone_issue: usize, pub milestone_mr: usize, } impl EventCounts { pub fn total(&self) -> usize { self.state_issue + self.state_mr + self.label_issue + self.label_mr + self.milestone_issue + self.milestone_mr } }