AkurAI Build
Menu

Public repository / 4fac5c7826068a0d615a09851633816b41204468

AkurAI-Notes / crates/notes-api/src/handlers/client_logs.rs

Latest change 2b8f40b2b033847632488d2ab876e6df05c91fd7 - fmt: rustfmt after request-logging change by agent

use crate::handlers::RouteHandler;
use crate::router_setup::{bad_request, internal_error, json_response};
use akurai_http::Request;
use akurai_json::Value;
use serde_json::Value as JsonValue;

const MONITOR_INGEST_URL: &str = "https://monitor.olibuijr.com/api/ingest";
const HOST_ID: i64 = 1;
const APPLICATION_ID: i64 = 7;
const MAX_BODY_BYTES: usize = 256 * 1024;

pub fn client_logs_route() -> RouteHandler {
    Box::new(|req: &Request| {
        if req.body.len() > MAX_BODY_BYTES {
            return bad_request("request body too large");
        }

        let mut body: JsonValue = match serde_json::from_slice(&req.body) {
            Ok(body) => body,
            Err(_) => return bad_request("invalid JSON"),
        };
        let forwarded = match tag_logs(&mut body) {
            Ok(forwarded) => forwarded,
            Err(message) => return bad_request(message),
        };

        let token = match std::env::var("MONITOR_INGEST_TOKEN") {
            Ok(token) if !token.trim().is_empty() => token,
            _ => return internal_error("monitor ingest is not configured"),
        };
        let response = match reqwest::blocking::Client::builder()
            .timeout(std::time::Duration::from_secs(5))
            .build()
            .and_then(|client| {
                client
                    .post(MONITOR_INGEST_URL)
                    .bearer_auth(token)
                    .json(&body)
                    .send()
            }) {
            Ok(response) if response.status().is_success() => response,
            Ok(response) => {
                eprintln!(
                    "client log forwarding failed with status {}",
                    response.status()
                );
                return internal_error("monitor ingest rejected logs");
            }
            Err(error) => {
                eprintln!("client log forwarding failed: {error}");
                return internal_error("monitor ingest unavailable");
            }
        };

        json_response(Value::Object(vec![
            ("forwarded".into(), Value::Int(forwarded as i64)),
            (
                "status".into(),
                Value::Int(response.status().as_u16() as i64),
            ),
        ]))
    })
}

fn tag_logs(body: &mut JsonValue) -> Result<usize, &'static str> {
    let logs = body
        .get_mut("logs")
        .and_then(JsonValue::as_array_mut)
        .ok_or("logs must be an array")?;
    let forwarded = logs.len();

    for log in logs {
        let log = log.as_object_mut().ok_or("each log must be an object")?;
        log.insert("origin".into(), JsonValue::String("client".into()));
        log.insert("host_id".into(), JsonValue::from(HOST_ID));
        log.insert("application_id".into(), JsonValue::from(APPLICATION_ID));
    }

    Ok(forwarded)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn tags_each_browser_log_with_notes_monitor_identity() {
        let mut body = serde_json::json!({
            "logs": [{
                "source": "untrusted-client",
                "line": "warn",
                "origin": "server",
                "host_id": 999,
                "application_id": 999
            }]
        });

        assert_eq!(tag_logs(&mut body), Ok(1));
        let log = &body["logs"][0];
        assert_eq!(log["origin"], "client");
        assert_eq!(log["host_id"], HOST_ID);
        assert_eq!(log["application_id"], APPLICATION_ID);
    }
}