feat: add user and session models

This commit is contained in:
Jef Roosens 2025-02-23 11:20:25 +01:00
parent eb0b16ea39
commit 67ad8c2b64
No known key found for this signature in database
GPG key ID: 21FD3D77D56BAF49
8 changed files with 328 additions and 1 deletions

View file

@ -0,0 +1,2 @@
pub mod session;
pub mod user;

34
src/db/models/session.rs Normal file
View file

@ -0,0 +1,34 @@
use diesel::prelude::*;
use rand::Rng;
use super::user::User;
use crate::db::{schema::*, DbPool, DbResult};
#[derive(Clone, Queryable, Selectable, Insertable, Associations)]
#[diesel(belongs_to(super::user::User))]
#[diesel(table_name = sessions)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct Session {
pub id: i64,
pub user_id: i64,
}
impl Session {
pub fn new_for_user(pool: &DbPool, user_id: i64) -> DbResult<Self> {
let id: i64 = rand::thread_rng().gen();
Ok(Self { id, user_id }
.insert_into(sessions::table)
.returning(Self::as_returning())
.get_result(&mut pool.get()?)?)
}
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(&mut pool.get()?)
.optional()?)
}
}

67
src/db/models/user.rs Normal file
View file

@ -0,0 +1,67 @@
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use diesel::prelude::*;
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use crate::db::{schema::*, 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 username: String,
pub password_hash: String,
}
#[derive(Deserialize, Insertable)]
#[diesel(table_name = users)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct NewUser {
pub username: String,
pub password_hash: String,
}
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) -> Self {
Self {
username,
password_hash: hash_password(&password),
}
}
pub fn insert(self, pool: &DbPool) -> DbResult<User> {
Ok(diesel::insert_into(users::table)
.values(self)
.returning(User::as_returning())
.get_result(&mut pool.get()?)?)
}
}
impl User {
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(&mut pool.get()?)
.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

@ -1,2 +1,23 @@
// @generated automatically by Diesel CLI.
diesel::table! {
sessions (id) {
id -> BigInt,
user_id -> BigInt,
}
}
diesel::table! {
users (id) {
id -> BigInt,
username -> Text,
password_hash -> Text,
}
}
diesel::joinable!(sessions -> users (user_id));
diesel::allow_tables_to_appear_in_same_query!(
sessions,
users,
);