use sqlx::sqlite::SqlitePool; use super::repo::Provider as _; #[cfg(test)] use super::{validate, Login, Password}; #[cfg(test)] use crate::{ clock::DateTime, event::{repo::Provider as _, Broadcaster, Event}, name::Name, }; pub struct Logins<'a> { db: &'a SqlitePool, #[cfg(test)] events: &'a Broadcaster, } impl<'a> Logins<'a> { #[cfg(not(test))] pub const fn new(db: &'a SqlitePool) -> Self { Self { db } } #[cfg(test)] pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self { Self { db, events } } #[cfg(test)] pub async fn create( &self, name: &Name, password: &Password, created_at: &DateTime, ) -> Result { if !validate::name(name) { return Err(CreateError::InvalidName(name.clone())); } let password_hash = password.hash()?; let mut tx = self.db.begin().await?; let created = tx.sequence().next(created_at).await?; let login = tx.logins().create(name, &password_hash, &created).await?; tx.commit().await?; self.events .broadcast(login.events().map(Event::from).collect::>()); Ok(login.as_created()) } pub async fn recanonicalize(&self) -> Result<(), sqlx::Error> { let mut tx = self.db.begin().await?; tx.logins().recanonicalize().await?; tx.commit().await?; Ok(()) } } #[cfg(test)] #[derive(Debug, thiserror::Error)] pub enum CreateError { #[error("invalid login name: {0}")] InvalidName(Name), #[error(transparent)] Database(#[from] sqlx::Error), #[error(transparent)] PasswordHash(#[from] password_hash::Error), }