feat: Start dummy in-memory database

This commit is contained in:
selfhoster selfhoster 2026-09-01 19:06:33 +02:00
commit bdb5008914
9 changed files with 181 additions and 3 deletions

36
src/db/common.rs Normal file
View file

@ -0,0 +1,36 @@
use tokio::sync::RwLock;
use std::sync::Arc;
use crate::db::{DatabaseInterface, UserRef};
#[derive(Clone, Debug)]
pub struct Database<D: DatabaseInterface> {
pub inner: Arc<RwLock<D>>,
}
impl<D: DatabaseInterface> Database<D> {
pub fn new(db: D) -> Self {
Self {
inner: Arc::new(RwLock::new(db)),
}
}
}
impl<D: DatabaseInterface> Database<D> {
/// Return false if the user doesn't exist, or the password is wrong.
///
/// TODO: should we return something else when the account doesn't exist?
/// or is it a feature to behave in the same way?
///
/// TODO: we may want to make sure the method runs in constant time
/// to avoid leaking information about existing users...
/// or maybe we do not care.
async fn check_password(&self, user: &UserRef, password: &str) -> bool {
let Some(user) = self.get_user(user).await else {
return false;
};
user.password == password
}
}

14
src/db/error.rs Normal file
View file

@ -0,0 +1,14 @@
use std::fmt;
use crate::db::UserRef;
#[derive(Debug)]
pub struct UserAlreadyExists(pub UserRef);
impl fmt::Display for UserAlreadyExists {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "User already exists: {}", self.0)
}
}
impl std::error::Error for UserAlreadyExists {}

5
src/db/group.rs Normal file
View file

@ -0,0 +1,5 @@
#[derive(Clone, Debug)]
pub struct Group {
name: String,
domain: String,
}

17
src/db/interface.rs Normal file
View file

@ -0,0 +1,17 @@
use crate::db::error::*;
use crate::db::{Database, User, UserRef};
impl<D: DatabaseInterface> DatabaseInterface for Database<D> {
async fn get_user(&self, user: &UserRef) -> Option<User> {
self.inner.read().await.get_user(user).await
}
async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists> {
self.inner.write().await.create_user(user).await
}
}
pub trait DatabaseInterface {
async fn get_user(&self, user: &UserRef) -> Option<User>;
async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists>;
}

38
src/db/memory.rs Normal file
View file

@ -0,0 +1,38 @@
use std::future::{Future, ready};
use crate::db::error::*;
use crate::db::{Database, DatabaseInterface, Group, User, UserRef};
#[derive(Clone, Debug, Default)]
pub struct MemoryDatabase {
pub users: Vec<User>,
pub groups: Vec<Group>,
}
impl MemoryDatabase {
pub fn new() -> Database<Self> {
Database::new(Self::default())
}
}
impl DatabaseInterface for MemoryDatabase {
fn get_user(&self, req_user: &UserRef) -> impl Future<Output = Option<User>> {
for user in &self.users {
if user.username == req_user.username && user.domain == req_user.domain {
return ready(Some(user.clone()));
}
}
ready(None)
}
async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists> {
let user_ref = user.user_ref();
if self.get_user(&user_ref).await.is_some() {
return Err(UserAlreadyExists(user_ref));
}
self.users.push(user);
Ok(())
}
}

11
src/db/mod.rs Normal file
View file

@ -0,0 +1,11 @@
mod common;
pub use common::Database;
pub mod error;
mod group;
pub use group::Group;
mod interface;
pub use interface::DatabaseInterface;
mod memory;
pub use memory::MemoryDatabase;
mod user;
pub use user::{User, UserRef};

37
src/db/user.rs Normal file
View file

@ -0,0 +1,37 @@
use std::fmt;
#[derive(Clone, Debug)]
pub struct UserRef {
pub username: String,
pub domain: String,
}
impl fmt::Display for UserRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}@{}", self.username, self.domain)
}
}
#[derive(Clone, Debug)]
pub struct User {
pub username: String,
pub domain: String,
pub password: String,
pub mail: String,
// recovery_mail: String,
}
impl User {
pub fn user_ref(&self) -> UserRef {
UserRef {
username: self.username.clone(),
domain: self.domain.clone(),
}
}
}
impl fmt::Display for User {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}@{}", self.username, self.domain)
}
}