feat: add event database entities
parent
1bc9ae01d8
commit
d7c5c85460
|
@ -253,6 +253,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"r2d2",
|
"r2d2",
|
||||||
"r2d2_sqlite",
|
"r2d2_sqlite",
|
||||||
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"tera",
|
"tera",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -286,6 +287,7 @@ dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
@ -1062,6 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
|
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
"chrono",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"fallible-streaming-iterator",
|
"fallible-streaming-iterator",
|
||||||
"hashlink",
|
"hashlink",
|
||||||
|
|
|
@ -5,9 +5,12 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.7.9", features = ["macros"] }
|
axum = { version = "0.7.9", features = ["macros"] }
|
||||||
chrono = "0.4.39"
|
chrono = { version = "0.4.39", features = ["serde"] }
|
||||||
r2d2 = "0.8.10"
|
r2d2 = "0.8.10"
|
||||||
r2d2_sqlite = "0.25.0"
|
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"] }
|
serde = { version = "1.0.217", features = ["derive"] }
|
||||||
tera = "1.20.0"
|
tera = "1.20.0"
|
||||||
tokio = { version = "1.42.0", features = ["full"] }
|
tokio = { version = "1.42.0", features = ["full"] }
|
||||||
|
|
|
@ -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<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"watering" => Ok(Self::Watering),
|
||||||
|
_ => Err(ParseEventTypeErr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql for EventType {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
|
||||||
|
Ok(self.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for EventType {
|
||||||
|
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
|
||||||
|
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<Utc>,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
pub fn from_row(row: Row<'_>) -> rusqlite::Result<Self> {
|
||||||
|
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<Utc>,
|
||||||
|
description: String,
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
mod comment;
|
mod comment;
|
||||||
|
mod event;
|
||||||
mod plant;
|
mod plant;
|
||||||
|
|
||||||
use r2d2_sqlite::{rusqlite, SqliteConnectionManager};
|
use r2d2_sqlite::{rusqlite, SqliteConnectionManager};
|
||||||
|
|
|
@ -7,10 +7,11 @@ use r2d2_sqlite::SqliteConnectionManager;
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
use tower_http::compression::CompressionLayer;
|
use tower_http::compression::CompressionLayer;
|
||||||
|
|
||||||
const MIGRATIONS: [&str; 3] = [
|
const MIGRATIONS: [&str; 4] = [
|
||||||
include_str!("migrations/000_initial.sql"),
|
include_str!("migrations/000_initial.sql"),
|
||||||
include_str!("migrations/001_plants.sql"),
|
include_str!("migrations/001_plants.sql"),
|
||||||
include_str!("migrations/002_comments.sql"),
|
include_str!("migrations/002_comments.sql"),
|
||||||
|
include_str!("migrations/003_events.sql"),
|
||||||
];
|
];
|
||||||
const STATIC_FILES: [(&str, &'static str); 1] = [(
|
const STATIC_FILES: [(&str, &'static str); 1] = [(
|
||||||
"htmx_2.0.4.min.js",
|
"htmx_2.0.4.min.js",
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
Loading…
Reference in New Issue