use sqlx::{Transaction, sqlite::Sqlite}; use super::{History, repo::Provider as _, validate}; use crate::{ clock::DateTime, event::{Broadcaster, Event, Sequence, repo::Provider as _}, login::{self, Login, repo::Provider as _}, name::Name, password::{Password, StoredHash}, }; #[must_use = "dropping a user creation attempt is likely a mistake"] pub struct Create<'a> { name: &'a Name, password: &'a Password, created_at: &'a DateTime, } impl<'a> Create<'a> { pub fn begin(name: &'a Name, password: &'a Password, created_at: &'a DateTime) -> Self { Self { name, password, created_at, } } pub fn validate(self) -> Result, Error> { let Self { name, password, created_at, } = self; if !validate::name(name) { return Err(Error::InvalidName(name.clone())); } let password_hash = password.hash()?; Ok(Validated { name, password: password_hash, created_at, }) } } #[must_use = "dropping a user creation attempt is likely a mistake"] pub struct Validated<'a> { name: &'a Name, password: StoredHash, created_at: &'a DateTime, } impl Validated<'_> { pub async fn store( self, tx: &mut Transaction<'_, Sqlite>, ) -> Result + use<>>, sqlx::Error> { let Self { name, password, created_at, } = self; let login = Login { id: login::Id::generate(), name: name.clone(), }; tx.logins().create(&login, &password).await?; let created = tx.sequence().next(created_at).await?; let user = History::begin(&login, created); let events = user.events().filter(Sequence::start_from(created)); tx.users().record_events(events.clone()).await?; Ok(Stored { events: events.map(Event::from), login, }) } } #[must_use = "dropping a user creation attempt is likely a mistake"] pub struct Stored { events: E, login: Login, } impl Stored where E: IntoIterator, { pub fn publish(self, events: &Broadcaster) { let Self { events: user_events, login: _, } = self; events.broadcast(user_events.into_iter().collect::>()); } pub fn login(&self) -> &Login { &self.login } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("invalid user name: {0}")] InvalidName(Name), #[error(transparent)] PasswordHash(#[from] password_hash::Error), }