From d7c5c8546032617fc1b47634409901d88ea3d2e2 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 7 Jan 2025 19:07:23 +0100 Subject: [PATCH] feat: add event database entities --- Cargo.lock | 3 ++ Cargo.toml | 5 +- src/db/event.rs | 88 +++++++++++++++++++++++++++++++++++ src/db/mod.rs | 1 + src/main.rs | 3 +- src/migrations/003_events.sql | 9 ++++ 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/db/event.rs create mode 100644 src/migrations/003_events.sql diff --git a/Cargo.lock b/Cargo.lock index ca27dc6..eb41036 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,7 @@ dependencies = [ "chrono", "r2d2", "r2d2_sqlite", + "rusqlite", "serde", "tera", "tokio", @@ -286,6 +287,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets", ] @@ -1062,6 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ "bitflags", + "chrono", "fallible-iterator", "fallible-streaming-iterator", "hashlink", diff --git a/Cargo.toml b/Cargo.toml index 838a997..5fe517e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,12 @@ edition = "2021" [dependencies] axum = { version = "0.7.9", features = ["macros"] } -chrono = "0.4.39" +chrono = { version = "0.4.39", features = ["serde"] } r2d2 = "0.8.10" r2d2_sqlite = "0.25.0" +# this dependency is needed soly because the r2d2_sqlite crate doesn't export +# the 'chrono' feature flag +rusqlite = { version = "0.32.1", features = ["chrono"] } serde = { version = "1.0.217", features = ["derive"] } tera = "1.20.0" tokio = { version = "1.42.0", features = ["full"] } diff --git a/src/db/event.rs b/src/db/event.rs new file mode 100644 index 0000000..08aa6f3 --- /dev/null +++ b/src/db/event.rs @@ -0,0 +1,88 @@ +use std::{fmt::Display, str::FromStr}; + +use chrono::Utc; +use r2d2_sqlite::rusqlite::{ + self, + types::{FromSql, FromSqlError}, + Row, ToSql, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub enum EventType { + Watering, +} + +impl ToString for EventType { + fn to_string(&self) -> String { + String::from(match self { + Self::Watering => "watering", + }) + } +} + +#[derive(Debug)] +pub struct ParseEventTypeErr; + +impl Display for ParseEventTypeErr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "invalid name") + } +} + +impl std::error::Error for ParseEventTypeErr {} + +impl FromStr for EventType { + type Err = ParseEventTypeErr; + + fn from_str(s: &str) -> Result { + match s { + "watering" => Ok(Self::Watering), + _ => Err(ParseEventTypeErr), + } + } +} + +impl ToSql for EventType { + fn to_sql(&self) -> rusqlite::Result> { + Ok(self.to_string().into()) + } +} + +impl FromSql for EventType { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + value + .as_str()? + .parse() + .map_err(|e| FromSqlError::Other(Box::new(e))) + } +} + +#[derive(Serialize)] +pub struct Event { + id: i32, + plant_id: i32, + event_type: EventType, + time: chrono::DateTime, + description: String, +} + +impl Event { + pub fn from_row(row: Row<'_>) -> rusqlite::Result { + Ok(Self { + id: row.get("id")?, + plant_id: row.get("plant_id")?, + event_type: row.get("event_type")?, + time: row.get("time")?, + description: row.get("description")?, + }) + } +} + +#[derive(Deserialize)] +pub struct NewEvent { + plant_id: i32, + event_type: EventType, + time: chrono::DateTime, + description: String, +} diff --git a/src/db/mod.rs b/src/db/mod.rs index 6ac39fc..9af9c10 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,4 +1,5 @@ mod comment; +mod event; mod plant; use r2d2_sqlite::{rusqlite, SqliteConnectionManager}; diff --git a/src/main.rs b/src/main.rs index 10d098b..2119d53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,11 @@ use r2d2_sqlite::SqliteConnectionManager; use tera::Tera; use tower_http::compression::CompressionLayer; -const MIGRATIONS: [&str; 3] = [ +const MIGRATIONS: [&str; 4] = [ include_str!("migrations/000_initial.sql"), include_str!("migrations/001_plants.sql"), include_str!("migrations/002_comments.sql"), + include_str!("migrations/003_events.sql"), ]; const STATIC_FILES: [(&str, &'static str); 1] = [( "htmx_2.0.4.min.js", diff --git a/src/migrations/003_events.sql b/src/migrations/003_events.sql new file mode 100644 index 0000000..2151bb7 --- /dev/null +++ b/src/migrations/003_events.sql @@ -0,0 +1,9 @@ +create table events ( + id integer primary key, + plant_id integer not null + references plants (id) + on delete cascade, + event_type text not null, + time text not null, + description text not null +);