summaryrefslogtreecommitdiff
path: root/src/invite/app.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/invite/app.rs')
-rw-r--r--src/invite/app.rs107
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),
}