feat: rewrite user model using diesel
							parent
							
								
									18a321853a
								
							
						
					
					
						commit
						77995ebec9
					
				| 
						 | 
					@ -579,6 +579,7 @@ checksum = "ccf1bedf64cdb9643204a36dd15b19a6ce8e7aa7f7b105868e9f1fad5ffa7d12"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "diesel_derives",
 | 
					 "diesel_derives",
 | 
				
			||||||
 "libsqlite3-sys",
 | 
					 "libsqlite3-sys",
 | 
				
			||||||
 | 
					 "r2d2",
 | 
				
			||||||
 "time",
 | 
					 "time",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ axum = { version = "0.8.0", features = ["macros"] }
 | 
				
			||||||
axum-extra = { version = "0.10.0", features = ["cookie"] }
 | 
					axum-extra = { version = "0.10.0", features = ["cookie"] }
 | 
				
			||||||
chrono = { version = "0.4.39", features = ["serde"] }
 | 
					chrono = { version = "0.4.39", features = ["serde"] }
 | 
				
			||||||
clap = { version = "4.5.26", features = ["derive", "env"] }
 | 
					clap = { version = "4.5.26", features = ["derive", "env"] }
 | 
				
			||||||
diesel = { version = "2.2.6", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }
 | 
					diesel = { version = "2.2.6", features = ["sqlite", "returning_clauses_for_sqlite_3_35", "r2d2"] }
 | 
				
			||||||
r2d2 = "0.8.10"
 | 
					r2d2 = "0.8.10"
 | 
				
			||||||
r2d2_sqlite = "0.25.0"
 | 
					r2d2_sqlite = "0.25.0"
 | 
				
			||||||
rand = "0.8.5"
 | 
					rand = "0.8.5"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
create table plants (
 | 
					create table plants (
 | 
				
			||||||
    id integer primary key not null,
 | 
					    id bigint primary key not null,
 | 
				
			||||||
    name text not null,
 | 
					    name text not null,
 | 
				
			||||||
    species text not null,
 | 
					    species text not null,
 | 
				
			||||||
    description text not null
 | 
					    description text not null
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
create table comments (
 | 
					create table comments (
 | 
				
			||||||
    id integer primary key not null,
 | 
					    id bigint primary key not null,
 | 
				
			||||||
    plant_id integer references plants (id),
 | 
					    plant_id bigint not null
 | 
				
			||||||
 | 
					        references plants (id)
 | 
				
			||||||
 | 
					        on delete cascade,
 | 
				
			||||||
    comment text not null
 | 
					    comment text not null
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
create table events (
 | 
					create table events (
 | 
				
			||||||
    id integer primary key not null,
 | 
					    id bigint primary key not null,
 | 
				
			||||||
    plant_id integer not null
 | 
					    plant_id bigint not null
 | 
				
			||||||
        references plants (id)
 | 
					        references plants (id)
 | 
				
			||||||
        on delete cascade,
 | 
					        on delete cascade,
 | 
				
			||||||
    event_type text not null,
 | 
					    event_type text not null,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
create table users (
 | 
					create table users (
 | 
				
			||||||
    id integer primary key not null,
 | 
					    id bigint primary key not null,
 | 
				
			||||||
    username text unique not null,
 | 
					    username text unique not null,
 | 
				
			||||||
    password_hash text not null,
 | 
					    password_hash text not null,
 | 
				
			||||||
    admin boolean not null
 | 
					    admin boolean not null
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
create table sessions (
 | 
					create table sessions (
 | 
				
			||||||
    id integer primary key not null,
 | 
					    id bigint primary key not null,
 | 
				
			||||||
    user_id integer not null
 | 
					    user_id bigint not null
 | 
				
			||||||
        references users (id)
 | 
					        references users (id)
 | 
				
			||||||
        on delete cascade,
 | 
					        on delete cascade,
 | 
				
			||||||
    unique (id, user_id)
 | 
					    unique (id, user_id)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
mod comment;
 | 
					mod comment;
 | 
				
			||||||
mod event;
 | 
					mod event;
 | 
				
			||||||
 | 
					mod models;
 | 
				
			||||||
mod plant;
 | 
					mod plant;
 | 
				
			||||||
mod schema;
 | 
					mod schema;
 | 
				
			||||||
mod session;
 | 
					mod session;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					mod user;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					use argon2::{
 | 
				
			||||||
 | 
					    password_hash::{rand_core::OsRng, SaltString},
 | 
				
			||||||
 | 
					    Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use diesel::prelude::*;
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::db::schema::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize, Deserialize, Clone, Queryable, Selectable)]
 | 
				
			||||||
 | 
					#[diesel(table_name = users)]
 | 
				
			||||||
 | 
					#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
 | 
				
			||||||
 | 
					pub struct User {
 | 
				
			||||||
 | 
					    pub id: i64,
 | 
				
			||||||
 | 
					    pub username: String,
 | 
				
			||||||
 | 
					    pub password_hash: String,
 | 
				
			||||||
 | 
					    pub admin: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Deserialize, Insertable)]
 | 
				
			||||||
 | 
					#[diesel(table_name = users)]
 | 
				
			||||||
 | 
					#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
 | 
				
			||||||
 | 
					pub struct NewUser {
 | 
				
			||||||
 | 
					    pub username: String,
 | 
				
			||||||
 | 
					    pub password_hash: String,
 | 
				
			||||||
 | 
					    pub admin: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn hash_password(password: impl AsRef<str>) -> String {
 | 
				
			||||||
 | 
					    let salt = SaltString::generate(&mut OsRng);
 | 
				
			||||||
 | 
					    let argon2 = Argon2::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    argon2
 | 
				
			||||||
 | 
					        .hash_password(password.as_ref().as_bytes(), &salt)
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					        .to_string()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl NewUser {
 | 
				
			||||||
 | 
					    pub fn new(username: String, password: String, admin: bool) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            username,
 | 
				
			||||||
 | 
					            password_hash: hash_password(&password),
 | 
				
			||||||
 | 
					            admin,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn insert(self, conn: &mut SqliteConnection) -> QueryResult<User> {
 | 
				
			||||||
 | 
					        diesel::insert_into(users::table)
 | 
				
			||||||
 | 
					            .values(self)
 | 
				
			||||||
 | 
					            .returning(User::as_returning())
 | 
				
			||||||
 | 
					            .get_result(conn)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl User {
 | 
				
			||||||
 | 
					    pub fn by_username(
 | 
				
			||||||
 | 
					        conn: &mut SqliteConnection,
 | 
				
			||||||
 | 
					        username: impl AsRef<str>,
 | 
				
			||||||
 | 
					    ) -> QueryResult<Option<Self>> {
 | 
				
			||||||
 | 
					        users::dsl::users
 | 
				
			||||||
 | 
					            .select(User::as_select())
 | 
				
			||||||
 | 
					            .filter(users::username.eq(username.as_ref()))
 | 
				
			||||||
 | 
					            .first(conn)
 | 
				
			||||||
 | 
					            .optional()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn verify_password(&self, password: impl AsRef<str>) -> bool {
 | 
				
			||||||
 | 
					        let password_hash = PasswordHash::new(&self.password_hash).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Argon2::default()
 | 
				
			||||||
 | 
					            .verify_password(password.as_ref().as_bytes(), &password_hash)
 | 
				
			||||||
 | 
					            .is_ok()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,16 +2,16 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
diesel::table! {
 | 
					diesel::table! {
 | 
				
			||||||
    comments (id) {
 | 
					    comments (id) {
 | 
				
			||||||
        id -> Integer,
 | 
					        id -> BigInt,
 | 
				
			||||||
        plant_id -> Nullable<Integer>,
 | 
					        plant_id -> BigInt,
 | 
				
			||||||
        comment -> Text,
 | 
					        comment -> Text,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
diesel::table! {
 | 
					diesel::table! {
 | 
				
			||||||
    events (id) {
 | 
					    events (id) {
 | 
				
			||||||
        id -> Integer,
 | 
					        id -> BigInt,
 | 
				
			||||||
        plant_id -> Integer,
 | 
					        plant_id -> BigInt,
 | 
				
			||||||
        event_type -> Text,
 | 
					        event_type -> Text,
 | 
				
			||||||
        date -> Text,
 | 
					        date -> Text,
 | 
				
			||||||
        description -> Text,
 | 
					        description -> Text,
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,7 @@ diesel::table! {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
diesel::table! {
 | 
					diesel::table! {
 | 
				
			||||||
    plants (id) {
 | 
					    plants (id) {
 | 
				
			||||||
        id -> Integer,
 | 
					        id -> BigInt,
 | 
				
			||||||
        name -> Text,
 | 
					        name -> Text,
 | 
				
			||||||
        species -> Text,
 | 
					        species -> Text,
 | 
				
			||||||
        description -> Text,
 | 
					        description -> Text,
 | 
				
			||||||
| 
						 | 
					@ -29,14 +29,14 @@ diesel::table! {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
diesel::table! {
 | 
					diesel::table! {
 | 
				
			||||||
    sessions (id) {
 | 
					    sessions (id) {
 | 
				
			||||||
        id -> Integer,
 | 
					        id -> BigInt,
 | 
				
			||||||
        user_id -> Integer,
 | 
					        user_id -> BigInt,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
diesel::table! {
 | 
					diesel::table! {
 | 
				
			||||||
    users (id) {
 | 
					    users (id) {
 | 
				
			||||||
        id -> Integer,
 | 
					        id -> BigInt,
 | 
				
			||||||
        username -> Text,
 | 
					        username -> Text,
 | 
				
			||||||
        password_hash -> Text,
 | 
					        password_hash -> Text,
 | 
				
			||||||
        admin -> Bool,
 | 
					        admin -> Bool,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue