Skip to main content

heldar_kernel/routes/
recording_control.rs

1//! Manual event-recording control.
2//!
3//! `POST /api/v1/cameras/{id}/record-trigger` fires an event recording trigger for a camera, exactly
4//! like a zone/breach event would: it extends the camera's post-roll recording window to
5//! `now + post_roll_seconds`. Only meaningful for `event` / `scheduled_event` cameras; managed by
6//! manager+. Triggers are evaluated against the SERVER's wall clock.
7
8use axum::extract::{Path, State};
9use axum::routing::post;
10use axum::{Json, Router};
11use chrono::{DateTime, Utc};
12use serde::Serialize;
13use serde_json::json;
14
15use crate::auth::{self, Principal};
16use crate::error::{AppError, AppResult};
17use crate::routes::cameras::load_camera;
18use crate::state::AppState;
19
20pub fn router() -> Router<AppState> {
21    Router::new().route("/api/v1/cameras/{id}/record-trigger", post(record_trigger))
22}
23
24/// Result of a manual event-recording trigger.
25#[derive(Debug, Serialize)]
26pub struct TriggerResult {
27    pub camera_id: String,
28    pub triggered: bool,
29    /// When the post-roll recording window currently ends (server time, UTC). Repeated triggers
30    /// extend it.
31    pub window_end: DateTime<Utc>,
32    pub pre_roll_seconds: i64,
33    pub post_roll_seconds: i64,
34}
35
36async fn record_trigger(
37    State(st): State<AppState>,
38    Path(id): Path<String>,
39    principal: Principal,
40) -> AppResult<Json<TriggerResult>> {
41    principal.require(principal.can_manage_registry(), "trigger event recording")?;
42    let cam = load_camera(&st.pool, &id).await?;
43    let window_end = st.recorder.trigger(&id, "manual").await.ok_or_else(|| {
44        AppError::BadRequest(
45            "camera is not in an event recording mode (`event` or `scheduled_event`), or recording is disabled".into(),
46        )
47    })?;
48    auth::audit(
49        &st.pool,
50        &principal,
51        "record_trigger",
52        "camera",
53        &id,
54        json!({ "window_end": window_end, "post_roll_seconds": cam.post_roll_seconds }),
55    )
56    .await;
57    Ok(Json(TriggerResult {
58        camera_id: id,
59        triggered: true,
60        window_end,
61        pre_roll_seconds: cam.pre_roll_seconds,
62        post_roll_seconds: cam.post_roll_seconds,
63    }))
64}