Split project (keep common)

This commit is contained in:
Tobias Reisinger 2024-04-30 09:13:17 +02:00
parent 9bc75b9627
commit 96a64cf90d
Signed by: serguzim
GPG key ID: 13AD60C237A28DFE
68 changed files with 32 additions and 3502 deletions

184
src/db/controllers.rs Normal file
View file

@ -0,0 +1,184 @@
use std::ops::DerefMut;
use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::{DbRelay, DbTag};
use crate::errors::DatabaseError;
use crate::types::EmgauwaUid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DbController {
#[serde(skip)]
pub id: i64,
#[serde(rename = "id")]
pub uid: EmgauwaUid,
pub name: String,
pub relay_count: i64,
pub active: bool,
}
impl DbController {
pub async fn get_all(
conn: &mut PoolConnection<Sqlite>,
) -> Result<Vec<DbController>, DatabaseError> {
sqlx::query_as!(DbController, "SELECT * FROM controllers")
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get(
conn: &mut PoolConnection<Sqlite>,
id: i64,
) -> Result<Option<DbController>, DatabaseError> {
sqlx::query_as!(DbController, "SELECT * FROM controllers 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<DbController>, DatabaseError> {
sqlx::query_as!(
DbController,
"SELECT * FROM controllers WHERE uid = ?",
filter_uid
)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_by_uid_or_create(
conn: &mut PoolConnection<Sqlite>,
uid: &EmgauwaUid,
new_name: &str,
new_relay_count: i64,
) -> Result<DbController, DatabaseError> {
match DbController::get_by_uid(conn, uid).await? {
Some(tag) => Ok(tag),
None => DbController::create(conn, uid, new_name, new_relay_count).await,
}
}
pub async fn get_by_tag(
conn: &mut PoolConnection<Sqlite>,
tag: &DbTag,
) -> Result<Vec<DbController>, DatabaseError> {
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
.map_err(DatabaseError::from)
}
pub async fn delete_by_uid(
conn: &mut PoolConnection<Sqlite>,
filter_uid: EmgauwaUid,
) -> Result<(), DatabaseError> {
if sqlx::query_scalar!("SELECT 1 FROM controllers WHERE uid = ?", filter_uid)
.fetch_optional(conn.deref_mut())
.await?
.is_none()
{
return Err(DatabaseError::NotFound);
}
sqlx::query!("DELETE FROM controllers WHERE uid = ?", filter_uid)
.execute(conn.deref_mut())
.await
.map(|res| match res.rows_affected() {
0 => Err(DatabaseError::DeleteError),
_ => Ok(()),
})?
}
pub async fn create(
conn: &mut PoolConnection<Sqlite>,
new_uid: &EmgauwaUid,
new_name: &str,
new_relay_count: i64,
) -> Result<DbController, DatabaseError> {
sqlx::query_as!(
DbController,
"INSERT INTO controllers (uid, name, relay_count) VALUES (?, ?, ?) RETURNING *",
new_uid,
new_name,
new_relay_count,
)
.fetch_optional(conn.deref_mut())
.await?
.ok_or(DatabaseError::InsertGetError)
}
pub async fn update(
&self,
conn: &mut PoolConnection<Sqlite>,
new_name: &str,
new_relay_count: i64,
) -> Result<DbController, DatabaseError> {
sqlx::query!(
"UPDATE controllers SET name = ?, relay_count = ? WHERE id = ?",
new_name,
new_relay_count,
self.id,
)
.execute(conn.deref_mut())
.await?;
Self::get(conn, self.id)
.await?
.ok_or(DatabaseError::UpdateGetError)
}
pub async fn update_active(
&self,
conn: &mut PoolConnection<Sqlite>,
new_active: bool,
) -> Result<DbController, DatabaseError> {
sqlx::query!(
"UPDATE controllers SET active = ? WHERE id = ?",
new_active,
self.id,
)
.execute(conn.deref_mut())
.await?;
Self::get(conn, self.id)
.await?
.ok_or(DatabaseError::UpdateGetError)
}
pub async fn get_relays(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<Vec<DbRelay>, DatabaseError> {
sqlx::query_as!(
DbRelay,
"SELECT * FROM relays WHERE controller_id = ?",
self.id
)
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn all_inactive(conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> {
sqlx::query!("UPDATE controllers SET active = 0")
.execute(conn.deref_mut())
.await?;
Ok(())
}
pub async fn reload(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<DbController, DatabaseError> {
Self::get(conn, self.id)
.await?
.ok_or(DatabaseError::NotFound)
}
}

View file

@ -0,0 +1,146 @@
use std::ops::DerefMut;
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::{DbRelay, DbSchedule};
use crate::errors::DatabaseError;
use crate::types::Weekday;
pub struct DbJunctionRelaySchedule {
pub id: i64,
pub weekday: Weekday,
pub relay_id: i64,
pub schedule_id: i64,
}
impl DbJunctionRelaySchedule {
pub async fn get(
conn: &mut PoolConnection<Sqlite>,
id: i64,
) -> Result<Option<DbJunctionRelaySchedule>, DatabaseError> {
sqlx::query_as!(
DbJunctionRelaySchedule,
"SELECT * FROM junction_relay_schedule WHERE id = ?",
id
)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_junction_by_relay_and_weekday(
conn: &mut PoolConnection<Sqlite>,
relay: &DbRelay,
weekday: Weekday,
) -> Result<Option<DbJunctionRelaySchedule>, DatabaseError> {
sqlx::query_as!(
DbJunctionRelaySchedule,
"SELECT * FROM junction_relay_schedule WHERE relay_id = ? AND weekday = ?",
relay.id,
weekday
)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_relays(
conn: &mut PoolConnection<Sqlite>,
schedule: &DbSchedule,
) -> Result<Vec<DbRelay>, DatabaseError> {
sqlx::query_as!(
DbRelay,
r#"SELECT relays.* FROM relays INNER JOIN junction_relay_schedule
ON junction_relay_schedule.relay_id = relays.id
WHERE junction_relay_schedule.schedule_id = ?
ORDER BY junction_relay_schedule.weekday"#,
schedule.id
)
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_schedule(
conn: &mut PoolConnection<Sqlite>,
relay: &DbRelay,
weekday: Weekday,
) -> Result<Option<DbSchedule>, DatabaseError> {
sqlx::query_as!(
DbSchedule,
r#"SELECT schedules.* FROM schedules INNER JOIN junction_relay_schedule
ON junction_relay_schedule.schedule_id = schedules.id
WHERE junction_relay_schedule.relay_id = ? AND junction_relay_schedule.weekday = ?"#,
relay.id,
weekday
)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_schedules(
conn: &mut PoolConnection<Sqlite>,
relay: &DbRelay,
) -> Result<Vec<DbSchedule>, DatabaseError> {
sqlx::query_as!(
DbSchedule,
r#"SELECT schedules.* FROM schedules INNER JOIN junction_relay_schedule
ON junction_relay_schedule.schedule_id = schedules.id
WHERE junction_relay_schedule.relay_id = ?
ORDER BY junction_relay_schedule.weekday"#,
relay.id
)
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn set_schedule(
conn: &mut PoolConnection<Sqlite>,
relay: &DbRelay,
schedule: &DbSchedule,
weekday: Weekday,
) -> Result<DbJunctionRelaySchedule, DatabaseError> {
match Self::get_junction_by_relay_and_weekday(conn, relay, weekday).await? {
None => sqlx::query_as!(
DbJunctionRelaySchedule,
"INSERT INTO junction_relay_schedule (weekday, relay_id, schedule_id) VALUES (?, ?, ?) RETURNING *",
weekday,
relay.id,
schedule.id
)
.fetch_optional(conn.deref_mut())
.await?
.ok_or(DatabaseError::InsertGetError),
Some(junction) => {
sqlx::query!(
"UPDATE junction_relay_schedule SET weekday = ?, relay_id = ?, schedule_id= ? WHERE id = ?",
weekday,
relay.id,
schedule.id,
junction.id
)
.execute(conn.deref_mut())
.await?;
Self::get(conn, junction.id)
.await?
.ok_or(DatabaseError::UpdateGetError)
}
}
}
pub async fn set_schedules(
conn: &mut PoolConnection<Sqlite>,
relay: &DbRelay,
schedules: Vec<&DbSchedule>,
) -> Result<(), DatabaseError> {
for (weekday, schedule) in schedules.iter().enumerate() {
Self::set_schedule(conn, relay, schedule, weekday as Weekday).await?;
}
Ok(())
}
}

48
src/db/junction_tag.rs Normal file
View file

@ -0,0 +1,48 @@
use std::ops::DerefMut;
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::{DbRelay, DbSchedule, DbTag};
use crate::errors::DatabaseError;
pub struct DbJunctionTag {
pub id: i64,
pub tag_id: i64,
pub relay_id: Option<i64>,
pub schedule_id: Option<i64>,
}
impl DbJunctionTag {
pub async fn link_relay(
conn: &mut PoolConnection<Sqlite>,
tag: &DbTag,
relay: &DbRelay,
) -> Result<DbJunctionTag, DatabaseError> {
sqlx::query_as!(
DbJunctionTag,
"INSERT INTO junction_tag (tag_id, relay_id) VALUES (?, ?) RETURNING *",
tag.id,
relay.id
)
.fetch_optional(conn.deref_mut())
.await?
.ok_or(DatabaseError::InsertGetError)
}
pub async fn link_schedule(
conn: &mut PoolConnection<Sqlite>,
tag: &DbTag,
schedule: &DbSchedule,
) -> Result<DbJunctionTag, DatabaseError> {
sqlx::query_as!(
DbJunctionTag,
"INSERT INTO junction_tag (tag_id, schedule_id) VALUES (?, ?) RETURNING *",
tag.id,
schedule.id
)
.fetch_optional(conn.deref_mut())
.await?
.ok_or(DatabaseError::InsertGetError)
}
}

166
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)
}
}

99
src/db/macro_action.rs Normal file
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)
}
}

55
src/db/mod.rs Normal file
View file

@ -0,0 +1,55 @@
use std::str::FromStr;
use sqlx::migrate::Migrator;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
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;
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;
use crate::errors::{DatabaseError, EmgauwaError};
static MIGRATOR: Migrator = sqlx::migrate!("../migrations"); // defaults to "./migrations"
pub async fn run_migrations(pool: &Pool<Sqlite>) -> Result<(), EmgauwaError> {
log::info!("Running migrations");
MIGRATOR.run(pool).await.map_err(DatabaseError::from)?;
Ok(())
}
pub async fn init(db: &str) -> Result<Pool<Sqlite>, EmgauwaError> {
let options = SqliteConnectOptions::from_str(db)?
.create_if_missing(true)
.log_statements(log::LevelFilter::Trace);
let pool: Pool<Sqlite> = SqlitePoolOptions::new()
.acquire_timeout(std::time::Duration::from_secs(1))
.max_connections(5)
.connect_with(options)
.await?;
run_migrations(&pool).await?;
let mut pool_conn = pool.acquire().await?;
DbSchedule::get_on(&mut pool_conn).await?;
DbSchedule::get_off(&mut pool_conn).await?;
Ok(pool)
}

137
src/db/model_utils.rs Normal file
View file

@ -0,0 +1,137 @@
use chrono::{NaiveTime, Timelike};
use serde::{Deserialize, Serialize};
use sqlx::database::HasArguments;
use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
use sqlx::sqlite::{SqliteTypeInfo, SqliteValueRef};
use sqlx::{Decode, Encode, Sqlite, Type};
use crate::db::DbPeriods;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Period {
#[serde(with = "period_format")]
pub start: NaiveTime,
#[serde(with = "period_format")]
pub end: NaiveTime,
}
mod period_format {
use chrono::NaiveTime;
use serde::{self, Deserialize, Deserializer, Serializer};
const FORMAT: &str = "%H:%M";
pub fn serialize<S>(time: &NaiveTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = format!("{}", time.format(FORMAT));
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveTime, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
NaiveTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)
}
}
impl Period {
pub fn new(start: NaiveTime, end: NaiveTime) -> Self {
Period { start, end }
}
pub fn new_on() -> Self {
Period {
start: NaiveTime::MIN,
end: NaiveTime::MIN,
}
}
pub fn is_on(&self, now: &NaiveTime) -> bool {
self.start.eq(&self.end) || (self.start.le(now) && self.end.gt(now))
}
pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> {
if self.start.eq(&self.end) {
// this period is always on
return None;
}
let start_after_now = self.start.gt(now);
let end_after_now = self.end.gt(now);
let start_before_end = self.start.lt(&self.end);
match (start_after_now, end_after_now, start_before_end) {
(false, false, _) => None, // both before now
(true, false, _) => Some(self.start), // only start after now
(false, true, _) => Some(self.end), // only end after now
(true, true, true) => Some(self.start), // both after now but start first
(true, true, false) => Some(self.end), // both after now but end first
}
}
}
impl Type<Sqlite> for DbPeriods {
fn type_info() -> SqliteTypeInfo {
<&[u8] as Type<Sqlite>>::type_info()
}
fn compatible(ty: &SqliteTypeInfo) -> bool {
<&[u8] as Type<Sqlite>>::compatible(ty)
}
}
impl<'q> Encode<'q, Sqlite> for DbPeriods {
//noinspection DuplicatedCode
fn encode_by_ref(&self, buf: &mut <Sqlite as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
<&Vec<u8> as Encode<Sqlite>>::encode(&Vec::from(self), buf)
}
}
impl<'r> Decode<'r, Sqlite> for DbPeriods {
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
let blob = <&[u8] as Decode<Sqlite>>::decode(value)?;
Ok(DbPeriods::from(Vec::from(blob)))
}
}
impl From<&DbPeriods> for Vec<u8> {
fn from(periods: &DbPeriods) -> Vec<u8> {
periods
.0
.iter()
.flat_map(|period| {
let vec = vec![
period.start.hour() as u8,
period.start.minute() as u8,
period.end.hour() as u8,
period.end.minute() as u8,
];
vec
})
.collect()
}
}
impl From<Vec<u8>> for DbPeriods {
fn from(value: Vec<u8>) -> Self {
let mut vec = Vec::new();
for i in (3..value.len()).step_by(4) {
let start_val_h: u32 = value[i - 3] as u32;
let start_val_m: u32 = value[i - 2] as u32;
let end_val_h: u32 = value[i - 1] as u32;
let end_val_m: u32 = value[i] as u32;
vec.push(Period {
start: NaiveTime::from_hms_opt(start_val_h, start_val_m, 0)
.expect("Failed to parse period start time from database"),
end: NaiveTime::from_hms_opt(end_val_h, end_val_m, 0)
.expect("Failed to parse period end time from database"),
});
}
DbPeriods(vec)
}
}

177
src/db/relays.rs Normal file
View file

@ -0,0 +1,177 @@
use std::ops::DerefMut;
use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::{DbController, DbJunctionRelaySchedule, DbJunctionTag, DbSchedule, DbTag};
use crate::errors::DatabaseError;
use crate::types::Weekday;
use crate::utils;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DbRelay {
#[serde(skip)]
pub id: i64,
pub name: String,
pub number: i64,
#[serde(skip)]
pub controller_id: i64,
}
impl DbRelay {
pub async fn get_all(conn: &mut PoolConnection<Sqlite>) -> Result<Vec<DbRelay>, DatabaseError> {
sqlx::query_as!(DbRelay, "SELECT * FROM relays")
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get(
conn: &mut PoolConnection<Sqlite>,
id: i64,
) -> Result<Option<DbRelay>, DatabaseError> {
sqlx::query_as!(DbRelay, "SELECT * FROM relays WHERE id = ?", id)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_by_controller_and_num(
conn: &mut PoolConnection<Sqlite>,
controller: &DbController,
number: i64,
) -> Result<Option<DbRelay>, DatabaseError> {
sqlx::query_as!(
DbRelay,
"SELECT * FROM relays WHERE controller_id = ? AND number = ?",
controller.id,
number
)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_by_controller_and_num_or_create(
conn: &mut PoolConnection<Sqlite>,
controller: &DbController,
number: i64,
new_name: &str,
) -> Result<(DbRelay, bool), DatabaseError> {
match DbRelay::get_by_controller_and_num(conn, controller, number).await? {
Some(relay) => Ok((relay, false)),
None => {
let relay = DbRelay::create(conn, new_name, number, controller).await?;
Ok((relay, true))
}
}
}
pub async fn get_by_tag(
conn: &mut PoolConnection<Sqlite>,
tag: &DbTag,
) -> Result<Vec<DbRelay>, DatabaseError> {
sqlx::query_as!(DbRelay, "SELECT relay.* FROM relays AS relay INNER JOIN junction_tag ON junction_tag.relay_id = relay.id WHERE junction_tag.tag_id = ?", tag.id)
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn create(
conn: &mut PoolConnection<Sqlite>,
new_name: &str,
new_number: i64,
new_controller: &DbController,
) -> Result<DbRelay, DatabaseError> {
sqlx::query_as!(
DbRelay,
"INSERT INTO relays (name, number, controller_id) VALUES (?, ?, ?) RETURNING *",
new_name,
new_number,
new_controller.id,
)
.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 relays WHERE id = ?", self.id)
.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<DbRelay, DatabaseError> {
sqlx::query!("UPDATE relays SET name = ? WHERE id = ?", new_name, self.id,)
.execute(conn.deref_mut())
.await?;
DbRelay::get(conn, self.id)
.await?
.ok_or(DatabaseError::UpdateGetError)
}
pub async fn get_controller(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<DbController, DatabaseError> {
DbController::get(conn, self.controller_id)
.await?
.ok_or(DatabaseError::NotFound)
}
pub async fn get_tags(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<Vec<String>, DatabaseError> {
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
.map_err(DatabaseError::from)
}
pub async fn set_tags(
&self,
conn: &mut PoolConnection<Sqlite>,
new_tags: &[String],
) -> Result<(), DatabaseError> {
sqlx::query!("DELETE FROM junction_tag WHERE relay_id = ?", self.id)
.execute(conn.deref_mut())
.await?;
for new_tag in new_tags {
let tag: DbTag = DbTag::get_by_tag_or_create(conn, new_tag).await?;
DbJunctionTag::link_relay(conn, &tag, self).await?;
}
Ok(())
}
pub async fn reload(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<DbRelay, DatabaseError> {
Self::get(conn, self.id)
.await?
.ok_or(DatabaseError::NotFound)
}
pub async fn get_active_schedule(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<DbSchedule, DatabaseError> {
let weekday = utils::get_weekday();
DbJunctionRelaySchedule::get_schedule(conn, self, weekday as Weekday)
.await?
.ok_or(DatabaseError::NotFound)
}
}

209
src/db/schedules.rs Normal file
View file

@ -0,0 +1,209 @@
use std::borrow::Borrow;
use std::ops::DerefMut;
use chrono::NaiveTime;
use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::db::model_utils::Period;
use crate::db::{DbJunctionTag, DbTag};
use crate::errors::DatabaseError;
use crate::types::ScheduleUid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DbSchedule {
#[serde(skip)]
pub id: i64,
#[serde(rename = "id")]
pub uid: ScheduleUid,
pub name: String,
pub periods: DbPeriods,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct DbPeriods(pub Vec<Period>);
impl DbSchedule {
pub async fn get_all(
conn: &mut PoolConnection<Sqlite>,
) -> Result<Vec<DbSchedule>, DatabaseError> {
sqlx::query_as!(DbSchedule, "SELECT * FROM schedules")
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get(
conn: &mut PoolConnection<Sqlite>,
id: i64,
) -> Result<Option<DbSchedule>, DatabaseError> {
sqlx::query_as!(DbSchedule, "SELECT * FROM schedules 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: &ScheduleUid,
) -> Result<Option<DbSchedule>, DatabaseError> {
sqlx::query_as!(
DbSchedule,
"SELECT * FROM schedules WHERE uid = ?",
filter_uid
)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_by_tag(
conn: &mut PoolConnection<Sqlite>,
tag: &DbTag,
) -> Result<Vec<DbSchedule>, DatabaseError> {
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
.map_err(DatabaseError::from)
}
pub async fn delete_by_uid(
conn: &mut PoolConnection<Sqlite>,
filter_uid: ScheduleUid,
) -> Result<(), DatabaseError> {
let filter_uid = match filter_uid {
ScheduleUid::Off => Err(DatabaseError::Protected),
ScheduleUid::On => Err(DatabaseError::Protected),
ScheduleUid::Any(_) => Ok(filter_uid),
}?;
if sqlx::query_scalar!("SELECT 1 FROM schedules WHERE uid = ?", filter_uid)
.fetch_optional(conn.deref_mut())
.await?
.is_none()
{
return Err(DatabaseError::NotFound);
}
sqlx::query!("DELETE FROM schedules WHERE uid = ?", filter_uid)
.execute(conn.deref_mut())
.await
.map(|res| match res.rows_affected() {
0 => Err(DatabaseError::DeleteError),
_ => Ok(()),
})?
}
pub async fn create(
conn: &mut PoolConnection<Sqlite>,
new_uid: ScheduleUid,
new_name: &str,
new_periods: &DbPeriods,
) -> Result<DbSchedule, DatabaseError> {
sqlx::query_as!(
DbSchedule,
"INSERT INTO schedules (uid, name, periods) VALUES (?, ?, ?) RETURNING *",
new_uid,
new_name,
new_periods,
)
.fetch_optional(conn.deref_mut())
.await?
.ok_or(DatabaseError::InsertGetError)
}
pub async fn get_by_uid_or_create(
conn: &mut PoolConnection<Sqlite>,
uid: ScheduleUid,
name: &str,
periods: &DbPeriods,
) -> Result<(DbSchedule, bool), DatabaseError> {
match DbSchedule::get_by_uid(conn, &uid).await? {
Some(schedule) => Ok((schedule, false)),
None => {
let schedule = DbSchedule::create(conn, uid, name, periods).await?;
Ok((schedule, true))
}
}
}
pub async fn get_on(conn: &mut PoolConnection<Sqlite>) -> Result<DbSchedule, DatabaseError> {
if let Some(schedule) = DbSchedule::get_by_uid(conn, &ScheduleUid::On).await? {
return Ok(schedule);
}
let periods = DbPeriods(vec![Period::new_on()]);
Self::create(conn, ScheduleUid::On, "On", &periods).await
}
pub async fn get_off(conn: &mut PoolConnection<Sqlite>) -> Result<DbSchedule, DatabaseError> {
if let Some(schedule) = DbSchedule::get_by_uid(conn, &ScheduleUid::Off).await? {
return Ok(schedule);
}
let periods = DbPeriods(vec![]);
Self::create(conn, ScheduleUid::Off, "Off", &periods).await
}
pub async fn update(
&self,
conn: &mut PoolConnection<Sqlite>,
new_name: &str,
new_periods: &DbPeriods,
) -> Result<DbSchedule, DatabaseError> {
// overwrite periods on protected schedules
let new_periods = match self.uid {
ScheduleUid::Off | ScheduleUid::On => self.periods.borrow(),
ScheduleUid::Any(_) => new_periods,
};
sqlx::query!(
"UPDATE schedules SET name = ?, periods = ? WHERE id = ?",
new_name,
new_periods,
self.id,
)
.execute(conn.deref_mut())
.await?;
DbSchedule::get(conn, self.id)
.await?
.ok_or(DatabaseError::UpdateGetError)
}
pub async fn get_tags(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<Vec<String>, DatabaseError> {
Ok(sqlx::query_scalar!("SELECT tag FROM tags INNER JOIN junction_tag ON junction_tag.tag_id = tags.id WHERE junction_tag.schedule_id = ?", self.id)
.fetch_all(conn.deref_mut())
.await?)
}
pub async fn set_tags(
&self,
conn: &mut PoolConnection<Sqlite>,
new_tags: &[String],
) -> Result<(), DatabaseError> {
sqlx::query!("DELETE FROM junction_tag WHERE schedule_id = ?", self.id)
.execute(conn.deref_mut())
.await?;
for new_tag in new_tags {
let tag: DbTag = DbTag::get_by_tag_or_create(conn, new_tag).await?;
DbJunctionTag::link_schedule(conn, &tag, self).await?;
}
Ok(())
}
pub fn is_on(&self, now: &NaiveTime) -> bool {
self.periods.0.iter().any(|period| period.is_on(now))
}
pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> {
self.periods
.0
.iter()
.filter_map(|period| period.get_next_time(now))
.min()
}
}

91
src/db/tag.rs Normal file
View file

@ -0,0 +1,91 @@
use std::ops::DerefMut;
use serde_derive::Serialize;
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use crate::errors::DatabaseError;
#[derive(Debug, Serialize, Clone)]
pub struct DbTag {
pub id: i64,
pub tag: String,
}
impl DbTag {
pub async fn create(
conn: &mut PoolConnection<Sqlite>,
new_tag: &str,
) -> Result<DbTag, DatabaseError> {
if new_tag.is_empty() {
return Err(DatabaseError::EmptyDataInsert);
}
sqlx::query_as!(
DbTag,
"INSERT INTO tags (tag) VALUES (?) RETURNING *",
new_tag
)
.fetch_optional(conn.deref_mut())
.await?
.ok_or(DatabaseError::InsertGetError)
}
pub async fn get_all(conn: &mut PoolConnection<Sqlite>) -> Result<Vec<DbTag>, DatabaseError> {
sqlx::query_as!(DbTag, "SELECT * FROM tags")
.fetch_all(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get(
conn: &mut PoolConnection<Sqlite>,
id: i64,
) -> Result<Option<DbTag>, DatabaseError> {
sqlx::query_as!(DbTag, "SELECT * FROM tags WHERE id = ?", id)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn get_by_tag_or_create(
conn: &mut PoolConnection<Sqlite>,
target_tag: &str,
) -> Result<DbTag, DatabaseError> {
match DbTag::get_by_tag(conn, target_tag).await? {
Some(tag) => Ok(tag),
None => DbTag::create(conn, target_tag).await,
}
}
pub async fn get_by_tag(
conn: &mut PoolConnection<Sqlite>,
target_tag: &str,
) -> Result<Option<DbTag>, DatabaseError> {
sqlx::query_as!(DbTag, "SELECT * FROM tags WHERE tag = ?", target_tag)
.fetch_optional(conn.deref_mut())
.await
.map_err(DatabaseError::from)
}
pub async fn delete_by_tag(
conn: &mut PoolConnection<Sqlite>,
filter_tag: &str,
) -> Result<(), DatabaseError> {
if sqlx::query_scalar!("SELECT 1 FROM tags WHERE tag = ?", filter_tag)
.fetch_optional(conn.deref_mut())
.await?
.is_none()
{
return Err(DatabaseError::NotFound);
}
sqlx::query!("DELETE FROM tags WHERE tag = ?", filter_tag)
.execute(conn.deref_mut())
.await
.map(|res| match res.rows_affected() {
0 => Err(DatabaseError::DeleteError),
_ => Ok(()),
})?
}
}