csaf-crud 0.3.4

CSAF 2.0 / 2.1 advisory CRUD server with HATEOAS JSON API and HTML UI (TLS 1.3, HTTP/1.1 + HTTP/2 + HTTP/3)
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 Pierre Gronau, ndaal in Cologne

//! System information and health check API handlers.

use http::{Response, StatusCode};
use serde_json::json;

use crate::app_state::AppState;
use crate::hateoas::self_link;
use crate::router::{Body, json_response, problem_response};

/// Application version from Cargo metadata.
const APP_VERSION: &str = env!("CARGO_PKG_VERSION");

/// Application name from Cargo metadata.
const APP_NAME: &str = env!("CARGO_PKG_NAME");

/// `GET /api/v1/system/info` -- System information including version, OS, and memory.
pub async fn system_info(
    state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let mut sys = sysinfo::System::new();
    sys.refresh_memory();

    let document_count = state.csaf_storage().count_documents().unwrap_or(0);
    let cpu_count = {
        let mut s = sysinfo::System::new();
        s.refresh_cpu_list(sysinfo::CpuRefreshKind::everything());
        s.cpus().len()
    };

    let body = json!({
        "data": {
            "name": APP_NAME,
            "version": APP_VERSION,
            "os": sysinfo::System::name().unwrap_or_else(|| "unknown".to_owned()),
            "os_version": sysinfo::System::os_version().unwrap_or_else(|| "unknown".to_owned()),
            "arch": std::env::consts::ARCH,
            "cpu_count": cpu_count,
            "total_memory_mb": sys.total_memory() / 1_048_576,
            "used_memory_mb": sys.used_memory() / 1_048_576,
            "document_count": document_count,
        },
        "_links": json!({
            "self": self_link("/api/v1/system/info"),
            "health": self_link("/api/v1/system/health"),
        }),
    });

    json_response(StatusCode::OK, &body)
}

/// `GET /api/v1/system/health` -- Health check endpoint.
pub async fn health_check(
    state: AppState,
    _parts: http::request::Parts,
    _params: Vec<(String, String)>,
) -> Response<Body> {
    let storage_ok = state.csaf_storage().check_storage_up().unwrap_or(false);

    let db_ok = state
        .db_pool()
        .with_conn(|conn| conn.query_row("SELECT 1", [], |row| row.get::<_, i64>(0)))
        .is_ok();

    let all_healthy = storage_ok && db_ok;
    let status = if all_healthy {
        StatusCode::OK
    } else {
        StatusCode::SERVICE_UNAVAILABLE
    };

    let body = json!({
        "data": {
            "status": if all_healthy { "healthy" } else { "degraded" },
            "storage": if storage_ok { "embedded (redb)" } else { "down" },
            "database": if db_ok { "embedded (sqlite)" } else { "down" },
        },
        "_links": json!({
            "self": self_link("/api/v1/system/health"),
            "info": self_link("/api/v1/system/info"),
        }),
    });

    let _ = problem_response; // retained for future use
    json_response(status, &body)
}