Add macros

This commit is contained in:
Tobias Reisinger 2024-04-29 01:11:52 +02:00
parent 51aa0d3c99
commit 661b5004e8
Signed by: serguzim
GPG key ID: 13AD60C237A28DFE
28 changed files with 582 additions and 2 deletions

View file

@ -554,6 +554,12 @@ paths:
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/macro'
operationId: get-api-v1-macros
description: Receive a list with all available macros.
post:

View file

@ -0,0 +1,161 @@
use actix::Addr;
use actix_web::{delete, get, post, put, web, HttpResponse};
use emgauwa_lib::db::DbMacro;
use emgauwa_lib::errors::{DatabaseError, EmgauwaError};
use emgauwa_lib::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay};
use emgauwa_lib::types::{
ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate,
};
use itertools::Itertools;
use sqlx::{Pool, Sqlite};
use crate::app_state;
use crate::app_state::AppState;
#[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().json("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_controller_uids: Vec<EmgauwaUid> = actions
.iter()
.map(|action| action.relay.controller_id.clone())
.unique()
.collect();
for controller_uid in affected_controller_uids {
let mut affected_relays: Vec<Relay> = Vec::new();
let mut affected_relay_ids: Vec<i64> = Vec::new();
for action in actions.iter_mut() {
if affected_relay_ids.contains(&action.relay.r.id) {
continue;
}
action.relay.reload(&mut pool_conn)?;
affected_relays.push(action.relay.clone());
affected_relay_ids.push(action.relay.r.id);
}
app_state
.send(app_state::Action {
controller_uid,
action: ControllerWsAction::Relays(affected_relays.clone()),
})
.await??;
}
Ok(HttpResponse::Ok().finish()) // TODO add a message?
}

View file

@ -1,4 +1,5 @@
pub mod controllers;
pub mod macros;
pub mod relays;
pub mod schedules;
pub mod tags;

View file

@ -108,6 +108,12 @@ async fn main() -> Result<(), std::io::Error> {
.service(handlers::v1::tags::show)
.service(handlers::v1::tags::delete)
.service(handlers::v1::tags::add)
.service(handlers::v1::macros::index)
.service(handlers::v1::macros::show)
.service(handlers::v1::macros::add)
.service(handlers::v1::macros::update)
.service(handlers::v1::macros::delete)
.service(handlers::v1::macros::execute)
.service(handlers::v1::ws::ws_controllers)
.service(handlers::v1::ws::ws_relays),
)

166
emgauwa-lib/src/db/macro.rs Normal file
View file

@ -0,0 +1,166 @@
use std::ops::DerefMut;
use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::{DbController, DbMacroAction, DbRelay, DbSchedule};
use crate::errors::DatabaseError;
use crate::types::{EmgauwaUid, RequestMacroAction};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DbMacro {
#[serde(skip)]
pub id: i64,
#[serde(rename = "id")]
pub uid: EmgauwaUid,
pub name: String,
}
impl DbMacro {
pub async fn get_all(conn: &mut PoolConnection<Sqlite>) -> Result<Vec<DbMacro>, DatabaseError> {
sqlx::query_as!(DbMacro, "SELECT * FROM macros")
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get(
conn: &mut PoolConnection<Sqlite>,
id: i64,
) -> Result<Option<DbMacro>, DatabaseError> {
sqlx::query_as!(DbMacro, "SELECT * FROM macros WHERE id = ?", id)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_by_uid(
conn: &mut PoolConnection<Sqlite>,
filter_uid: &EmgauwaUid,
) -> Result<Option<DbMacro>, DatabaseError> {
sqlx::query_as!(DbMacro, "SELECT * FROM macros WHERE uid = ?", filter_uid)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn create(
conn: &mut PoolConnection<Sqlite>,
new_uid: EmgauwaUid,
new_name: &str,
) -> Result<DbMacro, DatabaseError> {
sqlx::query_as!(
DbMacro,
"INSERT INTO macros (uid, name) VALUES (?, ?) RETURNING *",
new_uid,
new_name
)
.fetch_optional(conn.deref_mut())
.await?
.ok_or(DatabaseError::InsertGetError)
}
pub async fn delete(&self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> {
sqlx::query!("DELETE FROM macros WHERE id = ?", self.id)
.execute(conn.deref_mut())
.await
.map(|res| match res.rows_affected() {
0 => Err(DatabaseError::DeleteError),
_ => Ok(()),
})?
}
pub async fn delete_by_uid(
conn: &mut PoolConnection<Sqlite>,
filter_uid: EmgauwaUid,
) -> Result<(), DatabaseError> {
if sqlx::query_scalar!("SELECT 1 FROM macros WHERE uid = ?", filter_uid)
.fetch_optional(conn.deref_mut())
.await?
.is_none()
{
return Err(DatabaseError::NotFound);
}
sqlx::query!("DELETE FROM macros WHERE uid = ?", filter_uid)
.execute(conn.deref_mut())
.await
.map(|res| match res.rows_affected() {
0 => Err(DatabaseError::DeleteError),
_ => Ok(()),
})?
}
pub async fn update(
&self,
conn: &mut PoolConnection<Sqlite>,
new_name: &str,
) -> Result<DbMacro, DatabaseError> {
sqlx::query!("UPDATE relays SET name = ? WHERE id = ?", new_name, self.id,)
.execute(conn.deref_mut())
.await?;
DbMacro::get(conn, self.id)
.await?
.ok_or(DatabaseError::UpdateGetError)
}
pub async fn set_actions(
&self,
conn: &mut PoolConnection<Sqlite>,
new_actions: &[RequestMacroAction],
) -> Result<(), DatabaseError> {
sqlx::query!("DELETE FROM macro_actions WHERE macro_id = ?", self.id)
.execute(conn.deref_mut())
.await?;
for new_action in new_actions {
let controller = DbController::get_by_uid(conn, &new_action.relay.controller_id)
.await?
.ok_or(DatabaseError::NotFound)?;
let relay =
DbRelay::get_by_controller_and_num(conn, &controller, new_action.relay.number)
.await?
.ok_or(DatabaseError::NotFound)?;
let schedule = DbSchedule::get_by_uid(conn, &new_action.schedule.id)
.await?
.ok_or(DatabaseError::NotFound)?;
DbMacroAction::create(conn, self, &relay, &schedule, new_action.weekday).await?;
}
Ok(())
}
pub async fn get_actions(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<Vec<DbMacroAction>, DatabaseError> {
sqlx::query_as!(
DbMacroAction,
"SELECT * FROM macro_actions WHERE macro_id = ?",
self.id
)
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_actions_weekday(
&self,
conn: &mut PoolConnection<Sqlite>,
weekday: i64,
) -> Result<Vec<DbMacroAction>, DatabaseError> {
sqlx::query_as!(
DbMacroAction,
"SELECT * FROM macro_actions WHERE macro_id = ? AND weekday = ?",
self.id,
weekday
)
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
}

View file

@ -0,0 +1,99 @@
use std::ops::DerefMut;
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::{DbMacro, DbRelay, DbSchedule};
use crate::errors::DatabaseError;
#[derive(Debug, Clone)]
pub struct DbMacroAction {
pub id: i64,
pub macro_id: i64,
pub relay_id: i64,
pub schedule_id: i64,
pub weekday: i64, // should be u8, but sqlite will store it as i64
}
impl DbMacroAction {
pub async fn get_all(
conn: &mut PoolConnection<Sqlite>,
) -> Result<Vec<DbMacroAction>, DatabaseError> {
sqlx::query_as!(DbMacroAction, "SELECT * FROM macro_actions")
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get(
conn: &mut PoolConnection<Sqlite>,
id: i64,
) -> Result<Option<DbMacroAction>, DatabaseError> {
sqlx::query_as!(
DbMacroAction,
"SELECT * FROM macro_actions WHERE id = ?",
id
)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn create(
conn: &mut PoolConnection<Sqlite>,
new_macro: &DbMacro,
new_relay: &DbRelay,
new_schedule: &DbSchedule,
new_weekday: i64,
) -> Result<DbMacroAction, DatabaseError> {
sqlx::query_as!(
DbMacroAction,
"INSERT INTO macro_actions (macro_id, relay_id, schedule_id, weekday) VALUES (?, ?, ?, ?) RETURNING *",
new_macro.id,
new_relay.id,
new_schedule.id,
new_weekday
)
.fetch_optional(conn.deref_mut())
.await?
.ok_or(DatabaseError::InsertGetError)
}
pub async fn delete(&self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> {
sqlx::query!("DELETE FROM macro_actions WHERE id = ?", self.id)
.execute(conn.deref_mut())
.await
.map(|res| match res.rows_affected() {
0 => Err(DatabaseError::DeleteError),
_ => Ok(()),
})?
}
pub async fn get_relay(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<DbRelay, DatabaseError> {
DbRelay::get(conn, self.relay_id)
.await?
.ok_or(DatabaseError::NotFound)
}
pub async fn get_schedule(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<DbSchedule, DatabaseError> {
DbSchedule::get(conn, self.schedule_id)
.await?
.ok_or(DatabaseError::NotFound)
}
pub async fn get_macro(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<DbMacro, DatabaseError> {
DbMacro::get(conn, self.macro_id)
.await?
.ok_or(DatabaseError::NotFound)
}
}

View file

@ -7,6 +7,8 @@ use sqlx::{ConnectOptions, Pool, Sqlite};
mod controllers;
mod junction_relay_schedule;
mod junction_tag;
mod r#macro;
mod macro_action;
mod model_utils;
mod relays;
mod schedules;
@ -15,6 +17,8 @@ mod tag;
pub use controllers::DbController;
pub use junction_relay_schedule::DbJunctionRelaySchedule;
pub use junction_tag::DbJunctionTag;
pub use macro_action::DbMacroAction;
pub use r#macro::DbMacro;
pub use relays::DbRelay;
pub use schedules::{DbPeriods, DbSchedule};
pub use tag::DbTag;

View file

@ -0,0 +1,42 @@
use futures::executor::block_on;
use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::DbMacro;
use crate::errors::DatabaseError;
use crate::models::{convert_db_list, FromDbModel, MacroAction};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Macro {
#[serde(flatten)]
pub m: DbMacro,
pub actions: Vec<MacroAction>,
}
impl FromDbModel for Macro {
type DbModel = DbMacro;
type DbModelCache = ();
fn from_db_model(
conn: &mut PoolConnection<Sqlite>,
db_model: Self::DbModel,
) -> Result<Self, DatabaseError> {
Self::from_db_model_cache(conn, db_model, ())
}
fn from_db_model_cache(
conn: &mut PoolConnection<Sqlite>,
db_model: Self::DbModel,
_cache: Self::DbModelCache,
) -> Result<Self, DatabaseError> {
let actions_db = block_on(db_model.get_actions(conn))?;
let actions: Vec<MacroAction> = convert_db_list(conn, actions_db)?;
Ok(Macro {
m: db_model,
actions,
})
}
}

View file

@ -0,0 +1,56 @@
use futures::executor::block_on;
use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::{DbJunctionRelaySchedule, DbMacroAction};
use crate::errors::{DatabaseError, EmgauwaError};
use crate::models::{FromDbModel, Relay, Schedule};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MacroAction {
pub schedule: Schedule,
pub relay: Relay,
pub weekday: i64,
}
impl FromDbModel for MacroAction {
type DbModel = DbMacroAction;
type DbModelCache = ();
fn from_db_model(
conn: &mut PoolConnection<Sqlite>,
db_model: Self::DbModel,
) -> Result<Self, DatabaseError> {
Self::from_db_model_cache(conn, db_model, ())
}
fn from_db_model_cache(
conn: &mut PoolConnection<Sqlite>,
db_model: Self::DbModel,
_cache: Self::DbModelCache,
) -> Result<Self, DatabaseError> {
let schedule_db = block_on(db_model.get_schedule(conn))?;
let schedule = Schedule::from_db_model(conn, schedule_db)?;
let relay_db = block_on(db_model.get_relay(conn))?;
let relay = Relay::from_db_model(conn, relay_db)?;
let weekday = db_model.weekday;
Ok(MacroAction {
schedule,
relay,
weekday,
})
}
}
impl MacroAction {
pub async fn execute(&self, conn: &mut PoolConnection<Sqlite>) -> Result<(), EmgauwaError> {
DbJunctionRelaySchedule::set_schedule(conn, &self.relay.r, &self.schedule.s, self.weekday)
.await?;
Ok(())
}
}

View file

@ -1,9 +1,13 @@
mod controller;
mod r#macro;
mod macro_action;
mod relay;
mod schedule;
mod tag;
pub use controller::Controller;
pub use macro_action::MacroAction;
pub use r#macro::Macro;
pub use relay::Relay;
pub use schedule::Schedule;
use sqlx::pool::PoolConnection;

View file

@ -4,7 +4,7 @@ use sqlx::Sqlite;
use crate::db::{DbPeriods, DbSchedule};
use crate::errors::DatabaseError;
use crate::types::ScheduleUid;
use crate::types::{EmgauwaUid, ScheduleUid};
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestScheduleCreate {
@ -48,6 +48,41 @@ pub struct RequestTagCreate {
pub tag: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestMacroActionRelay {
pub number: i64,
pub controller_id: EmgauwaUid,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestMacroActionSchedule {
pub id: ScheduleUid,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestMacroAction {
pub weekday: i64,
pub relay: RequestMacroActionRelay,
pub schedule: RequestMacroActionSchedule,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestMacroCreate {
pub name: String,
pub actions: Vec<RequestMacroAction>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestMacroUpdate {
pub name: Option<String>,
pub actions: Option<Vec<RequestMacroAction>>,
}
#[derive(Debug, Deserialize)]
pub struct RequestMacroExecute {
pub weekday: Option<i64>,
}
impl RequestScheduleId {
pub async fn get_schedule(
&self,

View file

@ -125,7 +125,7 @@ CREATE TABLE macros
AUTOINCREMENT
NOT NULL,
uid
VARCHAR(36)
BLOB
NOT NULL
UNIQUE,
name