feat: fully replace database operations with diesel
parent
d0ecb9357a
commit
c7dc68926b
|
@ -17,18 +17,6 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
|
@ -341,10 +329,8 @@ dependencies = [
|
|||
"chrono",
|
||||
"clap",
|
||||
"diesel",
|
||||
"r2d2",
|
||||
"r2d2_sqlite",
|
||||
"diesel_migrations",
|
||||
"rand",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"tera",
|
||||
"tokio",
|
||||
|
@ -597,6 +583,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_migrations"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
"migrations_internals",
|
||||
"migrations_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_table_macro_syntax"
|
||||
version = "0.2.0"
|
||||
|
@ -638,16 +635,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
|
@ -766,21 +757,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
|
@ -929,6 +908,16 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
|
@ -975,7 +964,6 @@ version = "0.30.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
@ -1008,6 +996,27 @@ version = "2.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "migrations_internals"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "migrations_macros"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd"
|
||||
dependencies = [
|
||||
"migrations_internals",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
|
@ -1284,17 +1293,6 @@ dependencies = [
|
|||
"scheduled-thread-pool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r2d2_sqlite"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb14dba8247a6a15b7fdbc7d389e2e6f03ee9f184f87117706d509c092dfe846"
|
||||
dependencies = [
|
||||
"r2d2",
|
||||
"rusqlite",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
@ -1363,21 +1361,6 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.32.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"chrono",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
|
@ -1462,6 +1445,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
@ -1695,6 +1687,40 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
|
@ -1888,16 +1914,6 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
@ -2108,6 +2124,15 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
|
|
|
@ -14,12 +14,10 @@ axum-extra = { version = "0.10.0", features = ["cookie"] }
|
|||
chrono = { version = "0.4.39", features = ["serde"] }
|
||||
clap = { version = "4.5.26", features = ["derive", "env"] }
|
||||
diesel = { version = "2.2.6", features = ["sqlite", "returning_clauses_for_sqlite_3_35", "r2d2", "chrono"] }
|
||||
r2d2 = "0.8.10"
|
||||
r2d2_sqlite = "0.25.0"
|
||||
diesel_migrations = { version = "2.2.0", features = ["sqlite"] }
|
||||
rand = "0.8.5"
|
||||
# this dependency is needed soly because the r2d2_sqlite crate doesn't export
|
||||
# the 'chrono' feature flag
|
||||
rusqlite = { version = "0.32.1", features = ["chrono", "bundled"] }
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
tera = "1.20.0"
|
||||
tokio = { version = "1.42.0", features = ["full"] }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
create table plants (
|
||||
id bigint primary key not null,
|
||||
id integer primary key not null,
|
||||
name text not null,
|
||||
species text not null,
|
||||
description text not null
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
drop table comments;
|
|
@ -1,7 +0,0 @@
|
|||
create table comments (
|
||||
id bigint primary key not null,
|
||||
plant_id bigint not null
|
||||
references plants (id)
|
||||
on delete cascade,
|
||||
comment text not null
|
||||
);
|
|
@ -1,6 +1,6 @@
|
|||
create table events (
|
||||
id bigint primary key not null,
|
||||
plant_id bigint not null
|
||||
id integer primary key not null,
|
||||
plant_id integer not null
|
||||
references plants (id)
|
||||
on delete cascade,
|
||||
event_type text not null,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
create table users (
|
||||
id bigint primary key not null,
|
||||
id integer primary key not null,
|
||||
username text unique not null,
|
||||
password_hash text not null,
|
||||
admin boolean not null
|
||||
|
@ -7,7 +7,7 @@ create table users (
|
|||
|
||||
create table sessions (
|
||||
id bigint primary key not null,
|
||||
user_id bigint not null
|
||||
user_id integer not null
|
||||
references users (id)
|
||||
on delete cascade,
|
||||
unique (id, user_id)
|
||||
|
|
|
@ -1,32 +1,37 @@
|
|||
mod event;
|
||||
mod models;
|
||||
mod plant;
|
||||
mod schema;
|
||||
mod session;
|
||||
mod user;
|
||||
|
||||
use r2d2_sqlite::{rusqlite, SqliteConnectionManager};
|
||||
use diesel::{
|
||||
prelude::*,
|
||||
r2d2::{ConnectionManager, Pool, PooledConnection},
|
||||
SqliteConnection,
|
||||
};
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
|
||||
use std::{error::Error, fmt};
|
||||
use std::{error::Error, fmt, path::Path};
|
||||
|
||||
pub use event::{Event, EventType, NewEvent, EVENT_TYPES};
|
||||
pub use plant::{NewPlant, Plant};
|
||||
pub use session::Session;
|
||||
pub use user::{NewUser, User};
|
||||
pub use models::event::{Event, EventType, NewEvent, EVENT_TYPES};
|
||||
pub use models::plant::{NewPlant, Plant};
|
||||
pub use models::session::Session;
|
||||
pub use models::user::{NewUser, User};
|
||||
|
||||
pub type DbPool = r2d2::Pool<SqliteConnectionManager>;
|
||||
pub type DbPool = Pool<ConnectionManager<SqliteConnection>>;
|
||||
pub type DbConn = PooledConnection<ConnectionManager<SqliteConnection>>;
|
||||
pub type DbResult<T> = Result<T, DbError>;
|
||||
|
||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DbError {
|
||||
Pool(r2d2::Error),
|
||||
Db(rusqlite::Error),
|
||||
Pool(diesel::r2d2::PoolError),
|
||||
Db(diesel::result::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for DbError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Pool(_) => write!(f, "failed to acquire connection from pool"),
|
||||
Self::Db(_) => write!(f, "error while accessing the database"),
|
||||
Self::Db(_) => write!(f, "error while executing query"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,46 +45,25 @@ impl Error for DbError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<r2d2::Error> for DbError {
|
||||
fn from(value: r2d2::Error) -> Self {
|
||||
impl From<diesel::r2d2::PoolError> for DbError {
|
||||
fn from(value: diesel::r2d2::PoolError) -> Self {
|
||||
Self::Pool(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rusqlite::Error> for DbError {
|
||||
fn from(value: rusqlite::Error) -> Self {
|
||||
impl From<diesel::result::Error> for DbError {
|
||||
fn from(value: diesel::result::Error) -> Self {
|
||||
Self::Db(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_migrations(pool: &DbPool, migrations: &[&str]) -> rusqlite::Result<()> {
|
||||
let mut conn = pool.get().unwrap();
|
||||
pub fn initialize_db(path: impl AsRef<Path>, run_migrations: bool) -> Result<DbPool, DbError> {
|
||||
let manager = ConnectionManager::<SqliteConnection>::new(path.as_ref().to_string_lossy());
|
||||
let pool = Pool::new(manager)?;
|
||||
|
||||
// If the migration version query fails, we assume it's because the table isn't there yet so we
|
||||
// try to run the first migration
|
||||
let mut next_version = conn
|
||||
.query_row("select max(version) from migration_version", (), |row| {
|
||||
row.get::<_, usize>(0).map(|n| n + 1)
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
while next_version < migrations.len() {
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
tx.execute_batch(migrations[next_version])?;
|
||||
|
||||
let cur_time = chrono::Local::now().timestamp();
|
||||
tx.execute(
|
||||
"insert into migration_version values ($1, $2)",
|
||||
(next_version, cur_time),
|
||||
)?;
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
tracing::info!("Applied migration {next_version}");
|
||||
|
||||
next_version += 1;
|
||||
if run_migrations {
|
||||
pool.get()?.run_pending_migrations(MIGRATIONS).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(pool)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use crate::db::schema::*;
|
||||
pub const EVENT_TYPES: [&str; 1] = ["Watering"];
|
||||
|
||||
use crate::db::{schema::*, DbPool, DbResult};
|
||||
|
||||
#[derive(FromSqlRow, Debug, AsExpression, Serialize, Deserialize)]
|
||||
#[diesel(sql_type = Text)]
|
||||
|
@ -72,10 +74,11 @@ impl ToSql<Text, Sqlite> for EventType {
|
|||
|
||||
#[derive(Serialize, Queryable, Selectable)]
|
||||
#[diesel(table_name = events)]
|
||||
#[diesel(belongs_to(super::plant::Plant))]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct Event {
|
||||
id: i64,
|
||||
plant_id: i64,
|
||||
id: i32,
|
||||
plant_id: i32,
|
||||
event_type: EventType,
|
||||
date: NaiveDate,
|
||||
description: String,
|
||||
|
@ -85,17 +88,26 @@ pub struct Event {
|
|||
#[diesel(table_name = events)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct NewEvent {
|
||||
plant_id: i64,
|
||||
plant_id: i32,
|
||||
event_type: EventType,
|
||||
date: NaiveDate,
|
||||
description: String,
|
||||
}
|
||||
|
||||
impl NewEvent {
|
||||
pub fn insert(self, conn: &mut SqliteConnection) -> QueryResult<Event> {
|
||||
diesel::insert_into(events::table)
|
||||
.values(self)
|
||||
.returning(Event::as_returning())
|
||||
.get_result(conn)
|
||||
impl Event {
|
||||
pub fn for_plant(pool: &DbPool, plant_id: i32) -> DbResult<Vec<Self>> {
|
||||
Ok(events::dsl::events
|
||||
.select(Self::as_select())
|
||||
.filter(events::dsl::plant_id.eq(plant_id))
|
||||
.load(&mut pool.get()?)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl NewEvent {
|
||||
pub fn insert(self, pool: &DbPool) -> DbResult<Event> {
|
||||
Ok(diesel::insert_into(events::table)
|
||||
.values(self)
|
||||
.returning(Event::as_returning())
|
||||
.get_result(&mut pool.get()?)?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mod event;
|
||||
mod plant;
|
||||
mod session;
|
||||
mod user;
|
||||
pub mod event;
|
||||
pub mod plant;
|
||||
pub mod session;
|
||||
pub mod user;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::db::schema::*;
|
||||
use crate::db::{schema::*, DbPool, DbResult};
|
||||
|
||||
#[derive(Serialize, Queryable, Selectable)]
|
||||
#[diesel(table_name = plants)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct Plant {
|
||||
id: i64,
|
||||
name: String,
|
||||
species: String,
|
||||
description: String,
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub species: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Insertable)]
|
||||
|
@ -23,19 +23,26 @@ pub struct NewPlant {
|
|||
}
|
||||
|
||||
impl NewPlant {
|
||||
pub fn insert(self, conn: &mut SqliteConnection) -> QueryResult<Plant> {
|
||||
self.insert_into(plants::table)
|
||||
pub fn insert(self, pool: &DbPool) -> DbResult<Plant> {
|
||||
Ok(self
|
||||
.insert_into(plants::table)
|
||||
.returning(Plant::as_returning())
|
||||
.get_result(conn)
|
||||
.get_result(&mut pool.get()?)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Plant {
|
||||
pub fn by_id(conn: &mut SqliteConnection, id: i64) -> QueryResult<Option<Self>> {
|
||||
plants::table
|
||||
pub fn all(pool: &DbPool) -> DbResult<Vec<Self>> {
|
||||
Ok(plants::dsl::plants
|
||||
.select(Self::as_select())
|
||||
.load(&mut pool.get()?)?)
|
||||
}
|
||||
|
||||
pub fn by_id(pool: &DbPool, id: i32) -> DbResult<Option<Self>> {
|
||||
Ok(plants::table
|
||||
.find(id)
|
||||
.select(Self::as_select())
|
||||
.first(conn)
|
||||
.optional()
|
||||
.first(&mut pool.get()?)
|
||||
.optional()?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use diesel::prelude::*;
|
|||
use rand::Rng;
|
||||
|
||||
use super::user::User;
|
||||
use crate::db::schema::*;
|
||||
use crate::db::{schema::*, DbPool, DbResult};
|
||||
|
||||
#[derive(Clone, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(super::user::User))]
|
||||
|
@ -10,28 +10,25 @@ use crate::db::schema::*;
|
|||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct Session {
|
||||
pub id: i64,
|
||||
pub user_id: i64,
|
||||
pub user_id: i32,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new_for_user(conn: &mut SqliteConnection, user_id: i64) -> QueryResult<Self> {
|
||||
pub fn new_for_user(pool: &DbPool, user_id: i32) -> DbResult<Self> {
|
||||
let id: i64 = rand::thread_rng().gen();
|
||||
|
||||
Self { id, user_id }
|
||||
Ok(Self { id, user_id }
|
||||
.insert_into(sessions::table)
|
||||
.returning(Self::as_returning())
|
||||
.get_result(conn)
|
||||
.get_result(&mut pool.get()?)?)
|
||||
}
|
||||
|
||||
pub fn user_from_id(
|
||||
conn: &mut SqliteConnection,
|
||||
id: i64,
|
||||
) -> QueryResult<Option<super::user::User>> {
|
||||
sessions::dsl::sessions
|
||||
pub fn user_from_id(pool: &DbPool, id: i64) -> DbResult<Option<super::user::User>> {
|
||||
Ok(sessions::dsl::sessions
|
||||
.inner_join(users::table)
|
||||
.filter(sessions::id.eq(id))
|
||||
.select(User::as_select())
|
||||
.get_result(conn)
|
||||
.optional()
|
||||
.get_result(&mut pool.get()?)
|
||||
.optional()?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@ use argon2::{
|
|||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::db::schema::*;
|
||||
use crate::db::{schema::*, DbConn, DbPool, DbResult};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Queryable, Selectable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct User {
|
||||
pub id: i64,
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub password_hash: String,
|
||||
pub admin: bool,
|
||||
|
@ -45,24 +45,27 @@ impl NewUser {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert(self, conn: &mut SqliteConnection) -> QueryResult<User> {
|
||||
diesel::insert_into(users::table)
|
||||
pub fn insert(self, pool: &DbPool) -> DbResult<User> {
|
||||
Ok(diesel::insert_into(users::table)
|
||||
.values(self)
|
||||
.returning(User::as_returning())
|
||||
.get_result(conn)
|
||||
.get_result(&mut pool.get()?)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn by_username(
|
||||
conn: &mut SqliteConnection,
|
||||
username: impl AsRef<str>,
|
||||
) -> QueryResult<Option<Self>> {
|
||||
users::dsl::users
|
||||
pub fn by_username(pool: &DbPool, username: impl AsRef<str>) -> DbResult<Option<Self>> {
|
||||
Ok(users::dsl::users
|
||||
.select(User::as_select())
|
||||
.filter(users::username.eq(username.as_ref()))
|
||||
.first(conn)
|
||||
.optional()
|
||||
.first(&mut pool.get()?)
|
||||
.optional()?)
|
||||
}
|
||||
|
||||
pub fn all(pool: &DbPool) -> DbResult<Vec<Self>> {
|
||||
Ok(users::dsl::users
|
||||
.select(User::as_select())
|
||||
.load(&mut pool.get()?)?)
|
||||
}
|
||||
|
||||
pub fn verify_password(&self, password: impl AsRef<str>) -> bool {
|
||||
|
@ -72,4 +75,11 @@ impl User {
|
|||
.verify_password(password.as_ref().as_bytes(), &password_hash)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn remove_by_username(pool: &DbPool, username: impl AsRef<str>) -> DbResult<bool> {
|
||||
Ok(diesel::delete(users::table)
|
||||
.filter(users::username.eq(username.as_ref()))
|
||||
.execute(&mut pool.get()?)
|
||||
.map(|n| n > 0)?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
comments (id) {
|
||||
id -> BigInt,
|
||||
plant_id -> BigInt,
|
||||
comment -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
events (id) {
|
||||
id -> BigInt,
|
||||
plant_id -> BigInt,
|
||||
id -> Integer,
|
||||
plant_id -> Integer,
|
||||
event_type -> Text,
|
||||
date -> Date,
|
||||
description -> Text,
|
||||
|
@ -20,7 +12,7 @@ diesel::table! {
|
|||
|
||||
diesel::table! {
|
||||
plants (id) {
|
||||
id -> BigInt,
|
||||
id -> Integer,
|
||||
name -> Text,
|
||||
species -> Text,
|
||||
description -> Text,
|
||||
|
@ -30,21 +22,20 @@ diesel::table! {
|
|||
diesel::table! {
|
||||
sessions (id) {
|
||||
id -> BigInt,
|
||||
user_id -> BigInt,
|
||||
user_id -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
users (id) {
|
||||
id -> BigInt,
|
||||
id -> Integer,
|
||||
username -> Text,
|
||||
password_hash -> Text,
|
||||
admin -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(comments -> plants (plant_id));
|
||||
diesel::joinable!(events -> plants (plant_id));
|
||||
diesel::joinable!(sessions -> users (user_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(comments, events, plants, sessions, users,);
|
||||
diesel::allow_tables_to_appear_in_same_query!(events, plants, sessions, users,);
|
||||
|
|
27
src/main.rs
27
src/main.rs
|
@ -5,18 +5,13 @@ mod server;
|
|||
use std::{fs, path::Path, sync::Arc};
|
||||
|
||||
use clap::Parser;
|
||||
use cli::UserCmd;
|
||||
use db::DbError;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations};
|
||||
use tera::Tera;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
||||
const MIGRATIONS: [&str; 4] = [
|
||||
include_str!("migrations/000_initial.sql"),
|
||||
include_str!("migrations/001_plants.sql"),
|
||||
include_str!("migrations/003_events.sql"),
|
||||
include_str!("migrations/004_auth.sql"),
|
||||
];
|
||||
use cli::UserCmd;
|
||||
use db::DbError;
|
||||
|
||||
const DB_FILENAME: &str = "db.sqlite3";
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -26,8 +21,7 @@ pub struct Context {
|
|||
}
|
||||
|
||||
fn run_user_cli(data_dir: impl AsRef<Path>, cmd: UserCmd) -> Result<(), DbError> {
|
||||
let manager = SqliteConnectionManager::file(data_dir.as_ref().join(DB_FILENAME));
|
||||
let pool = r2d2::Pool::new(manager)?;
|
||||
let pool = db::initialize_db(data_dir.as_ref().join(DB_FILENAME), false)?;
|
||||
|
||||
match cmd.cmd {
|
||||
cli::UserSubCmd::List => {
|
||||
|
@ -44,12 +38,7 @@ fn run_user_cli(data_dir: impl AsRef<Path>, cmd: UserCmd) -> Result<(), DbError>
|
|||
password,
|
||||
admin,
|
||||
} => {
|
||||
db::NewUser {
|
||||
username,
|
||||
password,
|
||||
admin,
|
||||
}
|
||||
.insert(&pool)?;
|
||||
db::NewUser::new(username, password, admin).insert(&pool)?;
|
||||
}
|
||||
cli::UserSubCmd::Remove { username } => {
|
||||
db::User::remove_by_username(&pool, &username)?;
|
||||
|
@ -71,9 +60,7 @@ async fn main() {
|
|||
fs::create_dir_all(&args.data_dir).unwrap();
|
||||
}
|
||||
|
||||
let manager = SqliteConnectionManager::file(args.data_dir.join(DB_FILENAME));
|
||||
let pool = r2d2::Pool::new(manager).unwrap();
|
||||
db::run_migrations(&pool, &MIGRATIONS).unwrap();
|
||||
let pool = db::initialize_db(args.data_dir.join(DB_FILENAME), true).unwrap();
|
||||
|
||||
let tera =
|
||||
Tera::new(&format!("{}/**/*", args.templates_dir.to_string_lossy())).unwrap();
|
||||
|
|
|
@ -7,7 +7,7 @@ use axum::{
|
|||
};
|
||||
use tera::Context;
|
||||
|
||||
use crate::db::{self, DbError, Plant};
|
||||
use crate::db::{self, DbError, Event, Plant};
|
||||
|
||||
use super::error::AppError;
|
||||
|
||||
|
@ -20,13 +20,13 @@ pub fn app() -> axum::Router<crate::Context> {
|
|||
async fn get_plant_page(
|
||||
State(ctx): State<crate::Context>,
|
||||
headers: HeaderMap,
|
||||
Path(plant_id): Path<i64>,
|
||||
Path(plant_id): Path<i32>,
|
||||
) -> super::Result<Html<String>> {
|
||||
let res = tokio::task::spawn_blocking(move || {
|
||||
let plant = Plant::by_id(&ctx.pool, plant_id)?;
|
||||
|
||||
if let Some(plant) = plant {
|
||||
let events = plant.events(&ctx.pool)?;
|
||||
let events = Event::for_plant(&ctx.pool, plant.id)?;
|
||||
|
||||
Ok::<_, DbError>(Some((plant, events)))
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue