diff --git a/emgauwa-controller.toml b/emgauwa-controller.toml index eca558f..6e27e0a 100644 --- a/emgauwa-controller.toml +++ b/emgauwa-controller.toml @@ -13,3 +13,48 @@ file = "stdout" driver = "gpio" pin = 5 inverted = 1 + +[[relays]] +driver = "gpio" +pin = 4 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 3 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 2 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 1 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 0 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 16 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 15 +inverted = 1 + +[[relays]] +driver = "piface" +pin = 1 +inverted = 0 + +[[relays]] +driver = "piface" +pin = 0 +inverted = 0 diff --git a/emgauwa-controller/src/driver.rs b/emgauwa-controller/src/driver.rs index 4a7350c..ce54421 100644 --- a/emgauwa-controller/src/driver.rs +++ b/emgauwa-controller/src/driver.rs @@ -2,17 +2,19 @@ use serde::{Deserialize, Deserializer}; #[derive(Debug, Clone, Copy)] pub enum Driver { - Gpio, - Piface, + Gpio, + Piface, } impl<'de> Deserialize<'de> for Driver { - fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { - match String::deserialize(deserializer)?.as_str() { - "gpio" => Ok(Driver::Gpio), - "piface" => Ok(Driver::Piface), - _ => Err(serde::de::Error::custom("invalid driver")), - } - - } -} \ No newline at end of file + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match String::deserialize(deserializer)?.as_str() { + "gpio" => Ok(Driver::Gpio), + "piface" => Ok(Driver::Piface), + _ => Err(serde::de::Error::custom("invalid driver")), + } + } +} diff --git a/emgauwa-controller/src/main.rs b/emgauwa-controller/src/main.rs index b226b50..7a6e468 100644 --- a/emgauwa-controller/src/main.rs +++ b/emgauwa-controller/src/main.rs @@ -1,32 +1,51 @@ use std::str; -use futures::{future, pin_mut, SinkExt, StreamExt}; +use crate::relay_loop::run_relay_loop; +use crate::settings::Settings; +use emgauwa_lib::db::errors::DatabaseError; +use emgauwa_lib::db::{DbController, DbRelay}; +use emgauwa_lib::types::ControllerUid; +use emgauwa_lib::{db, models}; use futures::channel::mpsc; +use futures::{future, pin_mut, SinkExt, StreamExt}; use sqlx::pool::PoolConnection; use sqlx::Sqlite; use tokio::io::AsyncReadExt; -use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; use tokio_tungstenite::tungstenite::Error; -use emgauwa_lib::db; -use emgauwa_lib::db::Controller; -use emgauwa_lib::db::types::ControllerUid; -use crate::relay_loop::run_relay_loop; -use crate::settings::Settings; +use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; -mod settings; mod driver; mod relay_loop; +mod settings; -fn create_this_controller(conn: &mut PoolConnection, settings: &Settings) -> Controller { - futures::executor::block_on(async { - Controller::create( - conn, - &ControllerUid::new(), - &settings.name, - i64::try_from(settings.relays.len()).expect("Too many relays"), - true - ).await.expect("Failed to create controller") - }) +async fn create_this_controller( + conn: &mut PoolConnection, + settings: &Settings, +) -> DbController { + DbController::create( + conn, + &ControllerUid::default(), + &settings.name, + i64::try_from(settings.relays.len()).expect("Too many relays"), + true, + ) + .await + .expect("Failed to create controller") +} + +async fn create_this_relay( + conn: &mut PoolConnection, + this_controller: &DbController, + settings_relay: &settings::Relay, +) -> DbRelay { + DbRelay::create( + conn, + &settings_relay.name, + settings_relay.number.unwrap(), + this_controller, + ) + .await + .expect("Failed to create relay") } #[tokio::main] @@ -37,22 +56,60 @@ async fn main() { let mut conn = pool.acquire().await.unwrap(); - let this = Controller::get_all(&mut conn) + let db_controller = DbController::get_all(&mut conn) .await .expect("Failed to get controller from database") .pop() - .unwrap_or_else(|| create_this_controller(&mut conn, &settings)); + .unwrap_or_else(|| { + futures::executor::block_on(create_this_controller(&mut conn, &settings)) + }); + + let db_relays: Vec = settings + .relays + .iter() + .map(|relay| { + futures::executor::block_on(async { + match DbRelay::get_by_controller_and_num( + &mut conn, + &db_controller, + relay.number.unwrap(), + ) + .await + { + Ok(relay) => relay, + Err(err) => match err { + DatabaseError::NotFound => { + create_this_relay(&mut conn, &db_controller, relay).await + } + _ => panic!("Failed to get relay from database"), + }, + } + }) + }) + .collect(); + + let db_controller = db_controller + .update(&mut conn, &db_controller.name, db_relays.len() as i64, true) + .await + .unwrap(); + + let relays = db_relays + .into_iter() + .map(|relay| models::Relay::from_db_relay(relay, &mut conn)) + .collect(); + + let this = models::Controller { + controller: db_controller, + relays, + }; let this_json = serde_json::to_string(&this).unwrap(); - println!("{:?}", settings.relays); - println!("{:?}", this); println!("{}", this_json); let url = format!( "ws://{}:{}/api/v1/ws/controllers", - settings.core.host, - settings.core.port + settings.core.host, settings.core.port ); let (stdin_tx, stdin_rx) = mpsc::unbounded(); @@ -83,13 +140,14 @@ async fn read_stdin(tx: mpsc::UnboundedSender) { Ok(n) => n, }; buf.truncate(n); - tx.unbounded_send(Message::text(str::from_utf8(&buf).unwrap())).unwrap(); + tx.unbounded_send(Message::text(str::from_utf8(&buf).unwrap())) + .unwrap(); } } pub async fn handle_message(message_result: Result) { match message_result { Ok(message) => println!("{}", message.into_text().unwrap()), - Err(err) => println!("Error: {}", err) + Err(err) => println!("Error: {}", err), } -} \ No newline at end of file +} diff --git a/emgauwa-controller/src/relay_loop.rs b/emgauwa-controller/src/relay_loop.rs index 22890ff..d961879 100644 --- a/emgauwa-controller/src/relay_loop.rs +++ b/emgauwa-controller/src/relay_loop.rs @@ -1,9 +1,8 @@ -use chrono::Local; use crate::settings::Settings; +use chrono::Local; #[allow(unused_variables)] pub async fn run_relay_loop(settings: Settings) { - let next_timestamp = Local::now().naive_local(); - loop { - } -} \ No newline at end of file + let next_timestamp = Local::now().naive_local(); + loop {} +} diff --git a/emgauwa-controller/src/settings.rs b/emgauwa-controller/src/settings.rs index 111debf..ca59096 100644 --- a/emgauwa-controller/src/settings.rs +++ b/emgauwa-controller/src/settings.rs @@ -1,5 +1,6 @@ use emgauwa_lib::{constants, utils}; use serde_derive::Deserialize; + use crate::driver::Driver; #[derive(Clone, Debug, Deserialize)] @@ -23,6 +24,8 @@ pub struct Logging { #[allow(unused)] pub struct Relay { pub driver: Driver, + pub name: String, + pub number: Option, pub pin: u8, pub inverted: bool, } @@ -54,6 +57,8 @@ impl Default for Relay { fn default() -> Self { Relay { driver: Driver::Gpio, + number: None, + name: String::from("Relay"), pin: 0, inverted: false, } @@ -79,5 +84,13 @@ impl Default for Logging { } pub fn init() -> Settings { - utils::load_settings("controller", "CONTROLLER") + let mut settings: Settings = utils::load_settings("controller", "CONTROLLER"); + + for (num, relay) in settings.relays.iter_mut().enumerate() { + if relay.number.is_none() { + relay.number = Some(num as i64); + } + } + + settings } diff --git a/emgauwa-core/src/main.rs b/emgauwa-core/src/main.rs index 62a4681..1c17a3e 100644 --- a/emgauwa-core/src/main.rs +++ b/emgauwa-core/src/main.rs @@ -1,5 +1,5 @@ -use std::str::FromStr; use actix_cors::Cors; +use std::str::FromStr; use actix_web::middleware::TrailingSlash; use actix_web::{middleware, web, App, HttpServer}; @@ -13,8 +13,8 @@ mod settings; async fn main() -> std::io::Result<()> { let settings = settings::init(); - let log_level: LevelFilter = LevelFilter::from_str(&settings.logging.level) - .expect("Error parsing log level."); + let log_level: LevelFilter = + LevelFilter::from_str(&settings.logging.level).expect("Error parsing log level."); trace!("Log level set to {:?}", log_level); SimpleLogger::new() @@ -26,7 +26,6 @@ async fn main() -> std::io::Result<()> { log::info!("Starting server on {}:{}", settings.host, settings.port); HttpServer::new(move || { - let cors = Cors::default() .allow_any_method() .allow_any_header() diff --git a/emgauwa-lib/src/db/controllers.rs b/emgauwa-lib/src/db/controllers.rs index c1d683c..b930517 100644 --- a/emgauwa-lib/src/db/controllers.rs +++ b/emgauwa-lib/src/db/controllers.rs @@ -5,11 +5,11 @@ use sqlx::pool::PoolConnection; use sqlx::Sqlite; use crate::db::errors::DatabaseError; -use crate::db::Tag; -use crate::db::types::ControllerUid; +use crate::db::DbTag; +use crate::types::ControllerUid; #[derive(Debug, Serialize, Clone)] -pub struct Controller { +pub struct DbController { pub id: i64, pub uid: ControllerUid, pub name: String, @@ -17,11 +17,11 @@ pub struct Controller { pub active: bool, } -impl Controller { +impl DbController { pub async fn get_all( conn: &mut PoolConnection, - ) -> Result, DatabaseError> { - Ok(sqlx::query_as!(Controller, "SELECT * FROM controllers") + ) -> Result, DatabaseError> { + Ok(sqlx::query_as!(DbController, "SELECT * FROM controllers") .fetch_all(conn.deref_mut()) .await?) } @@ -29,12 +29,8 @@ impl Controller { pub async fn get( conn: &mut PoolConnection, id: i64, - ) -> Result { - sqlx::query_as!( - Controller, - "SELECT * FROM controllers WHERE id = ?", - id - ) + ) -> Result { + sqlx::query_as!(DbController, "SELECT * FROM controllers WHERE id = ?", id) .fetch_optional(conn.deref_mut()) .await .map(|s| s.ok_or(DatabaseError::NotFound))? @@ -43,9 +39,9 @@ impl Controller { pub async fn get_by_uid( conn: &mut PoolConnection, filter_uid: &ControllerUid, - ) -> Result { + ) -> Result { sqlx::query_as!( - Controller, + DbController, "SELECT * FROM controllers WHERE uid = ?", filter_uid ) @@ -56,9 +52,9 @@ impl Controller { pub async fn get_by_tag( conn: &mut PoolConnection, - tag: &Tag, - ) -> Result, DatabaseError> { - Ok(sqlx::query_as!(Controller, "SELECT schedule.* FROM controllers AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id) + tag: &DbTag, + ) -> Result, DatabaseError> { + Ok(sqlx::query_as!(DbController, "SELECT schedule.* FROM controllers AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id) .fetch_all(conn.deref_mut()) .await?) } @@ -81,10 +77,10 @@ impl Controller { new_uid: &ControllerUid, new_name: &str, new_relay_count: i64, - new_active: bool - ) -> Result { + new_active: bool, + ) -> Result { sqlx::query_as!( - Controller, + DbController, "INSERT INTO controllers (uid, name, relay_count, active) VALUES (?, ?, ?, ?) RETURNING *", new_uid, new_name, @@ -101,8 +97,8 @@ impl Controller { conn: &mut PoolConnection, new_name: &str, new_relay_count: i64, - new_active: bool - ) -> Result { + new_active: bool, + ) -> Result { sqlx::query!( "UPDATE controllers SET name = ?, relay_count = ?, active = ? WHERE id = ?", new_name, diff --git a/emgauwa-lib/src/db/mod.rs b/emgauwa-lib/src/db/mod.rs index 192822b..7e7df11 100644 --- a/emgauwa-lib/src/db/mod.rs +++ b/emgauwa-lib/src/db/mod.rs @@ -6,20 +6,19 @@ use std::str::FromStr; use crate::db::errors::DatabaseError; use crate::db::model_utils::Period; -use crate::db::types::ScheduleUid; +use crate::types::ScheduleUid; +mod controllers; pub mod errors; mod model_utils; +mod relays; mod schedules; mod tag; -pub mod types; -mod controllers; -mod relays; -pub use controllers::Controller; -pub use relays::Relay; -pub use schedules::{Periods, Schedule}; -pub use tag::Tag; +pub use controllers::DbController; +pub use relays::DbRelay; +pub use schedules::{DbPeriods, DbSchedule}; +pub use tag::DbTag; static MIGRATOR: Migrator = sqlx::migrate!("../migrations"); // defaults to "./migrations" @@ -32,16 +31,16 @@ async fn init_schedule( pool: &Pool, uid: &ScheduleUid, name: &str, - periods: Periods, + periods: DbPeriods, ) -> Result<(), DatabaseError> { trace!("Initializing schedule {:?}", name); - match Schedule::get_by_uid(&mut pool.acquire().await.unwrap(), uid).await { + match DbSchedule::get_by_uid(&mut pool.acquire().await.unwrap(), uid).await { Ok(_) => Ok(()), Err(err) => match err { DatabaseError::NotFound => { trace!("Schedule {:?} not found, inserting", name); sqlx::query_as!( - Schedule, + DbSchedule, "INSERT INTO schedules (uid, name, periods) VALUES (?, ?, ?) RETURNING *", uid, name, @@ -71,7 +70,7 @@ pub async fn init(db: &str) -> Pool { run_migrations(&pool).await; - init_schedule(&pool, &ScheduleUid::Off, "Off", Periods(vec![])) + init_schedule(&pool, &ScheduleUid::Off, "Off", DbPeriods(vec![])) .await .expect("Error initializing schedule Off"); @@ -79,7 +78,7 @@ pub async fn init(db: &str) -> Pool { &pool, &ScheduleUid::On, "On", - Periods(vec![Period::new_on()]), + DbPeriods(vec![Period::new_on()]), ) .await .expect("Error initializing schedule On"); diff --git a/emgauwa-lib/src/db/model_utils.rs b/emgauwa-lib/src/db/model_utils.rs index c455483..73d9f10 100644 --- a/emgauwa-lib/src/db/model_utils.rs +++ b/emgauwa-lib/src/db/model_utils.rs @@ -1,4 +1,4 @@ -use crate::db::Periods; +use crate::db::DbPeriods; use chrono::{NaiveTime, Timelike}; use serde::{Deserialize, Serialize}; use sqlx::database::HasArguments; @@ -51,7 +51,7 @@ impl Period { } } -impl Type for Periods { +impl Type for DbPeriods { fn type_info() -> SqliteTypeInfo { <&[u8] as Type>::type_info() } @@ -61,22 +61,22 @@ impl Type for Periods { } } -impl<'q> Encode<'q, Sqlite> for Periods { +impl<'q> Encode<'q, Sqlite> for DbPeriods { //noinspection DuplicatedCode fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { <&Vec as Encode>::encode(&Vec::from(self), buf) } } -impl<'r> Decode<'r, Sqlite> for Periods { +impl<'r> Decode<'r, Sqlite> for DbPeriods { fn decode(value: SqliteValueRef<'r>) -> Result { let blob = <&[u8] as Decode>::decode(value)?; - Ok(Periods::from(Vec::from(blob))) + Ok(DbPeriods::from(Vec::from(blob))) } } -impl From<&Periods> for Vec { - fn from(periods: &Periods) -> Vec { +impl From<&DbPeriods> for Vec { + fn from(periods: &DbPeriods) -> Vec { periods .0 .iter() @@ -93,7 +93,7 @@ impl From<&Periods> for Vec { } } -impl From> for Periods { +impl From> for DbPeriods { fn from(value: Vec) -> Self { let mut vec = Vec::new(); for i in (3..value.len()).step_by(4) { @@ -106,6 +106,6 @@ impl From> for Periods { end: NaiveTime::from_hms_opt(end_val_h, end_val_m, 0).unwrap(), }); } - Periods(vec) + DbPeriods(vec) } } diff --git a/emgauwa-lib/src/db/relays.rs b/emgauwa-lib/src/db/relays.rs index 8709b1c..70dcb1f 100644 --- a/emgauwa-lib/src/db/relays.rs +++ b/emgauwa-lib/src/db/relays.rs @@ -1,16 +1,15 @@ use serde_derive::Serialize; use std::ops::DerefMut; +use crate::db::DbController; use sqlx::pool::PoolConnection; use sqlx::Sqlite; -use crate::db::Controller; use crate::db::errors::DatabaseError; -use crate::db::Tag; +use crate::db::DbTag; -#[derive(Debug, Serialize, Clone)] -#[derive(sqlx::FromRow)] -pub struct Relay { +#[derive(Debug, Serialize, Clone, sqlx::FromRow)] +pub struct DbRelay { #[serde(skip)] pub id: i64, pub name: String, @@ -19,34 +18,41 @@ pub struct Relay { pub controller_id: i64, } -impl Relay { - pub async fn get_all( - conn: &mut PoolConnection, - ) -> Result, DatabaseError> { - Ok(sqlx::query_as!(Relay, "SELECT * FROM relays") +impl DbRelay { + pub async fn get_all(conn: &mut PoolConnection) -> Result, DatabaseError> { + Ok(sqlx::query_as!(DbRelay, "SELECT * FROM relays") .fetch_all(conn.deref_mut()) .await?) } - pub async fn get( - conn: &mut PoolConnection, - id: i64, - ) -> Result { - sqlx::query_as!( - Relay, - "SELECT * FROM relays WHERE id = ?", - id - ) + pub async fn get(conn: &mut PoolConnection, id: i64) -> Result { + sqlx::query_as!(DbRelay, "SELECT * FROM relays WHERE id = ?", id) .fetch_optional(conn.deref_mut()) .await .map(|s| s.ok_or(DatabaseError::NotFound))? } + pub async fn get_by_controller_and_num( + conn: &mut PoolConnection, + controller: &DbController, + number: i64, + ) -> Result { + sqlx::query_as!( + DbRelay, + "SELECT * FROM relays WHERE controller_id = ? AND number = ?", + controller.id, + number + ) + .fetch_optional(conn.deref_mut()) + .await + .map(|s| s.ok_or(DatabaseError::NotFound))? + } + pub async fn get_by_tag( conn: &mut PoolConnection, - tag: &Tag, - ) -> Result, DatabaseError> { - Ok(sqlx::query_as!(Relay, "SELECT schedule.* FROM relays AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id) + tag: &DbTag, + ) -> Result, DatabaseError> { + Ok(sqlx::query_as!(DbRelay, "SELECT schedule.* FROM relays AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id) .fetch_all(conn.deref_mut()) .await?) } @@ -55,10 +61,10 @@ impl Relay { conn: &mut PoolConnection, new_name: &str, new_number: i64, - new_controller: &Controller, - ) -> Result { + new_controller: &DbController, + ) -> Result { sqlx::query_as!( - Relay, + DbRelay, "INSERT INTO relays (name, number, controller_id) VALUES (?, ?, ?) RETURNING *", new_name, new_number, @@ -69,10 +75,7 @@ impl Relay { .ok_or(DatabaseError::InsertGetError) } - pub async fn delete( - &self, - conn: &mut PoolConnection, - ) -> Result<(), DatabaseError> { + pub async fn delete(&self, conn: &mut PoolConnection) -> Result<(), DatabaseError> { sqlx::query!("DELETE FROM relays WHERE id = ?", self.id) .execute(conn.deref_mut()) .await @@ -87,8 +90,8 @@ impl Relay { conn: &mut PoolConnection, new_name: &str, new_number: i64, - new_controller: &Controller, - ) -> Result { + new_controller: &DbController, + ) -> Result { sqlx::query!( "UPDATE relays SET name = ?, number = ?, controller_id = ? WHERE id = ?", new_name, @@ -99,14 +102,20 @@ impl Relay { .execute(conn.deref_mut()) .await?; - Relay::get(conn, self.id).await + DbRelay::get(conn, self.id).await } - pub async fn get_controller(&self, conn: &mut PoolConnection) -> Result { - Controller::get(conn, self.controller_id).await + pub async fn get_controller( + &self, + conn: &mut PoolConnection, + ) -> Result { + DbController::get(conn, self.controller_id).await } - pub async fn get_tags(&self, conn: &mut PoolConnection) -> Result, DatabaseError> { + pub async fn get_tags( + &self, + conn: &mut PoolConnection, + ) -> Result, DatabaseError> { Ok(sqlx::query_scalar!("SELECT tag FROM tags INNER JOIN junction_tag ON junction_tag.tag_id = tags.id WHERE junction_tag.relay_id = ?", self.id) .fetch_all(conn.deref_mut()) .await?) @@ -122,7 +131,7 @@ impl Relay { .await?; for new_tag in new_tags { - let tag: Tag = Tag::get_by_tag_or_create(conn, new_tag).await?; + let tag: DbTag = DbTag::get_by_tag_or_create(conn, new_tag).await?; tag.link_relay(conn, self).await?; } Ok(()) diff --git a/emgauwa-lib/src/db/schedules.rs b/emgauwa-lib/src/db/schedules.rs index b1c8004..bf5232f 100644 --- a/emgauwa-lib/src/db/schedules.rs +++ b/emgauwa-lib/src/db/schedules.rs @@ -7,27 +7,27 @@ use sqlx::Sqlite; use crate::db::errors::DatabaseError; use crate::db::model_utils::Period; -use crate::db::Tag; -use crate::db::types::ScheduleUid; +use crate::db::DbTag; +use crate::types::ScheduleUid; #[derive(Debug, Serialize, Clone)] -pub struct Schedule { +pub struct DbSchedule { #[serde(skip)] pub id: i64, #[serde(rename(serialize = "id"))] pub uid: ScheduleUid, pub name: String, - pub periods: Periods, + pub periods: DbPeriods, } #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] -pub struct Periods(pub Vec); +pub struct DbPeriods(pub Vec); -impl Schedule { +impl DbSchedule { pub async fn get_all( conn: &mut PoolConnection, - ) -> Result, DatabaseError> { - Ok(sqlx::query_as!(Schedule, "SELECT * FROM schedules") + ) -> Result, DatabaseError> { + Ok(sqlx::query_as!(DbSchedule, "SELECT * FROM schedules") .fetch_all(conn.deref_mut()) .await?) } @@ -35,12 +35,8 @@ impl Schedule { pub async fn get( conn: &mut PoolConnection, id: i64, - ) -> Result { - sqlx::query_as!( - Schedule, - "SELECT * FROM schedules WHERE id = ?", - id - ) + ) -> Result { + sqlx::query_as!(DbSchedule, "SELECT * FROM schedules WHERE id = ?", id) .fetch_optional(conn.deref_mut()) .await .map(|s| s.ok_or(DatabaseError::NotFound))? @@ -49,9 +45,9 @@ impl Schedule { pub async fn get_by_uid( conn: &mut PoolConnection, filter_uid: &ScheduleUid, - ) -> Result { + ) -> Result { sqlx::query_as!( - Schedule, + DbSchedule, "SELECT * FROM schedules WHERE uid = ?", filter_uid ) @@ -62,9 +58,9 @@ impl Schedule { pub async fn get_by_tag( conn: &mut PoolConnection, - tag: &Tag, - ) -> Result, DatabaseError> { - Ok(sqlx::query_as!(Schedule, "SELECT schedule.* FROM schedules AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id) + tag: &DbTag, + ) -> Result, DatabaseError> { + Ok(sqlx::query_as!(DbSchedule, "SELECT schedule.* FROM schedules AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id) .fetch_all(conn.deref_mut()) .await?) } @@ -91,11 +87,11 @@ impl Schedule { pub async fn create( conn: &mut PoolConnection, new_name: &str, - new_periods: &Periods, - ) -> Result { + new_periods: &DbPeriods, + ) -> Result { let uid = ScheduleUid::default(); sqlx::query_as!( - Schedule, + DbSchedule, "INSERT INTO schedules (uid, name, periods) VALUES (?, ?, ?) RETURNING *", uid, new_name, @@ -110,8 +106,8 @@ impl Schedule { &self, conn: &mut PoolConnection, new_name: &str, - new_periods: &Periods, - ) -> Result { + new_periods: &DbPeriods, + ) -> Result { // overwrite periods on protected schedules let new_periods = match self.uid { ScheduleUid::Off | ScheduleUid::On => self.periods.borrow(), @@ -127,7 +123,7 @@ impl Schedule { .execute(conn.deref_mut()) .await?; - Schedule::get(conn, self.id).await + DbSchedule::get(conn, self.id).await } pub async fn get_tags( @@ -149,7 +145,7 @@ impl Schedule { .await?; for new_tag in new_tags { - let tag: Tag = Tag::get_by_tag_or_create(conn, new_tag).await?; + let tag: DbTag = DbTag::get_by_tag_or_create(conn, new_tag).await?; tag.link_schedule(conn, self).await?; } Ok(()) diff --git a/emgauwa-lib/src/db/tag.rs b/emgauwa-lib/src/db/tag.rs index 08b4aa3..f788f5b 100644 --- a/emgauwa-lib/src/db/tag.rs +++ b/emgauwa-lib/src/db/tag.rs @@ -5,28 +5,28 @@ use sqlx::pool::PoolConnection; use sqlx::Sqlite; use crate::db::errors::DatabaseError; -use crate::db::{Relay, Schedule}; +use crate::db::{DbRelay, DbSchedule}; #[derive(Debug, Serialize, Clone)] -pub struct Tag { +pub struct DbTag { pub id: i64, pub tag: String, } -pub struct JunctionTag { +pub struct DbJunctionTag { pub id: i64, pub tag_id: i64, pub relay_id: Option, pub schedule_id: Option, } -impl Tag { +impl DbTag { pub async fn create( conn: &mut PoolConnection, new_tag: &str, - ) -> Result { + ) -> Result { sqlx::query_as!( - Tag, + DbTag, "INSERT INTO tags (tag) VALUES (?) RETURNING *", new_tag ) @@ -35,11 +35,8 @@ impl Tag { .ok_or(DatabaseError::InsertGetError) } - pub async fn get( - conn: &mut PoolConnection, - id: i64, - ) -> Result { - sqlx::query_as!(Tag, "SELECT * FROM tags WHERE id = ?", id) + pub async fn get(conn: &mut PoolConnection, id: i64) -> Result { + sqlx::query_as!(DbTag, "SELECT * FROM tags WHERE id = ?", id) .fetch_optional(conn.deref_mut()) .await .map(|t| t.ok_or(DatabaseError::NotFound))? @@ -48,10 +45,10 @@ impl Tag { pub async fn get_by_tag_or_create( conn: &mut PoolConnection, target_tag: &str, - ) -> Result { - match Tag::get_by_tag(conn, target_tag).await { + ) -> Result { + match DbTag::get_by_tag(conn, target_tag).await { Ok(tag) => Ok(tag), - Err(DatabaseError::NotFound) => Tag::create(conn, target_tag).await, + Err(DatabaseError::NotFound) => DbTag::create(conn, target_tag).await, Err(e) => Err(e), } } @@ -59,8 +56,8 @@ impl Tag { pub async fn get_by_tag( conn: &mut PoolConnection, target_tag: &str, - ) -> Result { - sqlx::query_as!(Tag, "SELECT * FROM tags WHERE tag = ?", target_tag) + ) -> Result { + sqlx::query_as!(DbTag, "SELECT * FROM tags WHERE tag = ?", target_tag) .fetch_optional(conn.deref_mut()) .await .map(|t| t.ok_or(DatabaseError::NotFound))? @@ -69,10 +66,10 @@ impl Tag { pub async fn link_relay( &self, conn: &mut PoolConnection, - target_relay: &Relay, - ) -> Result { + target_relay: &DbRelay, + ) -> Result { sqlx::query_as!( - JunctionTag, + DbJunctionTag, "INSERT INTO junction_tag (tag_id, relay_id) VALUES (?, ?) RETURNING *", self.id, target_relay.id @@ -85,10 +82,10 @@ impl Tag { pub async fn link_schedule( &self, conn: &mut PoolConnection, - target_schedule: &Schedule, - ) -> Result { + target_schedule: &DbSchedule, + ) -> Result { sqlx::query_as!( - JunctionTag, + DbJunctionTag, "INSERT INTO junction_tag (tag_id, schedule_id) VALUES (?, ?) RETURNING *", self.id, target_schedule.id diff --git a/emgauwa-lib/src/db/types/controller_uid.rs b/emgauwa-lib/src/db/types/controller_uid.rs deleted file mode 100644 index 91e4a0d..0000000 --- a/emgauwa-lib/src/db/types/controller_uid.rs +++ /dev/null @@ -1,68 +0,0 @@ -use serde::{Serialize, Serializer}; -use sqlx::{Decode, Encode, Sqlite, Type}; -use sqlx::database::HasArguments; -use sqlx::encode::IsNull; -use sqlx::error::BoxDynError; -use sqlx::sqlite::{SqliteTypeInfo, SqliteValueRef}; -use uuid::Uuid; - -#[derive(Clone, Debug)] -pub struct ControllerUid(Uuid); - -impl ControllerUid { - pub fn new() -> Self { - Self(Uuid::new_v4()) - } -} - -impl Serialize for ControllerUid { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - String::from(self).serialize(serializer) - } -} - -impl From<&ControllerUid> for String { - fn from(uid: &ControllerUid) -> String { - uid.0.as_hyphenated().to_string() - } -} - -impl Type for ControllerUid { - fn type_info() -> SqliteTypeInfo { - <&[u8] as Type>::type_info() - } - - fn compatible(ty: &SqliteTypeInfo) -> bool { - <&[u8] as Type>::compatible(ty) - } -} - -impl<'q> Encode<'q, Sqlite> for ControllerUid { - //noinspection DuplicatedCode - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { - let uuid_val = self.0.as_bytes().to_vec(); - <&Vec as Encode>::encode(&uuid_val, buf) - } -} - -impl<'r> Decode<'r, Sqlite> for ControllerUid { - //noinspection DuplicatedCode - fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(Self::from(<&[u8] as Decode>::decode(value)?)) - } -} - -impl From<&[u8]> for ControllerUid { - fn from(value: &[u8]) -> Self { - Self(Uuid::from_slice(&value).unwrap()) - } -} - -impl From> for ControllerUid { - fn from(value: Vec) -> Self { - Self::from(value.as_slice()) - } -} diff --git a/emgauwa-lib/src/handlers/v1/mod.rs b/emgauwa-lib/src/handlers/v1/mod.rs index a85414c..711bef9 100644 --- a/emgauwa-lib/src/handlers/v1/mod.rs +++ b/emgauwa-lib/src/handlers/v1/mod.rs @@ -1,3 +1,3 @@ +pub mod relays; pub mod schedules; pub mod ws; -pub mod relays; diff --git a/emgauwa-lib/src/handlers/v1/relays.rs b/emgauwa-lib/src/handlers/v1/relays.rs index 1968b56..2f7e69a 100644 --- a/emgauwa-lib/src/handlers/v1/relays.rs +++ b/emgauwa-lib/src/handlers/v1/relays.rs @@ -1,13 +1,12 @@ -use actix_web::{delete, get, post, put, web, HttpResponse}; +use actix_web::{get, web, HttpResponse}; use serde::{Deserialize, Serialize}; -use sqlx::pool::PoolConnection; + use sqlx::{Pool, Sqlite}; -use crate::db::errors::DatabaseError; -use crate::db::Relay; -use crate::db::Tag; +use crate::db::DbRelay; + use crate::handlers::errors::ApiError; -use crate::return_models::ReturnRelay; +use crate::models::Relay; #[derive(Debug, Serialize, Deserialize)] pub struct RequestRelay { @@ -19,10 +18,12 @@ pub struct RequestRelay { pub async fn index(pool: web::Data>) -> Result { let mut pool_conn = pool.acquire().await?; - let relays = Relay::get_all(&mut pool_conn).await?; + let relays = DbRelay::get_all(&mut pool_conn).await?; - let return_relays: Vec = - relays.into_iter().map(|s| ReturnRelay::from_relay(s, &mut pool_conn)).collect(); + let return_relays: Vec = relays + .into_iter() + .map(|s| Relay::from_db_relay(s, &mut pool_conn)) + .collect(); Ok(HttpResponse::Ok().json(return_relays)) } diff --git a/emgauwa-lib/src/handlers/v1/schedules.rs b/emgauwa-lib/src/handlers/v1/schedules.rs index fe3b064..489bac6 100644 --- a/emgauwa-lib/src/handlers/v1/schedules.rs +++ b/emgauwa-lib/src/handlers/v1/schedules.rs @@ -4,16 +4,16 @@ use sqlx::pool::PoolConnection; use sqlx::{Pool, Sqlite}; use crate::db::errors::DatabaseError; -use crate::db::{Periods, Schedule}; -use crate::db::Tag; -use crate::db::types::ScheduleUid; +use crate::db::DbTag; +use crate::db::{DbPeriods, DbSchedule}; use crate::handlers::errors::ApiError; -use crate::return_models::ReturnSchedule; +use crate::models::Schedule; +use crate::types::ScheduleUid; #[derive(Debug, Serialize, Deserialize)] pub struct RequestSchedule { name: String, - periods: Periods, + periods: DbPeriods, tags: Vec, } @@ -21,10 +21,12 @@ pub struct RequestSchedule { pub async fn index(pool: web::Data>) -> Result { let mut pool_conn = pool.acquire().await?; - let schedules = Schedule::get_all(&mut pool_conn).await?; + let schedules = DbSchedule::get_all(&mut pool_conn).await?; - let return_schedules: Vec = - schedules.into_iter().map(|s| ReturnSchedule::from_schedule(s, &mut pool_conn)).collect(); + let return_schedules: Vec = schedules + .into_iter() + .map(|s| Schedule::from_schedule(s, &mut pool_conn)) + .collect(); Ok(HttpResponse::Ok().json(return_schedules)) } @@ -37,12 +39,14 @@ pub async fn tagged( let mut pool_conn = pool.acquire().await?; let (tag,) = path.into_inner(); - let tag_db = Tag::get_by_tag(&mut pool_conn, &tag).await?; + let tag_db = DbTag::get_by_tag(&mut pool_conn, &tag).await?; - let schedules = Schedule::get_by_tag(&mut pool_conn, &tag_db).await?; + let schedules = DbSchedule::get_by_tag(&mut pool_conn, &tag_db).await?; - let return_schedules: Vec = - schedules.into_iter().map(|s| ReturnSchedule::from_schedule(s, &mut pool_conn)).collect(); + let return_schedules: Vec = schedules + .into_iter() + .map(|s| Schedule::from_schedule(s, &mut pool_conn)) + .collect(); Ok(HttpResponse::Ok().json(return_schedules)) } @@ -57,9 +61,9 @@ pub async fn show( let (schedule_uid,) = path.into_inner(); let uid = ScheduleUid::try_from(schedule_uid.as_str()).or(Err(ApiError::BadUid))?; - let schedule = Schedule::get_by_uid(&mut pool_conn, &uid).await?; + let schedule = DbSchedule::get_by_uid(&mut pool_conn, &uid).await?; - let return_schedule = ReturnSchedule::from_schedule(schedule, &mut pool_conn); + let return_schedule = Schedule::from_schedule(schedule, &mut pool_conn); Ok(HttpResponse::Ok().json(return_schedule)) } @@ -70,22 +74,22 @@ pub async fn add( ) -> Result { let mut pool_conn = pool.acquire().await?; - let new_schedule = Schedule::create(&mut pool_conn, &data.name, &data.periods).await?; + let new_schedule = DbSchedule::create(&mut pool_conn, &data.name, &data.periods).await?; new_schedule .set_tags(&mut pool_conn, data.tags.as_slice()) .await?; - let return_schedule = ReturnSchedule::from_schedule(new_schedule, &mut pool_conn); + let return_schedule = Schedule::from_schedule(new_schedule, &mut pool_conn); Ok(HttpResponse::Created().json(return_schedule)) } async fn add_list_single( conn: &mut PoolConnection, request_schedule: &RequestSchedule, -) -> Result { +) -> Result { let new_schedule = - Schedule::create(conn, &request_schedule.name, &request_schedule.periods).await?; + DbSchedule::create(conn, &request_schedule.name, &request_schedule.periods).await?; new_schedule .set_tags(conn, request_schedule.tags.as_slice()) @@ -101,7 +105,7 @@ pub async fn add_list( ) -> Result { let mut pool_conn = pool.acquire().await?; - let result: Vec> = data + let result: Vec> = data .as_slice() .iter() .map(|request_schedule| { @@ -109,10 +113,12 @@ pub async fn add_list( }) .collect(); - let mut return_schedules: Vec = Vec::new(); + let mut return_schedules: Vec = Vec::new(); for schedule in result { match schedule { - Ok(schedule) => return_schedules.push(ReturnSchedule::from_schedule(schedule, &mut pool_conn)), + Ok(schedule) => { + return_schedules.push(Schedule::from_schedule(schedule, &mut pool_conn)) + } Err(e) => return Ok(HttpResponse::from(e)), } } @@ -130,7 +136,7 @@ pub async fn update( let (schedule_uid,) = path.into_inner(); let uid = ScheduleUid::try_from(schedule_uid.as_str()).or(Err(ApiError::BadUid))?; - let schedule = Schedule::get_by_uid(&mut pool_conn, &uid).await?; + let schedule = DbSchedule::get_by_uid(&mut pool_conn, &uid).await?; let schedule = schedule .update(&mut pool_conn, data.name.as_str(), &data.periods) @@ -140,7 +146,7 @@ pub async fn update( .set_tags(&mut pool_conn, data.tags.as_slice()) .await?; - let return_schedule = ReturnSchedule::from_schedule(schedule, &mut pool_conn); + let return_schedule = Schedule::from_schedule(schedule, &mut pool_conn); Ok(HttpResponse::Ok().json(return_schedule)) } @@ -158,7 +164,7 @@ pub async fn delete( ScheduleUid::Off => Err(ApiError::ProtectedSchedule), ScheduleUid::On => Err(ApiError::ProtectedSchedule), ScheduleUid::Any(_) => { - Schedule::delete_by_uid(&mut pool_conn, uid).await?; + DbSchedule::delete_by_uid(&mut pool_conn, uid).await?; Ok(HttpResponse::Ok().json("schedule got deleted")) } } diff --git a/emgauwa-lib/src/handlers/v1/ws/controllers.rs b/emgauwa-lib/src/handlers/v1/ws/controllers.rs index 9b3f6e1..8a14de3 100644 --- a/emgauwa-lib/src/handlers/v1/ws/controllers.rs +++ b/emgauwa-lib/src/handlers/v1/ws/controllers.rs @@ -1,4 +1,4 @@ -use crate::db::Schedule; +use crate::db::DbSchedule; use crate::handlers::errors::ApiError; use actix::{Actor, StreamHandler}; use actix_web::{get, web, HttpRequest, HttpResponse}; @@ -15,10 +15,10 @@ impl Actor for ControllerWs { type Context = ws::WebsocketContext; } -async fn get_schedules(pool: &mut Pool) -> Result, ApiError> { +async fn get_schedules(pool: &mut Pool) -> Result, ApiError> { let mut pool_conn = pool.acquire().await?; - Ok(Schedule::get_all(&mut pool_conn).await?) + Ok(DbSchedule::get_all(&mut pool_conn).await?) } impl StreamHandler> for ControllerWs { @@ -28,9 +28,9 @@ impl StreamHandler> for ControllerWs { match msg { Ok(Message::Ping(msg)) => ctx.pong(&msg), Ok(Message::Text(text)) => { - println!("Got text: {}", text.to_string()); + println!("Got text: {}", text); ctx.text(schedules_json) - }, + } _ => {} } } diff --git a/emgauwa-lib/src/lib.rs b/emgauwa-lib/src/lib.rs index 0de9d46..acdab58 100644 --- a/emgauwa-lib/src/lib.rs +++ b/emgauwa-lib/src/lib.rs @@ -1,5 +1,6 @@ pub mod constants; pub mod db; pub mod handlers; -pub mod return_models; +pub mod models; +pub mod types; pub mod utils; diff --git a/emgauwa-lib/src/models/mod.rs b/emgauwa-lib/src/models/mod.rs new file mode 100644 index 0000000..bfd7a70 --- /dev/null +++ b/emgauwa-lib/src/models/mod.rs @@ -0,0 +1,51 @@ +use crate::db; +use futures::executor; +use serde_derive::Serialize; +use sqlx::pool::PoolConnection; +use sqlx::Sqlite; + +#[derive(Serialize, Debug)] +pub struct Schedule { + #[serde(flatten)] + pub schedule: db::DbSchedule, + pub tags: Vec, +} + +#[derive(Serialize, Debug)] +pub struct Relay { + #[serde(flatten)] + pub relay: db::DbRelay, + pub controller: db::DbController, + pub tags: Vec, +} + +#[derive(Serialize, Debug)] +pub struct Controller { + #[serde(flatten)] + pub controller: db::DbController, + pub relays: Vec, +} + +impl Schedule { + pub fn from_schedule(schedule: db::DbSchedule, conn: &mut PoolConnection) -> Self { + let schedule = schedule.clone(); + let tags = executor::block_on(schedule.get_tags(conn)).unwrap(); + + Schedule { schedule, tags } + } +} + +impl Relay { + pub fn from_db_relay(relay: db::DbRelay, conn: &mut PoolConnection) -> Self { + let relay = relay.clone(); + let controller = + executor::block_on(db::DbController::get(conn, relay.controller_id)).unwrap(); + let tags = executor::block_on(relay.get_tags(conn)).unwrap(); + + Relay { + relay, + controller, + tags, + } + } +} diff --git a/emgauwa-lib/src/return_models.rs b/emgauwa-lib/src/return_models.rs deleted file mode 100644 index 80caa58..0000000 --- a/emgauwa-lib/src/return_models.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::db::{Controller, Relay, Schedule}; -use futures::executor; -use serde::Serialize; -use sqlx::pool::PoolConnection; -use sqlx::Sqlite; -use crate::db::types::ControllerUid; - -#[derive(Debug, Serialize)] -pub struct ReturnSchedule { - #[serde(flatten)] - pub schedule: Schedule, - pub tags: Vec, -} - -impl ReturnSchedule { - pub fn from_schedule(schedule: Schedule, conn: &mut PoolConnection) -> Self { - let schedule = schedule.clone(); - let tags = executor::block_on(schedule.get_tags(conn)).unwrap(); - - ReturnSchedule { - schedule, - tags, - } - } -} - -#[derive(Debug, Serialize)] -pub struct ReturnRelay { - #[serde(flatten)] - pub relay: Relay, - pub controller: Controller, - pub controller_id: ControllerUid, - pub tags: Vec, -} - -impl ReturnRelay { - pub fn from_relay(relay: Relay, conn: &mut PoolConnection) -> Self { - let relay = relay.clone(); - let controller = executor::block_on(Controller::get(conn, relay.controller_id)).unwrap(); - let controller_uid = controller.uid.clone(); - let tags = executor::block_on(relay.get_tags(conn)).unwrap(); - - ReturnRelay { - relay, - controller, - controller_id: controller_uid, - tags, - } - } -} diff --git a/emgauwa-lib/src/types/controller_uid.rs b/emgauwa-lib/src/types/controller_uid.rs new file mode 100644 index 0000000..d2a594c --- /dev/null +++ b/emgauwa-lib/src/types/controller_uid.rs @@ -0,0 +1,68 @@ +use serde::{Serialize, Serializer}; +use sqlx::database::HasArguments; +use sqlx::encode::IsNull; +use sqlx::error::BoxDynError; +use sqlx::sqlite::{SqliteTypeInfo, SqliteValueRef}; +use sqlx::{Decode, Encode, Sqlite, Type}; +use uuid::Uuid; + +#[derive(Clone, Debug)] +pub struct ControllerUid(Uuid); + +impl Default for ControllerUid { + fn default() -> Self { + Self(Uuid::new_v4()) + } +} + +impl Serialize for ControllerUid { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + String::from(self).serialize(serializer) + } +} + +impl From<&ControllerUid> for String { + fn from(uid: &ControllerUid) -> String { + uid.0.as_hyphenated().to_string() + } +} + +impl Type for ControllerUid { + fn type_info() -> SqliteTypeInfo { + <&[u8] as Type>::type_info() + } + + fn compatible(ty: &SqliteTypeInfo) -> bool { + <&[u8] as Type>::compatible(ty) + } +} + +impl<'q> Encode<'q, Sqlite> for ControllerUid { + //noinspection DuplicatedCode + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + let uuid_val = self.0.as_bytes().to_vec(); + <&Vec as Encode>::encode(&uuid_val, buf) + } +} + +impl<'r> Decode<'r, Sqlite> for ControllerUid { + //noinspection DuplicatedCode + fn decode(value: SqliteValueRef<'r>) -> Result { + Ok(Self::from(<&[u8] as Decode>::decode(value)?)) + } +} + +impl From<&[u8]> for ControllerUid { + fn from(value: &[u8]) -> Self { + Self(Uuid::from_slice(value).unwrap()) + } +} + +impl From> for ControllerUid { + fn from(value: Vec) -> Self { + Self::from(value.as_slice()) + } +} diff --git a/emgauwa-lib/src/db/types/mod.rs b/emgauwa-lib/src/types/mod.rs similarity index 65% rename from emgauwa-lib/src/db/types/mod.rs rename to emgauwa-lib/src/types/mod.rs index 9974977..0af949c 100644 --- a/emgauwa-lib/src/db/types/mod.rs +++ b/emgauwa-lib/src/types/mod.rs @@ -1,5 +1,5 @@ -mod schedule_uid; mod controller_uid; +mod schedule_uid; +pub use controller_uid::ControllerUid; pub use schedule_uid::ScheduleUid; -pub use controller_uid::ControllerUid; \ No newline at end of file diff --git a/emgauwa-lib/src/db/types/schedule_uid.rs b/emgauwa-lib/src/types/schedule_uid.rs similarity index 100% rename from emgauwa-lib/src/db/types/schedule_uid.rs rename to emgauwa-lib/src/types/schedule_uid.rs diff --git a/emgauwa-lib/src/utils.rs b/emgauwa-lib/src/utils.rs index 694fe46..b74f68b 100644 --- a/emgauwa-lib/src/utils.rs +++ b/emgauwa-lib/src/utils.rs @@ -1,9 +1,8 @@ pub fn load_settings(config_name: &str, env_prefix: &str) -> T where - for<'de> T: serde::Deserialize<'de> + for<'de> T: serde::Deserialize<'de>, { - let default_file = config::File::with_name(&format!("emgauwa-{}", config_name)) - .required(false); + let default_file = config::File::with_name(&format!("emgauwa-{}", config_name)).required(false); config::Config::builder() .add_source(default_file) @@ -16,4 +15,4 @@ where .expect("Error building settings") .try_deserialize::() .expect("Error reading settings") -} \ No newline at end of file +}