//! Per-node installed dev-tool versions (probed by the daemon) and the latest //! upstream version per tool (filled by a nightly checker). Drives the fleet //! UI's "update available" badges. use std::collections::HashMap; use cm_domain::NodeId; use sqlx::{PgPool, Row}; use crate::DbError; /// Replace a node's reported tool versions with `tools` (tool, version). pub async fn upsert( pool: &PgPool, node_id: NodeId, tools: &[(String, String)], ) -> Result<(), DbError> { for (tool, version) in tools { sqlx::query( "INSERT INTO node_tools (node_id, tool, version, updated_at) VALUES ($1, $2, $3, now()) ON CONFLICT (node_id, tool) DO UPDATE SET version = excluded.version, updated_at = now()", ) .bind(node_id.as_uuid()) .bind(tool) .bind(version) .execute(pool) .await?; } Ok(()) } /// A node's installed tool versions as `(tool, version)`. pub async fn list(pool: &PgPool, node_id: NodeId) -> Result, DbError> { let rows = sqlx::query("SELECT tool, version FROM node_tools WHERE node_id = $1") .bind(node_id.as_uuid()) .fetch_all(pool) .await?; Ok(rows .into_iter() .map(|r| (r.get::("tool"), r.get::("version"))) .collect()) } /// Record the latest upstream version for a tool. pub async fn set_latest(pool: &PgPool, tool: &str, latest: &str) -> Result<(), DbError> { sqlx::query( "INSERT INTO tool_latest (tool, latest_version, checked_at) VALUES ($1, $2, now()) ON CONFLICT (tool) DO UPDATE SET latest_version = excluded.latest_version, checked_at = now()", ) .bind(tool) .bind(latest) .execute(pool) .await?; Ok(()) } /// All known latest versions, keyed by tool. pub async fn all_latest(pool: &PgPool) -> Result, DbError> { let rows = sqlx::query("SELECT tool, latest_version FROM tool_latest") .fetch_all(pool) .await?; Ok(rows .into_iter() .map(|r| { ( r.get::("tool"), r.get::("latest_version"), ) }) .collect()) }