Skip to main content

heldar_kernel/routes/
discovery.rs

1use axum::extract::State;
2use axum::routing::post;
3use axum::{Json, Router};
4use serde_json::{json, Value};
5
6use crate::auth::{self, Principal};
7use crate::error::{AppError, AppResult};
8use crate::services::discovery::{self, DiscoverOptions};
9use crate::state::AppState;
10
11pub fn router() -> Router<AppState> {
12    Router::new().route("/api/v1/discover", post(discover_handler))
13}
14
15/// Scan a network range for cameras; optionally verify credentials and auto-register them.
16async fn discover_handler(
17    State(st): State<AppState>,
18    principal: Principal,
19    Json(opts): Json<DiscoverOptions>,
20) -> AppResult<Json<Value>> {
21    // Scanning the LAN is an operational action (viewer+); auto-registering the cameras it finds is a
22    // registry mutation, so it additionally requires manage-registry.
23    principal.require(principal.can_view(), "scan for cameras")?;
24    if opts.auto_add {
25        principal.require(
26            principal.can_manage_registry(),
27            "auto-register discovered cameras",
28        )?;
29    }
30    let devices = discovery::discover(&st.pool, &st.cfg, &st.http, &opts)
31        .await
32        .map_err(AppError::BadRequest)?;
33
34    let mut added: Vec<String> = Vec::new();
35    if opts.auto_add {
36        for d in devices
37            .iter()
38            .filter(|d| d.verified && !d.already_registered)
39        {
40            match discovery::add_device(&st.pool, d).await {
41                Ok(id) => {
42                    st.recorder.reconcile(&id).await;
43                    added.push(id);
44                }
45                Err(e) => {
46                    tracing::error!(addr = %d.address, error = %e, "discover: auto-add failed")
47                }
48            }
49        }
50    }
51
52    if !added.is_empty() {
53        auth::audit(
54            &st.pool,
55            &principal,
56            "discover_auto_add",
57            "discovery",
58            "auto_add",
59            json!({ "added": &added }),
60        )
61        .await;
62    }
63
64    Ok(Json(json!({
65        "scanned": opts.targets,
66        "found": devices.len(),
67        "verified": devices.iter().filter(|d| d.verified).count(),
68        "added": added,
69        "devices": devices,
70    })))
71}