🏡 index : ~doyle/jogre.git

mod rocksdb;

use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use axum::async_trait;
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Serialize, Deserialize)]
pub struct User {
    id: Uuid,
    pub username: String,
    password: String,
}

impl User {
    pub fn new(username: String, password: &str) -> Self {
        let password = Argon2::default()
            .hash_password(password.as_bytes(), &SaltString::generate(&mut OsRng))
            .unwrap()
            .to_string();

        Self {
            id: Uuid::new_v4(),
            username,
            password,
        }
    }

    pub fn verify_password(&self, password: &str) -> bool {
        let parsed_hash = PasswordHash::new(&self.password).unwrap();
        Argon2::default()
            .verify_password(password.as_bytes(), &parsed_hash)
            .is_ok()
    }
}

#[async_trait]
pub trait UserProvider {
    type Error;

    async fn has_any_users(&self) -> Result<bool, Self::Error>;

    async fn create_user(&self, user: User) -> Result<(), Self::Error>;

    async fn get_by_username(&self, username: &str) -> Result<Option<User>, Self::Error>;
}

#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum StoreConfig {
    #[serde(rename = "rocksdb")]
    RocksDb(rocksdb::Config),
}

pub enum Store {
    RocksDb(rocksdb::RocksDb),
}

impl Store {
    pub fn from_config(config: StoreConfig) -> Self {
        match config {
            StoreConfig::RocksDb(config) => Self::RocksDb(rocksdb::RocksDb::new(config)),
        }
    }
}

#[async_trait]
impl UserProvider for Store {
    type Error = rocksdb::Error;

    async fn has_any_users(&self) -> Result<bool, Self::Error> {
        match self {
            Store::RocksDb(db) => db.has_any_users().await,
        }
    }

    async fn create_user(&self, user: User) -> Result<(), Self::Error> {
        match self {
            Store::RocksDb(db) => db.create_user(user).await,
        }
    }

    async fn get_by_username(&self, username: &str) -> Result<Option<User>, Self::Error> {
        match self {
            Store::RocksDb(db) => db.get_by_username(username).await,
        }
    }
}