use actix::Addr; use actix_web::{delete, get, post, put, web, HttpResponse}; use emgauwa_common::db::{DbController, DbMacro}; use emgauwa_common::errors::{DatabaseError, EmgauwaError}; use emgauwa_common::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay}; use emgauwa_common::types::{ ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate, }; use sqlx::pool::PoolConnection; use sqlx::{Pool, Sqlite}; use crate::app_state; use crate::app_state::AppState; use crate::handlers::EmgauwaMessage; #[get("/macros")] pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, EmgauwaError> { let mut pool_conn = pool.acquire().await?; let db_macros = DbMacro::get_all(&mut pool_conn).await?; let macros: Vec<Macro> = convert_db_list(&mut pool_conn, db_macros)?; Ok(HttpResponse::Ok().json(macros)) } #[get("/macros/{macro_id}")] pub async fn show( pool: web::Data<Pool<Sqlite>>, path: web::Path<(String,)>, ) -> Result<HttpResponse, EmgauwaError> { let mut pool_conn = pool.acquire().await?; let (macro_uid,) = path.into_inner(); let uid = EmgauwaUid::try_from(macro_uid.as_str())?; let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid) .await? .ok_or(DatabaseError::NotFound)?; let return_macro = Macro::from_db_model(&mut pool_conn, db_macro)?; Ok(HttpResponse::Ok().json(return_macro)) } #[post("/macros")] pub async fn add( pool: web::Data<Pool<Sqlite>>, data: web::Json<RequestMacroCreate>, ) -> Result<HttpResponse, EmgauwaError> { let mut pool_conn = pool.acquire().await?; let new_macro = DbMacro::create(&mut pool_conn, EmgauwaUid::default(), &data.name).await?; new_macro .set_actions(&mut pool_conn, data.actions.as_slice()) .await?; let return_macro = Macro::from_db_model(&mut pool_conn, new_macro)?; Ok(HttpResponse::Created().json(return_macro)) } #[put("/macros/{macro_id}")] pub async fn update( pool: web::Data<Pool<Sqlite>>, path: web::Path<(String,)>, data: web::Json<RequestMacroUpdate>, ) -> Result<HttpResponse, EmgauwaError> { let mut pool_conn = pool.acquire().await?; let (macro_uid,) = path.into_inner(); let uid = EmgauwaUid::try_from(macro_uid.as_str())?; let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid) .await? .ok_or(DatabaseError::NotFound)?; if let Some(name) = &data.name { db_macro.update(&mut pool_conn, name).await?; } if let Some(actions) = &data.actions { db_macro .set_actions(&mut pool_conn, actions.as_slice()) .await?; } let return_macro = Macro::from_db_model(&mut pool_conn, db_macro)?; Ok(HttpResponse::Ok().json(return_macro)) } #[delete("/macros/{macro_id}")] pub async fn delete( pool: web::Data<Pool<Sqlite>>, path: web::Path<(String,)>, ) -> Result<HttpResponse, EmgauwaError> { let mut pool_conn = pool.acquire().await?; let (macro_uid,) = path.into_inner(); let uid = EmgauwaUid::try_from(macro_uid.as_str())?; DbMacro::delete_by_uid(&mut pool_conn, uid).await?; Ok(HttpResponse::Ok().emgauwa_message("macro got deleted")) } #[put("/macros/{macro_id}/execute")] pub async fn execute( pool: web::Data<Pool<Sqlite>>, app_state: web::Data<Addr<AppState>>, path: web::Path<(String,)>, query: web::Query<RequestMacroExecute>, ) -> Result<HttpResponse, EmgauwaError> { let mut pool_conn = pool.acquire().await?; let (macro_uid,) = path.into_inner(); let uid = EmgauwaUid::try_from(macro_uid.as_str())?; let db_macro = DbMacro::get_by_uid(&mut pool_conn, &uid) .await? .ok_or(DatabaseError::NotFound)?; let actions_db = match query.weekday { None => db_macro.get_actions(&mut pool_conn).await?, Some(weekday) => { db_macro .get_actions_weekday(&mut pool_conn, weekday) .await? } }; let mut actions: Vec<MacroAction> = convert_db_list(&mut pool_conn, actions_db)?; for action in &actions { action.execute(&mut pool_conn).await?; } let affected_controllers = collect_affected_controllers(&mut pool_conn, &actions).await?; for controller in affected_controllers { let affected_relays = collect_affected_relays(&mut pool_conn, &mut actions, &controller).await?; app_state .send(app_state::Action { controller_uid: controller.uid, action: ControllerWsAction::Relays(affected_relays.clone()), }) .await??; } Ok(HttpResponse::Ok().emgauwa_message("macro got executed")) } async fn collect_affected_controllers( pool_conn: &mut PoolConnection<Sqlite>, actions: &Vec<MacroAction>, ) -> Result<Vec<DbController>, DatabaseError> { let mut affected_controllers: Vec<DbController> = Vec::new(); for action in actions { let controller_id = action.relay.r.controller_id; if affected_controllers .iter() .any(|controller| controller.id == controller_id) { continue; } let controller = DbController::get(pool_conn, controller_id) .await? .ok_or(DatabaseError::NotFound)?; affected_controllers.push(controller); } Ok(affected_controllers) } async fn collect_affected_relays( pool_conn: &mut PoolConnection<Sqlite>, actions: &mut Vec<MacroAction>, controller: &DbController, ) -> Result<Vec<Relay>, DatabaseError> { let mut affected_relays: Vec<Relay> = Vec::new(); for action in actions { if affected_relays .iter() .any(|relay| relay.r.id == action.relay.r.id) || action.relay.r.controller_id != controller.id { continue; } action.relay.reload(pool_conn)?; affected_relays.push(action.relay.clone()); } Ok(affected_relays) }