Skip to main content

max / makenotwork

2.6 KB · 86 lines History Blame Raw
1 //! Route check storage, including pruning of paths no longer in `expected_routes`.
2
3 use super::{Result, SqlitePool};
4 use tracing::instrument;
5
6 #[derive(Debug, Clone, sqlx::FromRow, serde::Serialize)]
7 pub struct RouteCheckRow {
8 pub id: i64,
9 pub target: String,
10 pub path: String,
11 pub status_code: i64,
12 pub ok: bool,
13 pub response_time_ms: i64,
14 pub checked_at: String,
15 pub error: Option<String>,
16 }
17
18 #[instrument(skip_all)]
19 pub async fn insert_route_check(
20 pool: &SqlitePool,
21 result: &crate::checks::routes::RouteCheckResult,
22 ) -> Result<i64> {
23 let row = sqlx::query(
24 "INSERT INTO route_checks (target, path, status_code, ok, response_time_ms, checked_at, error)
25 VALUES (?, ?, ?, ?, ?, ?, ?)",
26 )
27 .bind(&result.target)
28 .bind(&result.path)
29 .bind(result.status_code as i64)
30 .bind(result.ok)
31 .bind(result.response_time_ms)
32 .bind(&result.checked_at)
33 .bind(&result.error)
34 .execute(pool)
35 .await?;
36 Ok(row.last_insert_rowid())
37 }
38
39 /// Get the latest route check per path for a target.
40 #[instrument(skip_all)]
41 pub async fn get_latest_route_checks(
42 pool: &SqlitePool,
43 target: &str,
44 ) -> Result<Vec<RouteCheckRow>> {
45 Ok(sqlx::query_as::<_, RouteCheckRow>(
46 "SELECT r.id, r.target, r.path, r.status_code, r.ok, r.response_time_ms, r.checked_at, r.error
47 FROM route_checks r
48 INNER JOIN (SELECT path, MAX(id) as max_id FROM route_checks WHERE target = ? GROUP BY path) latest
49 ON r.id = latest.max_id
50 ORDER BY r.path",
51 )
52 .bind(target)
53 .fetch_all(pool)
54 .await?)
55 }
56
57 /// Delete route_checks for paths no longer in the config.
58 #[instrument(skip_all)]
59 pub async fn prune_stale_routes(
60 pool: &SqlitePool,
61 target: &str,
62 expected_routes: &[String],
63 ) -> Result<u64> {
64 if expected_routes.is_empty() {
65 // No configured routes, delete all route checks for this target
66 let result = sqlx::query("DELETE FROM route_checks WHERE target = ?")
67 .bind(target)
68 .execute(pool)
69 .await?;
70 return Ok(result.rows_affected());
71 }
72
73 // Build placeholders for the IN clause
74 let placeholders: Vec<&str> = expected_routes.iter().map(|_| "?").collect();
75 let sql = format!(
76 "DELETE FROM route_checks WHERE target = ? AND path NOT IN ({})",
77 placeholders.join(", ")
78 );
79 let mut query = sqlx::query(sqlx::AssertSqlSafe(sql)).bind(target);
80 for route in expected_routes {
81 query = query.bind(route);
82 }
83 let result = query.execute(pool).await?;
84 Ok(result.rows_affected())
85 }
86