use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sqlx::types::Json;
use sqlx::FromRow;
use crate::camera_url;
#[derive(Debug, Clone, FromRow)]
pub struct Camera {
pub id: String,
pub site_id: Option<String>,
pub name: String,
pub vendor: String,
pub model: Option<String>,
pub address: Option<String>,
pub rtsp_port: i64,
pub username: Option<String>,
pub password: Option<String>,
pub main_stream_url: Option<String>,
pub sub_stream_url: Option<String>,
pub record_stream: String,
pub codec: Option<String>,
pub resolution_main: Option<String>,
pub resolution_sub: Option<String>,
pub fps_main: Option<i64>,
pub fps_sub: Option<i64>,
pub capabilities: Json<Value>,
pub record_enabled: bool,
pub segment_seconds: i64,
pub retention_hours: i64,
pub storage_quota_bytes: Option<i64>,
pub record_audio: bool,
pub record_mode: String,
pub pre_roll_seconds: i64,
pub post_roll_seconds: i64,
pub mirror_enabled: bool,
pub anr_enabled: bool,
pub anr_replay_url_template: Option<String>,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Camera {
pub fn should_record(&self) -> bool {
self.enabled && self.record_enabled
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CameraView {
pub id: String,
pub site_id: Option<String>,
pub name: String,
pub vendor: String,
pub model: Option<String>,
pub address: Option<String>,
pub rtsp_port: i64,
pub username: Option<String>,
pub has_password: bool,
pub record_stream: String,
pub record_url_masked: Option<String>,
pub codec: Option<String>,
pub resolution_main: Option<String>,
pub resolution_sub: Option<String>,
pub fps_main: Option<i64>,
pub fps_sub: Option<i64>,
pub capabilities: Value,
pub record_enabled: bool,
pub segment_seconds: i64,
pub retention_hours: i64,
pub storage_quota_bytes: Option<i64>,
pub record_audio: bool,
pub record_mode: String,
pub pre_roll_seconds: i64,
pub post_roll_seconds: i64,
pub mirror_enabled: bool,
pub anr_enabled: bool,
pub anr_replay_url_template: Option<String>,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl From<Camera> for CameraView {
fn from(c: Camera) -> Self {
let record_url_masked = camera_url::record_url(&c).map(|u| camera_url::mask_url(&u));
CameraView {
id: c.id,
site_id: c.site_id,
name: c.name,
vendor: c.vendor,
model: c.model,
address: c.address,
rtsp_port: c.rtsp_port,
username: c.username,
has_password: c
.password
.as_deref()
.map(|p| !p.is_empty())
.unwrap_or(false),
record_stream: c.record_stream,
record_url_masked,
codec: c.codec,
resolution_main: c.resolution_main,
resolution_sub: c.resolution_sub,
fps_main: c.fps_main,
fps_sub: c.fps_sub,
capabilities: c.capabilities.0,
record_enabled: c.record_enabled,
segment_seconds: c.segment_seconds,
retention_hours: c.retention_hours,
storage_quota_bytes: c.storage_quota_bytes,
record_audio: c.record_audio,
record_mode: c.record_mode,
pre_roll_seconds: c.pre_roll_seconds,
post_roll_seconds: c.post_roll_seconds,
mirror_enabled: c.mirror_enabled,
anr_enabled: c.anr_enabled,
anr_replay_url_template: c.anr_replay_url_template,
enabled: c.enabled,
created_at: c.created_at,
updated_at: c.updated_at,
}
}
}
#[derive(Debug, Deserialize)]
pub struct CameraCreate {
pub id: Option<String>,
pub name: String,
pub site_id: Option<String>,
#[serde(default = "default_vendor")]
pub vendor: String,
pub model: Option<String>,
pub address: Option<String>,
pub rtsp_port: Option<i64>,
pub username: Option<String>,
pub password: Option<String>,
pub main_stream_url: Option<String>,
pub sub_stream_url: Option<String>,
pub record_stream: Option<String>,
pub capabilities: Option<Value>,
pub record_enabled: Option<bool>,
pub segment_seconds: Option<i64>,
pub retention_hours: Option<i64>,
pub storage_quota_bytes: Option<i64>,
pub record_audio: Option<bool>,
pub record_mode: Option<String>,
pub pre_roll_seconds: Option<i64>,
pub post_roll_seconds: Option<i64>,
pub mirror_enabled: Option<bool>,
pub anr_enabled: Option<bool>,
pub anr_replay_url_template: Option<String>,
pub enabled: Option<bool>,
}
fn default_vendor() -> String {
"generic".to_string()
}
#[derive(Debug, Deserialize, Default)]
pub struct CameraUpdate {
pub name: Option<String>,
pub site_id: Option<String>,
pub vendor: Option<String>,
pub model: Option<String>,
pub address: Option<String>,
pub rtsp_port: Option<i64>,
pub username: Option<String>,
pub password: Option<String>,
pub main_stream_url: Option<String>,
pub sub_stream_url: Option<String>,
pub record_stream: Option<String>,
pub capabilities: Option<Value>,
pub record_enabled: Option<bool>,
pub segment_seconds: Option<i64>,
pub retention_hours: Option<i64>,
pub storage_quota_bytes: Option<i64>,
pub record_audio: Option<bool>,
pub record_mode: Option<String>,
pub pre_roll_seconds: Option<i64>,
pub post_roll_seconds: Option<i64>,
pub mirror_enabled: Option<bool>,
pub anr_enabled: Option<bool>,
pub anr_replay_url_template: Option<String>,
pub enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct Segment {
pub id: String,
pub camera_id: String,
pub path: String,
pub start_time: DateTime<Utc>,
pub end_time: DateTime<Utc>,
pub duration_s: f64,
pub codec: Option<String>,
pub width: Option<i64>,
pub height: Option<i64>,
pub size_bytes: i64,
pub container: String,
pub locked: bool,
pub evidence_locked: bool,
pub incident_id: Option<String>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct RecordingGap {
pub id: String,
pub camera_id: String,
pub gap_start: DateTime<Utc>,
pub gap_end: DateTime<Utc>,
pub gap_seconds: i64,
pub fill_state: String,
pub fill_attempts: i64,
pub last_attempt_at: Option<DateTime<Utc>>,
pub filled_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct CameraStatus {
pub camera_id: String,
pub state: String,
pub last_segment_at: Option<DateTime<Utc>>,
pub last_started_at: Option<DateTime<Utc>>,
pub reconnect_count: i64,
pub segments_written: i64,
pub fps_observed: Option<f64>,
pub bitrate_kbps: Option<f64>,
pub last_error: Option<String>,
pub recorder_pid: Option<i64>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct Event {
pub id: String,
pub camera_id: Option<String>,
pub site_id: Option<String>,
pub event_type: String,
pub severity: String,
pub timestamp: DateTime<Utc>,
pub payload: Json<Value>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct AiTask {
pub id: String,
pub camera_id: String,
pub task_type: String,
pub enabled: bool,
pub stream_profile: String,
pub fps: f64,
pub width: i64,
pub config: Json<Value>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct AiTaskCreate {
pub task_type: String,
pub stream_profile: Option<String>,
pub fps: Option<f64>,
pub width: Option<i64>,
pub config: Option<Value>,
pub enabled: Option<bool>,
}
#[derive(Debug, Deserialize, Default)]
pub struct AiTaskUpdate {
pub task_type: Option<String>,
pub stream_profile: Option<String>,
pub fps: Option<f64>,
pub width: Option<i64>,
pub config: Option<Value>,
pub enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct Detection {
pub id: String,
pub camera_id: String,
pub task_type: String,
pub timestamp: DateTime<Utc>,
pub label: Option<String>,
pub confidence: Option<f64>,
pub bbox: Option<Json<Value>>,
pub track_id: Option<String>,
pub attributes: Json<Value>,
pub frame_id: Option<String>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct DetectionIngest {
pub label: Option<String>,
pub confidence: Option<f64>,
pub bbox: Option<Value>,
pub track_id: Option<String>,
pub attributes: Option<Value>,
}
#[derive(Debug, Deserialize)]
pub struct IngestEvent {
pub event_type: String,
pub severity: Option<String>,
pub payload: Option<Value>,
}
#[derive(Debug, Deserialize)]
pub struct AiIngest {
pub camera_id: String,
pub task_type: String,
pub timestamp: Option<String>,
pub frame_id: Option<String>,
#[serde(default)]
pub detections: Vec<DetectionIngest>,
pub event: Option<IngestEvent>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct Zone {
pub id: String,
pub camera_id: String,
pub name: String,
pub kind: String,
pub polygon: Json<Value>,
pub dwell_seconds: f64,
pub labels: Json<Value>,
pub severity: String,
pub config: Json<Value>,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct ZoneCreate {
pub name: String,
pub kind: Option<String>,
pub polygon: Value,
pub dwell_seconds: Option<f64>,
pub labels: Option<Value>,
pub severity: Option<String>,
pub config: Option<Value>,
pub enabled: Option<bool>,
}
#[derive(Debug, Deserialize, Default)]
pub struct ZoneUpdate {
pub name: Option<String>,
pub kind: Option<String>,
pub polygon: Option<Value>,
pub dwell_seconds: Option<f64>,
pub labels: Option<Value>,
pub severity: Option<String>,
pub config: Option<Value>,
pub enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct ZoneEvent {
pub id: String,
pub camera_id: String,
pub zone_id: String,
pub zone_name: String,
pub track_id: Option<String>,
pub event_type: String,
pub label: Option<String>,
pub timestamp: DateTime<Utc>,
pub dwell_seconds: Option<f64>,
pub evidence_path: Option<String>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, FromRow)]
pub struct User {
pub id: String,
pub username: String,
pub password_hash: String,
pub role: String,
pub display_name: Option<String>,
pub active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize)]
pub struct UserView {
pub id: String,
pub username: String,
pub role: String,
pub display_name: Option<String>,
pub active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl From<User> for UserView {
fn from(u: User) -> Self {
UserView {
id: u.id,
username: u.username,
role: u.role,
display_name: u.display_name,
active: u.active,
created_at: u.created_at,
updated_at: u.updated_at,
}
}
}
#[derive(Debug, Deserialize)]
pub struct UserCreate {
pub username: String,
pub password: String,
pub role: Option<String>,
pub display_name: Option<String>,
pub active: Option<bool>,
}
#[derive(Debug, Deserialize, Default)]
pub struct UserUpdate {
pub password: Option<String>,
pub role: Option<String>,
pub display_name: Option<String>,
pub active: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, FromRow)]
pub struct ApiKey {
pub id: String,
pub name: String,
pub key_hash: String,
pub key_prefix: String,
pub role: String,
pub active: bool,
pub last_used_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ApiKeyView {
pub id: String,
pub name: String,
pub key_prefix: String,
pub role: String,
pub active: bool,
pub last_used_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}
impl From<ApiKey> for ApiKeyView {
fn from(k: ApiKey) -> Self {
ApiKeyView {
id: k.id,
name: k.name,
key_prefix: k.key_prefix,
role: k.role,
active: k.active,
last_used_at: k.last_used_at,
created_at: k.created_at,
}
}
}
#[derive(Debug, Deserialize)]
pub struct ApiKeyCreate {
pub name: String,
pub role: Option<String>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct SnapshotSchedule {
pub id: String,
pub camera_id: String,
pub interval_seconds: i64,
pub enabled: bool,
pub last_fired_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct SnapshotScheduleCreate {
pub interval_seconds: Option<i64>,
pub enabled: Option<bool>,
}
#[derive(Debug, Deserialize, Default)]
pub struct SnapshotScheduleUpdate {
pub interval_seconds: Option<i64>,
pub enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct PersistedSnapshot {
pub id: String,
pub camera_id: String,
pub schedule_id: Option<String>,
pub path: String,
pub taken_at: DateTime<Utc>,
pub size_bytes: i64,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct RecordSchedule {
pub id: String,
pub camera_id: String,
pub days: Json<Value>,
pub time_start: String,
pub time_end: String,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct RecordScheduleCreate {
pub days: Value,
pub time_start: String,
pub time_end: String,
pub enabled: Option<bool>,
}
#[derive(Debug, Deserialize, Default)]
pub struct RecordScheduleUpdate {
pub days: Option<Value>,
pub time_start: Option<String>,
pub time_end: Option<String>,
pub enabled: Option<bool>,
}
pub const BACKUP_SECRET_KEYS: &[&str] = &["pass", "password", "secret_key", "secret"];
#[derive(Debug, Clone, FromRow)]
pub struct BackupDestination {
pub id: String,
pub name: String,
pub kind: String,
pub config: Json<Value>,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize)]
pub struct BackupDestinationView {
pub id: String,
pub name: String,
pub kind: String,
pub config: Value,
pub has_credentials: bool,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
pub fn mask_backup_config(mut config: Value) -> (Value, bool) {
let mut has_credentials = false;
if let Some(obj) = config.as_object_mut() {
for key in BACKUP_SECRET_KEYS {
if let Some(v) = obj.get_mut(*key) {
if v.as_str().map(|s| !s.is_empty()).unwrap_or(false) {
has_credentials = true;
*v = Value::String("***".to_string());
}
}
}
}
(config, has_credentials)
}
impl From<BackupDestination> for BackupDestinationView {
fn from(d: BackupDestination) -> Self {
let (config, has_credentials) = mask_backup_config(d.config.0);
BackupDestinationView {
id: d.id,
name: d.name,
kind: d.kind,
config,
has_credentials,
enabled: d.enabled,
created_at: d.created_at,
updated_at: d.updated_at,
}
}
}
#[derive(Debug, Deserialize)]
pub struct BackupDestinationCreate {
pub name: String,
pub kind: String,
pub config: Option<Value>,
pub enabled: Option<bool>,
}
#[derive(Debug, Deserialize, Default)]
pub struct BackupDestinationUpdate {
pub name: Option<String>,
pub kind: Option<String>,
pub config: Option<Value>,
pub enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize)]
pub struct BackupTestResult {
pub ok: bool,
pub error: Option<String>,
pub latency_ms: i64,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct BackupPolicy {
pub id: String,
pub name: String,
pub destination_id: String,
pub camera_ids: Json<Value>,
pub incident_lock_only: bool,
pub schedule_interval_s: i64,
pub lookback_hours: i64,
pub last_run_at: Option<DateTime<Utc>>,
pub last_job_id: Option<String>,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct BackupPolicyCreate {
pub name: String,
pub destination_id: String,
pub camera_ids: Option<Value>,
pub incident_lock_only: Option<bool>,
pub schedule_interval_s: Option<i64>,
pub lookback_hours: Option<i64>,
pub enabled: Option<bool>,
}
#[derive(Debug, Deserialize, Default)]
pub struct BackupPolicyUpdate {
pub name: Option<String>,
pub destination_id: Option<String>,
pub camera_ids: Option<Value>,
pub incident_lock_only: Option<bool>,
pub schedule_interval_s: Option<i64>,
pub lookback_hours: Option<i64>,
pub enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct BackupJob {
pub id: String,
pub policy_id: Option<String>,
pub destination_id: Option<String>,
pub kind: String,
pub camera_ids: Json<Value>,
pub from_time: Option<DateTime<Utc>>,
pub to_time: Option<DateTime<Utc>>,
pub incident_lock_only: bool,
pub status: String,
pub files_total: i64,
pub files_copied: i64,
pub bytes_copied: i64,
pub error: Option<String>,
pub output_path: Option<String>,
pub output_url: Option<String>,
pub started_at: Option<DateTime<Utc>>,
pub finished_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct CameraOnvif {
pub camera_id: String,
pub device_url: String,
pub manufacturer: Option<String>,
pub model: Option<String>,
pub firmware_version: Option<String>,
pub serial_number: Option<String>,
pub hardware_id: Option<String>,
pub scopes: Json<Value>,
pub media_url: Option<String>,
pub ptz_url: Option<String>,
pub profile_token: Option<String>,
pub ptz_node_token: Option<String>,
pub ptz_enabled: bool,
pub probed_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct PtzPreset {
pub id: String,
pub camera_id: String,
pub token: String,
pub name: Option<String>,
pub fetched_at: DateTime<Utc>,
}
#[derive(Debug, Clone, FromRow, Serialize)]
pub struct CameraIsapi {
pub camera_id: String,
pub device_name: Option<String>,
pub model: Option<String>,
pub firmware_version: Option<String>,
pub serial_number: Option<String>,
pub onvif_enabled: bool,
pub onvif_user_created: bool,
pub time_mode: Option<String>,
pub ntp_server: Option<String>,
pub fetched_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct ArchiveExportRequest {
#[serde(default)]
pub camera_ids: Vec<String>,
pub from: Option<String>,
pub to: Option<String>,
pub incident_lock_only: Option<bool>,
pub trim: Option<bool>,
}