diff options
Diffstat (limited to 'src/invite/app.rs')
| -rw-r--r-- | src/invite/app.rs | 107 |
1 files changed, 55 insertions, 52 deletions
diff --git a/src/invite/app.rs b/src/invite/app.rs index 6f58d0a..54272bc 100644 --- a/src/invite/app.rs +++ b/src/invite/app.rs @@ -4,17 +4,14 @@ use sqlx::sqlite::SqlitePool; use super::{Id, Invite, Summary, repo::Provider as _}; use crate::{ clock::DateTime, - db::{Duplicate as _, NotFound as _}, + db::{self, NotFound as _}, + error::failed::{ErrorExt as _, Failed, ResultExt as _}, event::{Broadcaster, repo::Provider as _}, login::Login, - name::{self, Name}, + name::Name, password::Password, token::{Secret, Token, repo::Provider as _}, - user::{ - self, - create::{self, Create}, - repo::{LoadError, Provider as _}, - }, + user::{self, create, create::Create, repo::Provider as _}, }; pub struct Invites { @@ -27,20 +24,30 @@ impl Invites { Self { db, events } } - pub async fn issue(&self, issuer: &Login, issued_at: &DateTime) -> Result<Invite, Error> { - let issuer_not_found = || Error::IssuerNotFound(issuer.id.clone().into()); - let issuer_deleted = || Error::IssuerDeleted(issuer.id.clone().into()); + pub async fn issue(&self, issuer: &Login, issued_at: &DateTime) -> Result<Invite, IssueError> { + let issuer_not_found = || IssueError::IssuerNotFound(issuer.id.clone().into()); + let issuer_deleted = || IssueError::IssuerDeleted(issuer.id.clone().into()); - let mut tx = self.db.begin().await?; + let mut tx = self.db.begin().await.fail(db::failed::BEGIN)?; let issuer = tx .users() .by_login(issuer) .await - .not_found(issuer_not_found)?; - let now = tx.sequence().current().await?; + .optional() + .fail("Failed to load issuing user")? + .ok_or_else(issuer_not_found)?; + let now = tx + .sequence() + .current() + .await + .fail("Failed to find event sequence number")?; let issuer = issuer.as_of(now).ok_or_else(issuer_deleted)?; - let invite = tx.invites().create(&issuer, issued_at).await?; - tx.commit().await?; + let invite = tx + .invites() + .create(&issuer, issued_at) + .await + .fail("Failed to store new invitation")?; + tx.commit().await.fail(db::failed::COMMIT)?; Ok(invite) } @@ -60,33 +67,52 @@ impl Invites { password: &Password, accepted_at: &DateTime, ) -> Result<Secret, AcceptError> { + let to_not_found = || AcceptError::NotFound(invite.clone()); + let create = Create::begin(name, password, accepted_at); - let mut tx = self.db.begin().await?; + let mut tx = self.db.begin().await.fail(db::failed::BEGIN)?; let invite = tx .invites() .by_id(invite) .await - .not_found(|| AcceptError::NotFound(invite.clone()))?; + .optional() + .fail("Failed to load invitation")? + .ok_or_else(to_not_found)?; // Split the tx here so we don't block writes while we deal with the password, // and don't deal with the password until we're fairly confident we can accept // the invite. Final validation is in the next tx. - tx.commit().await?; + tx.commit().await.fail(db::failed::COMMIT)?; - let validated = create.validate()?; + let validated = create.validate().map_err(|err| match err { + create::Error::InvalidName(name) => AcceptError::InvalidName(name), + create::Error::Failed(_) => err.fail("Failed to validate invited user"), + })?; - let mut tx = self.db.begin().await?; + let mut tx = self.db.begin().await.fail(db::failed::BEGIN)?; // If the invite has been deleted or accepted in the interim, this step will // catch it. - tx.invites().accept(&invite).await?; - let stored = validated - .store(&mut tx) + tx.invites() + .accept(&invite) .await - .duplicate(|| AcceptError::DuplicateLogin(name.clone()))?; + .fail("Failed to remove accepted invitation")?; + let stored = + validated + .store(&mut tx) + .await + .map_err(|err| match err.as_database_error() { + Some(err) if err.is_unique_violation() => { + AcceptError::DuplicateLogin(name.clone()) + } + _ => err.fail("Failed to store invited user"), + })?; let login = stored.login(); let (token, secret) = Token::generate(login, accepted_at); - tx.tokens().create(&token, &secret).await?; - tx.commit().await?; + tx.tokens() + .create(&token, &secret) + .await + .fail("Failed to issue token for invited user")?; + tx.commit().await.fail(db::failed::COMMIT)?; stored.publish(&self.events); @@ -106,25 +132,13 @@ impl Invites { } #[derive(Debug, thiserror::Error)] -pub enum Error { +pub enum IssueError { #[error("issuing user {0} not found")] IssuerNotFound(user::Id), #[error("issuing user {0} deleted")] IssuerDeleted(user::Id), #[error(transparent)] - Database(#[from] sqlx::Error), - #[error(transparent)] - Name(#[from] name::Error), -} - -impl From<user::repo::LoadError> for Error { - fn from(error: LoadError) -> Self { - use user::repo::LoadError; - match error { - LoadError::Database(error) => error.into(), - LoadError::Name(error) => error.into(), - } - } + Failed(#[from] Failed), } #[derive(Debug, thiserror::Error)] @@ -136,16 +150,5 @@ pub enum AcceptError { #[error("name in use: {0}")] DuplicateLogin(Name), #[error(transparent)] - Database(#[from] sqlx::Error), - #[error(transparent)] - PasswordHash(#[from] password_hash::Error), -} - -impl From<create::Error> for AcceptError { - fn from(error: create::Error) -> Self { - match error { - create::Error::InvalidName(name) => Self::InvalidName(name), - create::Error::PasswordHash(error) => Self::PasswordHash(error), - } - } + Failed(#[from] Failed), } |
