feat: rewrite user model using diesel

image-uploads
Jef Roosens 2025-01-13 13:43:09 +01:00
parent 18a321853a
commit 77995ebec9
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
10 changed files with 97 additions and 17 deletions

1
Cargo.lock generated
View File

@ -579,6 +579,7 @@ checksum = "ccf1bedf64cdb9643204a36dd15b19a6ce8e7aa7f7b105868e9f1fad5ffa7d12"
dependencies = [ dependencies = [
"diesel_derives", "diesel_derives",
"libsqlite3-sys", "libsqlite3-sys",
"r2d2",
"time", "time",
] ]

View File

@ -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"

View File

@ -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

View File

@ -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
); );

View File

@ -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,

View File

@ -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)

View File

@ -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;

View File

@ -0,0 +1 @@
mod user;

View File

@ -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()
}
}

View File

@ -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,