use axum::{ Json, Router, extract::State, http::{ StatusCode, header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}, }, routing::{get, post}, }; use color_eyre::eyre; use serde::{Deserialize, Serialize}; use tower::ServiceBuilder; use tower_http::{ cors::{Any, CorsLayer}, services::ServeDir, trace::TraceLayer, }; use tracing::{error, warn}; use crate::{AppState, config::Config, device_storage::Mode}; pub async fn app(state: AppState, config: &Config) -> eyre::Result { let app = Router::new() .route("/api/health", get(health_check)) .route("/api/devices", get(get_devices)) .route("/api/device", post(update_device)) .with_state(state) .fallback_service(ServeDir::new(&config.static_files_dir)) .layer( ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer( CorsLayer::new() .allow_headers([AUTHORIZATION, CONTENT_TYPE, ACCEPT]) .allow_origin(Any) .allow_methods(Any), ), ); Ok(app) } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Device { pub id: String, pub mode: Mode, pub color: String, pub sun_brightness: u8, } async fn get_devices(state: State) -> Json> { let devices = state .device_storage .get_all() .await .iter() .map(|(id, data)| Device { id: id.clone(), mode: data.mode.clone(), color: data.color.clone(), sun_brightness: data.sun_brightness, }) .collect(); Json(devices) } async fn update_device(state: State, Json(device): Json) -> StatusCode { if let Err(err) = state .device_storage .update_state(&device.id, device.mode, device.color, device.sun_brightness) .await { warn!("Failed to update device: {}", err); return StatusCode::BAD_REQUEST; } if let Err(err) = state.device_change_events.send(device.id) { error!("Failed to send device change event: {}", err); return StatusCode::INTERNAL_SERVER_ERROR; } StatusCode::OK } async fn health_check() -> &'static str { "ok" }