heldar_kernel/routes/
discovery.rs1use 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
15async fn discover_handler(
17 State(st): State<AppState>,
18 principal: Principal,
19 Json(opts): Json<DiscoverOptions>,
20) -> AppResult<Json<Value>> {
21 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}